lcy
2026-06-23 772fb9532ff627222023e9550e5ec47f08dc4049
662 武将试炼 修复等级达到或官职达到没视为新增的问题
1个文件已修改
303 ■■■■ 已修改文件
Main/System/HeroTrial/HeroTrialManager.cs 303 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroTrial/HeroTrialManager.cs
@@ -12,17 +12,23 @@
    int challengeRecordDay;
    // 当天发起过挑战的武将不再显示红点,胜负无关。
    HashSet<int> challengedHeroIDsToday = new HashSet<int>();
    // 当天新开放的可提示武将,优先于普通候选排队顺延。
    List<int> pendingNewRedHeroIDsToday = new List<int>();
    // 当天新开放过的武将副本。这里保存“发生过新开放”的事实,待显示队列每次刷新时从它派生,避免持久化当前红点/队列进度。
    List<int> newOpenedHeroIDsToday = new List<int>();
    // 历史上客户端已经见过的可挑战武将副本,用于识别当天因等级/官职达成而新解锁的副本。
    HashSet<int> seenUnlockedHeroIDs = new HashSet<int>();
    int currentRedHeroID;
    bool isChallengeRecordLoaded;
    bool needSeedSeenUnlockedHeroIDs;
    bool isRefreshingByPlayerProgress;
    int lastObservedPlayerLV = -1;
    int lastObservedRealmLevel = -1;
    string challengeRecordDayKey { get { return StringUtility.Concat("HeroTrial_ChallengeRecordDay_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string lastChallengeHeroIDTodayKey { get { return StringUtility.Concat("HeroTrial_LastChallengeHeroIDToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string challengedHeroIDsTodayKey { get { return StringUtility.Concat("HeroTrial_ChallengedHeroIDsToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string normalRedConsumedTodayKey { get { return StringUtility.Concat("HeroTrial_NormalRedConsumedToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string pendingNewRedHeroIDsTodayKey { get { return StringUtility.Concat("HeroTrial_PendingNewRedHeroIDsToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string currentRedHeroIDKey { get { return StringUtility.Concat("HeroTrial_CurrentRedHeroID_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string newOpenedHeroIDsTodayKey { get { return StringUtility.Concat("HeroTrial_NewOpenedHeroIDsToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
    string seenUnlockedHeroIDsKey { get { return StringUtility.Concat("HeroTrial_SeenUnlockedHeroIDs_", PlayerDatas.Instance.PlayerId.ToString()); } }
    /// <summary>数据更新事件,UI订阅刷新</summary>
    public event Action OnUpdateHeroTrialInfoEvent;
@@ -72,7 +78,15 @@
        if (type != PlayerDataType.LV && type != PlayerDataType.RealmLevel)
            return;
        RefreshHeroTrialState();
        isRefreshingByPlayerProgress = true;
        try
        {
            RefreshHeroTrialState();
        }
        finally
        {
            isRefreshingByPlayerProgress = false;
        }
    }
    void OnServerOpenDayRefresh()
@@ -110,6 +124,7 @@
        EnsureTodayChallengeRecord();
        RefreshCurrentRedHeroID();
        UpdateRedPoint();
        RecordObservedPlayerProgress();
        OnUpdateHeroTrialInfoEvent?.Invoke();
    }
@@ -129,36 +144,43 @@
        }
        // 红点规则:
        // 1. 当天新开放的武将优先显示,并按配置顺序顺延。
        // 2. 当天新开放队列被挑战完后,当天不再回流到旧武将副本。
        // 3. 今天没有新开放提示链路且普通红点未被挑战消费时,显示所有已解锁且有可挑战关卡的武将里,当前可挑战关卡战力最低的那个。
        // 1. 当天新开放武将优先,按配置顺序逐个提示;新开放来源包含开服天、等级、官职。
        // 2. 只保存“今天出现过哪些新开放武将”,当前显示目标每次刷新实时派生,避免本地存储队列进度。
        // 3. 今天出现过新开放武将后,队列耗尽也不回流旧武将;完全没有新开放时,旧武将一天只提示一次最低战力目标。
        List<int> heroIDList = GetHeroTrialHeroIDList();
        bool onlyProgressUnlocksCanBecomeNew = needSeedSeenUnlockedHeroIDs && isRefreshingByPlayerProgress;
        if (needSeedSeenUnlockedHeroIDs && !onlyProgressUnlocksCanBecomeNew)
        {
            SeedSeenUnlockedHeroIDs(heroIDList);
            needSeedSeenUnlockedHeroIDs = false;
            isDirty = true;
        }
        if (DetectNewOpenedHeroIDsToday(heroIDList, onlyProgressUnlocksCanBecomeNew))
            isDirty = true;
        if (onlyProgressUnlocksCanBecomeNew)
        {
            needSeedSeenUnlockedHeroIDs = false;
            isDirty = true;
        }
        if (currentRedHeroID != 0 && !CanShowHeroItemRed(currentRedHeroID))
        {
            currentRedHeroID = 0;
            isDirty = true;
        }
        List<int> heroIDList = GetHeroTrialHeroIDList();
        List<int> candidateHeroIDs = BuildCandidateHeroIDs(heroIDList);
        List<int> todayOpenHeroIDs = BuildTodayOpenHeroIDs(candidateHeroIDs);
        RemoveHeroIDs(candidateHeroIDs, todayOpenHeroIDs);
        if (NormalizePendingNewRedHeroIDs(heroIDList))
            isDirty = true;
        if (AppendTodayOpenPendingCandidates(todayOpenHeroIDs))
            isDirty = true;
        if (NormalizePendingNewRedHeroIDs(heroIDList))
            isDirty = true;
        if (PromoteTodayOpenPendingRed(todayOpenHeroIDs))
        int nextNewOpenedHeroID = GetNextNewOpenedRedHeroID(heroIDList);
        if (nextNewOpenedHeroID != 0)
        {
            isDirty = true;
            if (currentRedHeroID != nextNewOpenedHeroID)
            {
                currentRedHeroID = nextNewOpenedHeroID;
                isDirty = true;
            }
        }
        else if (HasChallengedTodayOpenHeroTrialToday() &&
            pendingNewRedHeroIDsToday.Count == 0 &&
            (currentRedHeroID == 0 || !IsHeroTrialHeroOpenToday(currentRedHeroID)))
        else if (newOpenedHeroIDsToday.Count > 0)
        {
            if (currentRedHeroID != 0)
            {
@@ -166,7 +188,7 @@
                isDirty = true;
            }
        }
        else if (!normalRedConsumedToday && (currentRedHeroID == 0 || !IsHeroTrialHeroOpenToday(currentRedHeroID)))
        else if (!normalRedConsumedToday)
        {
            int bestHeroID = GetLowestCanChallengeHeroID(heroIDList);
            if (currentRedHeroID != bestHeroID)
@@ -180,43 +202,86 @@
            SaveTodayChallengeRecord();
    }
    List<int> BuildCandidateHeroIDs(List<int> sortedHeroIDList)
    bool DetectNewOpenedHeroIDsToday(List<int> sortedHeroIDList, bool onlyProgressUnlocksCanBecomeNew)
    {
        List<int> candidateHeroIDs = new List<int>();
        bool isDirty = false;
        for (int i = 0; i < sortedHeroIDList.Count; i++)
        {
            int heroID = sortedHeroIDList[i];
            if (!CanShowHeroItemRed(heroID))
            if (!IsVisibleUnlockedHeroTrial(heroID))
                continue;
            candidateHeroIDs.Add(heroID);
            bool unlockedByPlayerProgressNow = isRefreshingByPlayerProgress && IsHeroTrialHeroUnlockedByPlayerProgressNow(heroID);
            // 首次见到一个已解锁且有可挑战关卡的武将,说明它刚由等级/官职条件进入可提示范围。
            if (seenUnlockedHeroIDs.Add(heroID))
            {
                if (!onlyProgressUnlocksCanBecomeNew || unlockedByPlayerProgressNow)
                    AppendNewOpenedHeroIDToday(heroID);
                isDirty = true;
            }
            else if (unlockedByPlayerProgressNow && AppendNewOpenedHeroIDToday(heroID))
            {
                // 兼容旧本地记录:若历史已见集合曾误记该武将,本次明确跨过等级/官职门槛时仍按新开放处理。
                isDirty = true;
            }
            // 开服天新开放不依赖历史已见集合;当天首次刷新就应进入新开放优先队列。
            if (IsHeroTrialHeroOpenByServerDayToday(heroID) && AppendNewOpenedHeroIDToday(heroID))
                isDirty = true;
        }
        return candidateHeroIDs;
        return isDirty;
    }
    List<int> BuildTodayOpenHeroIDs(List<int> candidateHeroIDs)
    bool IsHeroTrialHeroUnlockedByPlayerProgressNow(int heroID)
    {
        List<int> todayOpenHeroIDs = new List<int>();
        for (int i = 0; i < candidateHeroIDs.Count; i++)
        {
            int heroID = candidateHeroIDs[i];
            if (IsHeroTrialHeroOpenToday(heroID))
                todayOpenHeroIDs.Add(heroID);
        }
        return todayOpenHeroIDs;
        if (!HeroTrialConfig.TryGetFirstLevelConfig(heroID, out var config))
            return false;
        int currentLV = PlayerDatas.Instance.baseData.LV;
        int currentRealmLevel = PlayerDatas.Instance.baseData.realmLevel;
        bool levelJustReached = config.LVLimit > 0 && currentLV >= config.LVLimit &&
            ((lastObservedPlayerLV >= 0 && lastObservedPlayerLV < config.LVLimit) ||
            (lastObservedPlayerLV < 0 && currentLV == config.LVLimit));
        bool realmJustReached = config.RealmLimit > 0 && currentRealmLevel >= config.RealmLimit &&
            ((lastObservedRealmLevel >= 0 && lastObservedRealmLevel < config.RealmLimit) ||
            (lastObservedRealmLevel < 0 && currentRealmLevel == config.RealmLimit));
        return levelJustReached || realmJustReached;
    }
    bool HasChallengedTodayOpenHeroTrialToday()
    void RecordObservedPlayerProgress()
    {
        foreach (int heroID in challengedHeroIDsToday)
        {
            if (IsHeroTrialHeroOpenToday(heroID))
                return true;
        }
        return false;
        lastObservedPlayerLV = PlayerDatas.Instance.baseData.LV;
        lastObservedRealmLevel = PlayerDatas.Instance.baseData.realmLevel;
    }
    bool IsHeroTrialHeroOpenToday(int heroID)
    void SeedSeenUnlockedHeroIDs(List<int> sortedHeroIDList)
    {
        // 没有历史已见记录时,先把当前可见副本作为旧状态记住,避免版本升级后把所有旧副本误判成当天新开放。
        for (int i = 0; i < sortedHeroIDList.Count; i++)
        {
            int heroID = sortedHeroIDList[i];
            if (IsVisibleUnlockedHeroTrial(heroID))
                seenUnlockedHeroIDs.Add(heroID);
        }
    }
    bool IsVisibleUnlockedHeroTrial(int heroID)
    {
        return IsHeroTrialHeroUnlocked(heroID) && HasChallengeableLevel(heroID);
    }
    bool AppendNewOpenedHeroIDToday(int heroID)
    {
        if (heroID == 0 || newOpenedHeroIDsToday.Contains(heroID))
            return false;
        newOpenedHeroIDsToday.Add(heroID);
        return true;
    }
    bool IsHeroTrialHeroOpenByServerDayToday(int heroID)
    {
        HeroConfig heroConfig = HeroConfig.Get(heroID);
        return heroConfig != null &&
@@ -224,87 +289,16 @@
            heroConfig.OpenCollectionDay == TimeUtility.OpenDay + 1;
    }
    void RemoveHeroIDs(List<int> heroIDs, List<int> removeHeroIDs)
    int GetNextNewOpenedRedHeroID(List<int> sortedHeroIDList)
    {
        if (removeHeroIDs.IsNullOrEmpty())
            return;
        for (int i = heroIDs.Count - 1; i >= 0; i--)
        for (int i = 0; i < sortedHeroIDList.Count; i++)
        {
            if (removeHeroIDs.Contains(heroIDs[i]))
                heroIDs.RemoveAt(i);
        }
    }
    bool AppendTodayOpenPendingCandidates(List<int> candidateHeroIDs)
    {
        bool isDirty = false;
        for (int i = 0; i < candidateHeroIDs.Count; i++)
        {
            int heroID = candidateHeroIDs[i];
            if (AppendPendingCandidate(heroID))
                isDirty = true;
            int heroID = sortedHeroIDList[i];
            if (newOpenedHeroIDsToday.Contains(heroID) && CanShowHeroItemRed(heroID))
                return heroID;
        }
        return isDirty;
    }
    bool AppendPendingCandidate(int heroID)
    {
        if (heroID == currentRedHeroID || pendingNewRedHeroIDsToday.Contains(heroID))
            return false;
        pendingNewRedHeroIDsToday.Add(heroID);
        return true;
    }
    bool NormalizePendingNewRedHeroIDs(List<int> sortedHeroIDList)
    {
        // 队列只保留仍可提示的当天新开放武将,并按配置顺序归一,保证 E -> F -> G 顺延。
        List<int> normalizedHeroIDs = new List<int>();
        for (int i = 0; i < pendingNewRedHeroIDsToday.Count; i++)
        {
            int heroID = pendingNewRedHeroIDsToday[i];
            if (heroID == currentRedHeroID)
                continue;
            if (!IsHeroTrialHeroOpenToday(heroID))
                continue;
            if (!CanShowHeroItemRed(heroID))
                continue;
            if (!normalizedHeroIDs.Contains(heroID))
                normalizedHeroIDs.Add(heroID);
        }
        normalizedHeroIDs.Sort((a, b) => GetHeroSortIndex(sortedHeroIDList, a).CompareTo(GetHeroSortIndex(sortedHeroIDList, b)));
        if (IsSameHeroIDList(pendingNewRedHeroIDsToday, normalizedHeroIDs))
            return false;
        pendingNewRedHeroIDsToday.Clear();
        pendingNewRedHeroIDsToday.AddRange(normalizedHeroIDs);
        return true;
    }
    int GetHeroSortIndex(List<int> sortedHeroIDList, int heroID)
    {
        int index = sortedHeroIDList.IndexOf(heroID);
        return index >= 0 ? index : int.MaxValue;
    }
    bool IsSameHeroIDList(List<int> listA, List<int> listB)
    {
        if (listA.Count != listB.Count)
            return false;
        for (int i = 0; i < listA.Count; i++)
        {
            if (listA[i] != listB[i])
                return false;
        }
        return true;
        return 0;
    }
    int GetLowestCanChallengeHeroID(List<int> heroIDList)
@@ -391,29 +385,6 @@
        }
        return config != null;
    }
    bool PromoteTodayOpenPendingRed(List<int> todayOpenHeroIDs)
    {
        if (todayOpenHeroIDs.IsNullOrEmpty())
            return false;
        if (currentRedHeroID != 0 && todayOpenHeroIDs.Contains(currentRedHeroID))
            return false;
        for (int i = 0; i < pendingNewRedHeroIDsToday.Count; i++)
        {
            int heroID = pendingNewRedHeroIDsToday[i];
            if (!todayOpenHeroIDs.Contains(heroID))
                continue;
            // 今天新开放的武将优先展示;队列耗尽后当天不再回流到旧武将副本。
            currentRedHeroID = heroID;
            pendingNewRedHeroIDsToday.RemoveAt(i);
            return true;
        }
        return false;
    }
    /// <summary>
@@ -589,10 +560,10 @@
            lastChallengeHeroIDToday = config.HeroID;
            SaveTodayChallengeRecord();
        }
        bool isDirty = pendingNewRedHeroIDsToday.Remove(config.HeroID);
        bool isDirty = false;
        if (currentRedHeroID == config.HeroID)
        {
            if (!IsHeroTrialHeroOpenToday(config.HeroID))
            if (!newOpenedHeroIDsToday.Contains(config.HeroID))
                normalRedConsumedToday = true;
            currentRedHeroID = 0;
@@ -651,9 +622,9 @@
        challengeRecordDay = today;
        challengedHeroIDsToday.Clear();
        newOpenedHeroIDsToday.Clear();
        lastChallengeHeroIDToday = 0;
        normalRedConsumedToday = false;
        pendingNewRedHeroIDsToday.Clear();
        currentRedHeroID = 0;
        SaveTodayChallengeRecord();
    }
@@ -662,24 +633,30 @@
    {
        challengeRecordDay = 0;
        challengedHeroIDsToday.Clear();
        newOpenedHeroIDsToday.Clear();
        seenUnlockedHeroIDs.Clear();
        lastChallengeHeroIDToday = 0;
        normalRedConsumedToday = false;
        pendingNewRedHeroIDsToday.Clear();
        currentRedHeroID = 0;
        isChallengeRecordLoaded = false;
        needSeedSeenUnlockedHeroIDs = false;
        isRefreshingByPlayerProgress = false;
        lastObservedPlayerLV = -1;
        lastObservedRealmLevel = -1;
    }
    void LoadTodayChallengeRecord()
    {
        isChallengeRecordLoaded = true;
        challengeRecordDay = LocalSave.GetInt(challengeRecordDayKey);
        needSeedSeenUnlockedHeroIDs = !LoadHeroIDSet(seenUnlockedHeroIDsKey, seenUnlockedHeroIDs);
        if (challengeRecordDay != GetTodayID())
        {
            challengeRecordDay = GetTodayID();
            challengedHeroIDsToday.Clear();
            newOpenedHeroIDsToday.Clear();
            lastChallengeHeroIDToday = 0;
            normalRedConsumedToday = false;
            pendingNewRedHeroIDsToday.Clear();
            currentRedHeroID = 0;
            SaveTodayChallengeRecord();
            return;
@@ -688,8 +665,8 @@
        LoadHeroIDSet(challengedHeroIDsTodayKey, challengedHeroIDsToday);
        lastChallengeHeroIDToday = LocalSave.GetInt(lastChallengeHeroIDTodayKey);
        normalRedConsumedToday = LocalSave.GetInt(normalRedConsumedTodayKey) != 0;
        LoadHeroIDList(pendingNewRedHeroIDsTodayKey, pendingNewRedHeroIDsToday);
        currentRedHeroID = LocalSave.GetInt(currentRedHeroIDKey);
        LoadHeroIDList(newOpenedHeroIDsTodayKey, newOpenedHeroIDsToday);
        currentRedHeroID = 0;
    }
    void SaveTodayChallengeRecord()
@@ -698,22 +675,24 @@
        LocalSave.SetIntArray(challengedHeroIDsTodayKey, ToArray(challengedHeroIDsToday));
        LocalSave.SetInt(lastChallengeHeroIDTodayKey, lastChallengeHeroIDToday);
        LocalSave.SetInt(normalRedConsumedTodayKey, normalRedConsumedToday ? 1 : 0);
        LocalSave.SetIntArray(pendingNewRedHeroIDsTodayKey, pendingNewRedHeroIDsToday.ToArray());
        LocalSave.SetInt(currentRedHeroIDKey, currentRedHeroID);
        LocalSave.SetIntArray(newOpenedHeroIDsTodayKey, newOpenedHeroIDsToday.ToArray());
        LocalSave.SetIntArray(seenUnlockedHeroIDsKey, ToArray(seenUnlockedHeroIDs));
    }
    void LoadHeroIDSet(string key, HashSet<int> heroIDSet)
    bool LoadHeroIDSet(string key, HashSet<int> heroIDSet)
    {
        heroIDSet.Clear();
        int[] heroIDs = LocalSave.GetIntArray(key);
        if (heroIDs == null)
            return;
            return false;
        for (int i = 0; i < heroIDs.Length; i++)
        {
            if (heroIDs[i] != 0)
                heroIDSet.Add(heroIDs[i]);
        }
        return true;
    }
    void LoadHeroIDList(string key, List<int> heroIDList)