From 9eb225e0b1297fdc47b558c922c6ed5248dfd95d Mon Sep 17 00:00:00 2001
From: lcy <1459594991@qq.com>
Date: 星期二, 23 六月 2026 17:16:51 +0800
Subject: [PATCH] 662 武将试炼 重构红点系统,精简本地存储内容

---
 Main/System/HeroTrial/HeroTrialManager.cs | 1177 ++++++++++++++++++++++++++--------------------------------
 1 files changed, 533 insertions(+), 644 deletions(-)

diff --git a/Main/System/HeroTrial/HeroTrialManager.cs b/Main/System/HeroTrial/HeroTrialManager.cs
index f16dfe6..ed17929 100644
--- a/Main/System/HeroTrial/HeroTrialManager.cs
+++ b/Main/System/HeroTrial/HeroTrialManager.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using LitJson;
 
 public class HeroTrialManager : GameSystemManager<HeroTrialManager>
 {
@@ -9,45 +10,22 @@
     /// <summary>姝﹀皢璇曠偧杩涘害瀛楀吀 <HeroID, PassLevelNum></summary>
     public Dictionary<uint, byte> heroTrialDict = new Dictionary<uint, byte>();
 
-    int challengeRecordDay;
-    // 褰撳ぉ鍙戣捣杩囨寫鎴樼殑姝﹀皢涓嶅啀鏄剧ず绾㈢偣锛岃儨璐熸棤鍏炽��
-    HashSet<int> challengedHeroIDsToday = new HashSet<int>();
-    // 褰撳ぉ鏂板紑鏀捐繃鐨勬灏嗗壇鏈�傝繖閲屼繚瀛樷�滃彂鐢熻繃鏂板紑鏀锯�濈殑浜嬪疄锛屽緟鏄剧ず闃熷垪姣忔鍒锋柊鏃朵粠瀹冩淳鐢燂紝閬垮厤鎸佷箙鍖栧綋鍓嶇孩鐐�/闃熷垪杩涘害銆�
-    List<int> newOpenedHeroIDsToday = new List<int>();
-    // 鍘嗗彶涓婂鎴风宸茬粡瑙佽繃鐨勫彲鎸戞垬姝﹀皢鍓湰锛岀敤浜庤瘑鍒綋澶╁洜绛夌骇/瀹樿亴杈炬垚鑰屾柊瑙i攣鐨勫壇鏈��
-    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 newOpenedHeroIDsTodayKey { get { return StringUtility.Concat("HeroTrial_NewOpenedHeroIDsToday_", PlayerDatas.Instance.PlayerId.ToString()); } }
-    string seenUnlockedHeroIDsKey { get { return StringUtility.Concat("HeroTrial_SeenUnlockedHeroIDs_", PlayerDatas.Instance.PlayerId.ToString()); } }
-
-    /// <summary>鏁版嵁鏇存柊浜嬩欢锛孶I璁㈤槄鍒锋柊</summary>
-    public event Action OnUpdateHeroTrialInfoEvent;
-
-    /// <summary>涓绘寫鎴樼孩鐐�</summary>
-    public Redpoint parentRedpoint = new Redpoint(MainRedDot.MainChallengeRedpoint, MainRedDot.HeroTrialRepoint);
-
-    // 褰撳ぉ鏈�鍚庝竴娆″彂璧锋寫鎴樼殑姝﹀皢鍓湰锛岀敤浜庣晫闈㈤娆℃墦寮�鏃剁殑榛樿璺宠浆銆�
-    int lastChallengeHeroIDToday;
-    // 鏅�氱孩鐐瑰綋澶╁彧鎻愮ず涓�娆★紱褰撳ぉ鏂板紑鏀炬灏嗗壇鏈粛鍙嫭绔嬫帓闃熸彁绀恒��
-    bool normalRedConsumedToday;
+    // 鏈湴鍙繚瀛� day/new/consumed 浜嬪疄锛涙渶缁堢孩鐐圭洰鏍囧缁堢敱褰撳墠閰嶇疆銆佽繘搴﹀拰娑堣垂鐘舵�佸疄鏃惰绠椼��
+    bool hasLoadedLocalRecord;
+    // 棣栨蹇収鍙缓绔嬪熀绾匡紝閬垮厤鐧诲綍鏃舵妸鍘嗗彶宸插紑鏀惧壇鏈兘璇嗗埆鎴愨�滀粖鏃ユ柊寮�鏀锯�濄��
+    bool hasAvailableSnapshot;
+    int todayKey;
+    readonly HashSet<int> todayNewOpenHeroIDs = new HashSet<int>();
+    readonly HashSet<int> consumedRedHeroIDsToday = new HashSet<int>();
+    readonly HashSet<int> knownAvailableHeroIDs = new HashSet<int>();
 
     public override void Init()
     {
         DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin += OnBeforePlayerDataInitializeEventOnRelogin;
         FuncOpen.Instance.OnFuncStateChangeEvent += OnFuncStateChangeEvent;
         PlayerDatas.Instance.playerDataRefreshEvent += OnPlayerDataRefreshEvent;
+        TimeMgr.Instance.OnDayEvent += OnDayEvent;
         TimeUtility.OnServerOpenDayRefresh += OnServerOpenDayRefresh;
-        TimeUtility.OnServerTimeRefresh += OnServerTimeRefresh;
     }
 
     public override void Release()
@@ -55,22 +33,29 @@
         DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin -= OnBeforePlayerDataInitializeEventOnRelogin;
         FuncOpen.Instance.OnFuncStateChangeEvent -= OnFuncStateChangeEvent;
         PlayerDatas.Instance.playerDataRefreshEvent -= OnPlayerDataRefreshEvent;
+        TimeMgr.Instance.OnDayEvent -= OnDayEvent;
         TimeUtility.OnServerOpenDayRefresh -= OnServerOpenDayRefresh;
-        TimeUtility.OnServerTimeRefresh -= OnServerTimeRefresh;
     }
 
     void OnBeforePlayerDataInitializeEventOnRelogin()
     {
         heroTrialDict.Clear();
-        ResetTodayChallengeRecordCache();
-        parentRedpoint.state = RedPointState.None;
+        hasLoadedLocalRecord = false;
+        hasAvailableSnapshot = false;
+        todayKey = 0;
+        lastChallengeHeroIDToday = 0;
+        todayNewOpenHeroIDs.Clear();
+        consumedRedHeroIDsToday.Clear();
+        knownAvailableHeroIDs.Clear();
+        ClearRedpoint();
     }
 
     void OnFuncStateChangeEvent(int obj)
     {
         if (obj != funcId)
             return;
-        RefreshHeroTrialState();
+
+        RefreshRedpointAndNotify();
     }
 
     void OnPlayerDataRefreshEvent(PlayerDataType type)
@@ -78,331 +63,59 @@
         if (type != PlayerDataType.LV && type != PlayerDataType.RealmLevel)
             return;
 
-        isRefreshingByPlayerProgress = true;
-        try
-        {
-            RefreshHeroTrialState();
-        }
-        finally
-        {
-            isRefreshingByPlayerProgress = false;
-        }
+        // 绛夌骇/瀹樿亴浼氬奖鍝嶈В閿併�侀攣瀹氭彁绀哄拰鎸戞垬鎸夐挳锛屽嵆浣跨孩鐐圭洰鏍囦笉鍙樹篃瑕佸埛鏂扮晫闈€��
+        RefreshRedpointAndNotify(true);
+    }
+
+    void OnDayEvent()
+    {
+        // 杩欓噷鍙唬琛ㄦ湇鍔″櫒鏃ユ湡鍙樺寲锛屼笉淇濊瘉 OpenDay 宸叉洿鏂般��
+        // 姝﹀皢璇曠偧闄愬畾鍓湰渚濊禆 OpenDay锛屽繀椤荤瓑 OnServerOpenDayRefresh 鍚庡啀閲嶇畻銆�
     }
 
     void OnServerOpenDayRefresh()
     {
-        RefreshHeroTrialState();
+        // 闄愬畾姝﹀皢寮�鏀句緷璧� OpenDay锛涜繖涓簨浠跺湪 OpenDay 鏇存柊鍚庤Е鍙戯紝蹇呴』鍐嶅埛鏂颁竴娆°��
+        RefreshRedpointAndNotify(true);
     }
-
-    void OnServerTimeRefresh()
+    
+    public void SendChallenge(HeroTrialConfig config)
     {
-        int oldDay = challengeRecordDay;
-        EnsureTodayChallengeRecord();
-        if (oldDay != challengeRecordDay)
-            RefreshHeroTrialState();
+        if (config == null)
+            return;
+
+        if (!CanChallenge(config, out string tip))
+        {
+            if (!string.IsNullOrEmpty(tip))
+                SysNotifyMgr.Instance.ShowStringTip(tip);
+            return;
+        }
+
+        ConsumeRedpointOnChallenge(config.HeroID);
+        BattleManager.Instance.SendTurnFight((uint)DataMapID, (uint)GetFuncLineID(config));
     }
 
+    public event Action OnUpdateHeroTrialInfoEvent;
     /// <summary>
     /// 澶勭悊 B203 灏佸寘锛屾洿鏂版灏嗚瘯鐐兼暟鎹�
     /// </summary>
     public void UpdateHeroTrialInfo(HB203_tagSCHeroTrialInfo vNetData)
     {
+        // 鏈嶅姟鍣ㄥ寘涓虹┖鏃舵病鏈夊彲鏇存柊鐨勮瘯鐐艰繘搴︺��
         if (vNetData == null || vNetData.HeroTrialList == null)
             return;
 
+        // 鎶婃湇鍔″櫒涓嬪彂鐨勬瘡涓灏嗛�氬叧杩涘害鍐欏叆瀛楀吀銆�
         for (int i = 0; i < vNetData.HeroTrialList.Length; i++)
         {
             var item = vNetData.HeroTrialList[i];
             heroTrialDict[item.HeroID] = item.PassLevelNum;
         }
 
-        RefreshHeroTrialState();
-    }
-
-    void RefreshHeroTrialState()
-    {
-        EnsureTodayChallengeRecord();
-        RefreshCurrentRedHeroID();
-        UpdateRedPoint();
-        RecordObservedPlayerProgress();
+        UpdateRedpoint();
         OnUpdateHeroTrialInfoEvent?.Invoke();
+
     }
-
-    void RefreshCurrentRedHeroID()
-    {
-        bool isDirty = false;
-        if (!FuncOpen.Instance.IsFuncOpen(funcId))
-        {
-            if (currentRedHeroID != 0)
-            {
-                currentRedHeroID = 0;
-                isDirty = true;
-            }
-            if (isDirty)
-                SaveTodayChallengeRecord();
-            return;
-        }
-
-        // 绾㈢偣瑙勫垯锛�
-        // 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;
-        }
-
-        int nextNewOpenedHeroID = GetNextNewOpenedRedHeroID(heroIDList);
-        if (nextNewOpenedHeroID != 0)
-        {
-            if (currentRedHeroID != nextNewOpenedHeroID)
-            {
-                currentRedHeroID = nextNewOpenedHeroID;
-                isDirty = true;
-            }
-        }
-        else if (newOpenedHeroIDsToday.Count > 0)
-        {
-            if (currentRedHeroID != 0)
-            {
-                currentRedHeroID = 0;
-                isDirty = true;
-            }
-        }
-        else if (!normalRedConsumedToday)
-        {
-            int bestHeroID = GetLowestCanChallengeHeroID(heroIDList);
-            if (currentRedHeroID != bestHeroID)
-            {
-                currentRedHeroID = bestHeroID;
-                isDirty = true;
-            }
-        }
-
-        if (isDirty)
-            SaveTodayChallengeRecord();
-    }
-
-    bool DetectNewOpenedHeroIDsToday(List<int> sortedHeroIDList, bool onlyProgressUnlocksCanBecomeNew)
-    {
-        bool isDirty = false;
-        for (int i = 0; i < sortedHeroIDList.Count; i++)
-        {
-            int heroID = sortedHeroIDList[i];
-            if (!IsVisibleUnlockedHeroTrial(heroID))
-                continue;
-
-            bool unlockedByPlayerProgressNow = isRefreshingByPlayerProgress && IsHeroTrialHeroUnlockedByPlayerProgressNow(heroID);
-            // 棣栨瑙佸埌涓�涓凡瑙i攣涓旀湁鍙寫鎴樺叧鍗$殑姝﹀皢锛岃鏄庡畠鍒氱敱绛夌骇/瀹樿亴鏉′欢杩涘叆鍙彁绀鸿寖鍥淬��
-            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 isDirty;
-    }
-
-    bool IsHeroTrialHeroUnlockedByPlayerProgressNow(int heroID)
-    {
-        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;
-    }
-
-    void RecordObservedPlayerProgress()
-    {
-        lastObservedPlayerLV = PlayerDatas.Instance.baseData.LV;
-        lastObservedRealmLevel = PlayerDatas.Instance.baseData.realmLevel;
-    }
-
-    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 &&
-            heroConfig.OpenCollectionDay > 0 &&
-            heroConfig.OpenCollectionDay == TimeUtility.OpenDay + 1;
-    }
-
-    int GetNextNewOpenedRedHeroID(List<int> sortedHeroIDList)
-    {
-        for (int i = 0; i < sortedHeroIDList.Count; i++)
-        {
-            int heroID = sortedHeroIDList[i];
-            if (newOpenedHeroIDsToday.Contains(heroID) && CanShowHeroItemRed(heroID))
-                return heroID;
-        }
-
-        return 0;
-    }
-
-    int GetLowestCanChallengeHeroID(List<int> heroIDList)
-    {
-        int bestHeroID = 0;
-        long bestFightPower = long.MaxValue;
-        int bestSortIndex = int.MaxValue;
-
-        for (int i = 0; i < heroIDList.Count; i++)
-        {
-            int heroID = heroIDList[i];
-            if (!CanShowHeroItemRed(heroID))
-                continue;
-
-            if (!TryGetLowestCanChallengeConfig(heroID, out var config))
-                continue;
-
-            if (config.FightPower < bestFightPower)
-            {
-                bestHeroID = heroID;
-                bestFightPower = config.FightPower;
-                bestSortIndex = i;
-                continue;
-            }
-
-            if (config.FightPower == bestFightPower && i < bestSortIndex)
-            {
-                bestHeroID = heroID;
-                bestSortIndex = i;
-            }
-        }
-
-        return bestHeroID;
-    }
-
-    int GetLowestEnterHeroID(List<int> heroIDList)
-    {
-        int bestHeroID = 0;
-        long bestFightPower = long.MaxValue;
-        int bestSortIndex = int.MaxValue;
-
-        for (int i = 0; i < heroIDList.Count; i++)
-        {
-            int heroID = heroIDList[i];
-            if (!IsHeroTrialHeroUnlocked(heroID))
-                continue;
-            if (!HasChallengeableLevel(heroID))
-                continue;
-
-            if (!TryGetLowestCanChallengeConfig(heroID, out var config))
-                continue;
-
-            if (config.FightPower < bestFightPower)
-            {
-                bestHeroID = heroID;
-                bestFightPower = config.FightPower;
-                bestSortIndex = i;
-                continue;
-            }
-
-            if (config.FightPower == bestFightPower && i < bestSortIndex)
-            {
-                bestHeroID = heroID;
-                bestSortIndex = i;
-            }
-        }
-
-        return bestHeroID;
-    }
-
-    bool TryGetLowestCanChallengeConfig(int heroID, out HeroTrialConfig config)
-    {
-        config = null;
-        if (!HeroTrialConfig.TryGetHeroConfigList(heroID, out var configs))
-            return false;
-
-        for (int i = 0; i < configs.Count; i++)
-        {
-            if (!CanChallenge(configs[i], out _))
-                continue;
-
-            if (config == null || configs[i].FightPower < config.FightPower)
-                config = configs[i];
-        }
-
-        return config != null;
-    }
-
-    /// <summary>
-    /// 鏇存柊绾㈢偣鐘舵��
-    /// </summary>
-    public void UpdateRedPoint()
-    {
-        parentRedpoint.state = RedPointState.None;
-
-        if (!FuncOpen.Instance.IsFuncOpen(funcId))
-            return;
-
-        if (currentRedHeroID != 0)
-        {
-            parentRedpoint.state = RedPointState.Simple;
-        }
-    }
-
     public int GetFuncLineID(HeroTrialConfig config)
     {
         if (config == null)
@@ -427,108 +140,32 @@
         return HeroTrialConfig.TryGetHeroTrialConfig(heroID, levelNum, out config);
     }
 
-    public bool CanChallenge(HeroTrialConfig config, out string tip)
+    // 鏄笉鏄檺瀹氭灏�
+    bool IsHeroTrialHeroVisible(int heroID)
     {
-        tip = string.Empty;
-        if (config == null)
-            return false;
-
-        if (!IsHeroTrialHeroUnlocked(config.HeroID, out string heroUnlockTip))
-        {
-            tip = heroUnlockTip;
-            return false;
-        }
-
-        if (!IsTrialLevelLimitMet(config, out tip))
-            return false;
-
-        bool hasProgress = TryGetHeroPassLevelNum((uint)config.HeroID, out byte passLevelNum);
-        bool isFirstLevel = HeroTrialConfig.TryGetFirstLevelConfig(config.HeroID, out var firstConfig) && firstConfig.CfgID == config.CfgID;
-        if (!hasProgress)
-        {
-            passLevelNum = 0;
-            if (isFirstLevel && passLevelNum < config.LevelNum)
-                return true;
-
-            tip = Language.Get("HeroTrial05");
-            return false;
-        }
-
-        if (isFirstLevel && passLevelNum < config.LevelNum)
+        HeroConfig heroConfig = HeroConfig.Get(heroID);
+        if (heroConfig == null)
             return true;
 
-        if (config.LevelNum != passLevelNum + 1)
+        return heroConfig.IsActLimit <= 0 ||
+            heroConfig.OpenCollectionDay <= 0 ||
+            TimeUtility.OpenDay + 1 >= heroConfig.OpenCollectionDay;
+    }
+
+    // 鑾峰彇姝﹀皢璇曠偧姝﹀皢鍒楄〃
+    public List<int> GetHeroTrialHeroIDList()
+    {
+        List<int> heroIDList = HeroTrialConfig.GetSortedHeroIDList();
+        for (int i = heroIDList.Count - 1; i >= 0; i--)
         {
-            tip = Language.Get("HeroTrial05");
-            return false;
+            if (!IsHeroTrialHeroVisible(heroIDList[i]))
+                heroIDList.RemoveAt(i);
         }
 
-        return true;
+        return heroIDList;
     }
 
-    bool IsTrialLevelLimitMet(HeroTrialConfig config, out string tip)
-    {
-        tip = string.Empty;
-        if (config == null)
-            return false;
-
-        if (config.LVLimit > 0 && PlayerDatas.Instance.baseData.LV < config.LVLimit)
-        {
-            tip = Language.Get("HeroTrial04", config.LVLimit);
-            return false;
-        }
-
-        if (config.RealmLimit > 0 && PlayerDatas.Instance.baseData.realmLevel < config.RealmLimit)
-        {
-            var realmConfig = RealmConfig.Get(config.RealmLimit);
-            tip = Language.Get("HeroTrial03", realmConfig != null ? realmConfig.Name : string.Empty);
-            return false;
-        }
-
-        return true;
-    }
-
-    public bool IsShowHeroItemRed(int heroID)
-    {
-        return heroID != 0 &&
-            heroID == currentRedHeroID &&
-            CanShowHeroItemRed(heroID) &&
-            TryGetLowestCanChallengeConfig(heroID, out _);
-    }
-
-    public bool IsShowHeroTrialLevelRed(HeroTrialConfig config)
-    {
-        if (config == null || config.HeroID != currentRedHeroID)
-            return false;
-
-        if (!CanChallenge(config, out _))
-            return false;
-
-        return TryGetLowestCanChallengeConfig(config.HeroID, out var lowestConfig) &&
-            lowestConfig != null &&
-            lowestConfig.CfgID == config.CfgID;
-    }
-
-    public bool HasAnyChallengeableLevel()
-    {
-        List<int> heroIDList = GetHeroTrialHeroIDList();
-        for (int i = 0; i < heroIDList.Count; i++)
-        {
-            if (HasChallengeableLevel(heroIDList[i]))
-                return true;
-        }
-
-        return false;
-    }
-
-    bool CanShowHeroItemRed(int heroID)
-    {
-        return FuncOpen.Instance.IsFuncOpen(funcId) &&
-            IsHeroTrialHeroUnlocked(heroID) &&
-            !HasChallengedHeroTrialToday(heroID) &&
-            HasChallengeableLevel(heroID);
-    }
-
+    // 妫�鏌ユ灏嗘槸鍚︽湁鍙寫鎴樺叧鍗�
     bool HasChallengeableLevel(int heroID)
     {
         if (!HeroTrialConfig.TryGetHeroConfigList(heroID, out var configs))
@@ -543,211 +180,51 @@
         return false;
     }
 
-    public void SendChallenge(HeroTrialConfig config)
+    // 鏄惁浠绘剰涓�涓灏嗗瓨鍦ㄥ彲鎸戞垬鍏冲崱
+    public bool HasAnyChallengeableLevel()
     {
-        if (!CanChallenge(config, out string tip))
-        {
-            if (!string.IsNullOrEmpty(tip))
-            {
-                SysNotifyMgr.Instance.ShowStringTip(tip);
-            }
-            return;
-        }
-
-        MarkHeroTrialChallengedToday(config.HeroID);
-        if (lastChallengeHeroIDToday != config.HeroID)
-        {
-            lastChallengeHeroIDToday = config.HeroID;
-            SaveTodayChallengeRecord();
-        }
-        bool isDirty = false;
-        if (currentRedHeroID == config.HeroID)
-        {
-            if (!newOpenedHeroIDsToday.Contains(config.HeroID))
-                normalRedConsumedToday = true;
-
-            currentRedHeroID = 0;
-            isDirty = true;
-        }
-        if (isDirty)
-            SaveTodayChallengeRecord();
-        RefreshHeroTrialState();
-        BattleManager.Instance.SendTurnFight((uint)DataMapID, (uint)GetFuncLineID(config));
-    }
-
-    public bool HasChallengedHeroTrialToday(int heroID)
-    {
-        EnsureTodayChallengeRecord();
-        return challengedHeroIDsToday.Contains(heroID);
-    }
-
-    /// <summary>
-    /// 鐣岄潰棣栨鎵撳紑鏃剁殑榛樿钀界偣锛�
-    /// 1. 褰撳墠绾㈢偣姝﹀皢锛�
-    /// 2. 涓婃鎸戞垬杩囦笖褰撳墠浠嶅彲鎸戞垬鐨勬灏嗭紱
-    /// 3. 鎵�鏈夊凡瑙i攣涓斿彲鎸戞垬鍏冲崱涓垬鍔涙渶浣庣殑姝﹀皢銆�
-    /// </summary>
-    public int GetHeroTrialEnterHeroID()
-    {
-        EnsureTodayChallengeRecord();
-
-        if (FuncOpen.Instance.IsFuncOpen(funcId))
-        {
-            if (currentRedHeroID != 0 && IsShowHeroItemRed(currentRedHeroID))
-                return currentRedHeroID;
-        }
-
-        if (lastChallengeHeroIDToday != 0 && HasChallengeableLevel(lastChallengeHeroIDToday))
-            return lastChallengeHeroIDToday;
-
         List<int> heroIDList = GetHeroTrialHeroIDList();
-        return GetLowestEnterHeroID(heroIDList);
-    }
-
-    void MarkHeroTrialChallengedToday(int heroID)
-    {
-        EnsureTodayChallengeRecord();
-        if (challengedHeroIDsToday.Add(heroID))
-            SaveTodayChallengeRecord();
-    }
-
-    void EnsureTodayChallengeRecord()
-    {
-        if (!isChallengeRecordLoaded)
-            LoadTodayChallengeRecord();
-
-        int today = GetTodayID();
-        if (challengeRecordDay == today)
-            return;
-
-        challengeRecordDay = today;
-        challengedHeroIDsToday.Clear();
-        newOpenedHeroIDsToday.Clear();
-        lastChallengeHeroIDToday = 0;
-        normalRedConsumedToday = false;
-        currentRedHeroID = 0;
-        SaveTodayChallengeRecord();
-    }
-
-    void ResetTodayChallengeRecordCache()
-    {
-        challengeRecordDay = 0;
-        challengedHeroIDsToday.Clear();
-        newOpenedHeroIDsToday.Clear();
-        seenUnlockedHeroIDs.Clear();
-        lastChallengeHeroIDToday = 0;
-        normalRedConsumedToday = false;
-        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())
+        for (int i = 0; i < heroIDList.Count; i++)
         {
-            challengeRecordDay = GetTodayID();
-            challengedHeroIDsToday.Clear();
-            newOpenedHeroIDsToday.Clear();
-            lastChallengeHeroIDToday = 0;
-            normalRedConsumedToday = false;
-            currentRedHeroID = 0;
-            SaveTodayChallengeRecord();
-            return;
+            if (HasChallengeableLevel(heroIDList[i]))
+                return true;
         }
 
-        LoadHeroIDSet(challengedHeroIDsTodayKey, challengedHeroIDsToday);
-        lastChallengeHeroIDToday = LocalSave.GetInt(lastChallengeHeroIDTodayKey);
-        normalRedConsumedToday = LocalSave.GetInt(normalRedConsumedTodayKey) != 0;
-        LoadHeroIDList(newOpenedHeroIDsTodayKey, newOpenedHeroIDsToday);
-        currentRedHeroID = 0;
+        return false;
     }
 
-    void SaveTodayChallengeRecord()
+    bool IsActLimitHero(HeroConfig heroConfig)
     {
-        LocalSave.SetInt(challengeRecordDayKey, challengeRecordDay);
-        LocalSave.SetIntArray(challengedHeroIDsTodayKey, ToArray(challengedHeroIDsToday));
-        LocalSave.SetInt(lastChallengeHeroIDTodayKey, lastChallengeHeroIDToday);
-        LocalSave.SetInt(normalRedConsumedTodayKey, normalRedConsumedToday ? 1 : 0);
-        LocalSave.SetIntArray(newOpenedHeroIDsTodayKey, newOpenedHeroIDsToday.ToArray());
-        LocalSave.SetIntArray(seenUnlockedHeroIDsKey, ToArray(seenUnlockedHeroIDs));
+        return heroConfig.IsActLimit > 0;
     }
 
-    bool LoadHeroIDSet(string key, HashSet<int> heroIDSet)
-    {
-        heroIDSet.Clear();
-        int[] heroIDs = LocalSave.GetIntArray(key);
-        if (heroIDs == null)
-            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)
-    {
-        heroIDList.Clear();
-        int[] heroIDs = LocalSave.GetIntArray(key);
-        if (heroIDs == null)
-            return;
-
-        for (int i = 0; i < heroIDs.Length; i++)
-        {
-            if (heroIDs[i] != 0 && !heroIDList.Contains(heroIDs[i]))
-                heroIDList.Add(heroIDs[i]);
-        }
-    }
-
-    int[] ToArray(HashSet<int> heroIDSet)
-    {
-        int[] heroIDs = new int[heroIDSet.Count];
-        heroIDSet.CopyTo(heroIDs);
-        return heroIDs;
-    }
-
-    int GetTodayID()
-    {
-        // 姝﹀皢璇曠偧鐨勬柊寮�鏀捐鍒欐寜寮�鏈嶅ぉ璁$畻锛屽綋澶╄褰曚篃蹇呴』浣跨敤鍚屼竴鍙e緞銆�
-        return TimeUtility.OpenDay;
-    }
-
-    /// <summary>
-    /// 灏濊瘯鑾峰彇鏌愭灏嗙殑閫氬叧杩涘害
-    /// </summary>
+    // 灏濊瘯鑾峰彇鏌愭灏嗙殑閫氬叧杩涘害
     public bool TryGetHeroPassLevelNum(uint heroID, out byte passLevelNum)
     {
         return heroTrialDict.TryGetValue(heroID, out passLevelNum);
     }
 
+    bool IsActLimitHeroLocked(HeroConfig heroConfig)
+    {
+        return heroConfig.OpenCollectionDay <= 0 || TimeUtility.OpenDay + 1 >= heroConfig.OpenCollectionDay;
+    }
     public bool IsHeroTrialHeroUnlocked(int heroID)
     {
+        // 涓嶅叧蹇冮攣瀹氭彁绀烘椂璧拌繖涓噸杞姐��
         return IsHeroTrialHeroUnlocked(heroID, out _);
     }
 
+    //姝﹀皢鍓湰鏄惁瑙i攣
     public bool IsHeroTrialHeroUnlocked(int heroID, out string lockTip)
     {
         lockTip = string.Empty;
-
         HeroConfig heroConfig = HeroConfig.Get(heroID);
         if (heroConfig == null)
             return false;
 
-        if (heroConfig.IsActLimit > 0)
+        if (IsActLimitHero(heroConfig))
         {
-            bool isOpenCollectionDayMet = heroConfig.OpenCollectionDay <= 0 ||
-                TimeUtility.OpenDay + 1 >= heroConfig.OpenCollectionDay;
-            if (!isOpenCollectionDayMet)
+            if (!IsActLimitHeroLocked(heroConfig))
             {
                 lockTip = Language.Get("HeroTrial02", heroConfig.Name);
                 return false;
@@ -756,53 +233,465 @@
         else
         {
             if (HeroTrialConfig.TryGetFirstLevelConfig(heroConfig.HeroID, out var trialConfig))
-            {
-                if (trialConfig.LVLimit > 0)
-                {
-                    if (PlayerDatas.Instance.baseData.LV < trialConfig.LVLimit)
-                    {
-                        lockTip = Language.Get("HeroTrial04", trialConfig.LVLimit);
-                        return false;
-                    }
-                }
-                else if (trialConfig.RealmLimit > 0)
-                {
-                    if (PlayerDatas.Instance.baseData.realmLevel < trialConfig.RealmLimit)
-                    {
-                        var config = RealmConfig.Get(trialConfig.RealmLimit);
-                        lockTip = Language.Get("HeroTrial03", config != null ? config.Name : string.Empty);
-                        return false;
-                    }
-                }
-            }
+                return IsTrialLevelLimitMet(trialConfig, out lockTip);
         }
 
         return true;
     }
 
-    /// <summary>
-    /// 鑾峰彇姝﹀皢璇曠偧姝﹀皢鍒楄〃锛氭寜閰嶇疆琛ㄦ帓搴忥紝涓嶄緷璧栨湇鍔″櫒杩涘害鏁版嵁
-    /// </summary>
-    public List<int> GetHeroTrialHeroIDList()
+    public bool CanChallenge(HeroTrialConfig config, out string tip)
     {
-        List<int> heroIDList = HeroTrialConfig.GetSortedHeroIDList();
-        for (int i = heroIDList.Count - 1; i >= 0; i--)
+        tip = string.Empty;
+        if (config == null)
+            return false;
+
+        // 姝﹀皢鍓湰鏁翠綋鏄惁瑙i攣
+        if (!IsHeroTrialHeroUnlocked(config.HeroID, out string heroUnlockTip))
         {
-            if (!IsHeroTrialHeroVisible(heroIDList[i]))
-                heroIDList.RemoveAt(i);
+            tip = heroUnlockTip;
+            return false;
         }
 
-        return heroIDList;
-    }
+        // 杩欎竴鍏宠嚜宸辩殑绛夌骇/瀹樿亴闄愬埗鏄惁婊¤冻銆�
+        if (!IsTrialLevelLimitMet(config, out tip))
+            return false;
 
-    bool IsHeroTrialHeroVisible(int heroID)
-    {
-        HeroConfig heroConfig = HeroConfig.Get(heroID);
-        if (heroConfig == null)
+        // 璇诲彇鏈嶅姟鍣ㄤ笅鍙戠殑璇ユ灏嗗凡閫氬叧杩涘害锛涙病鏈夎褰曟椂鎸� 0 鍏冲鐞嗐��
+        bool hasProgress = TryGetHeroPassLevelNum((uint)config.HeroID, out byte passLevelNum);
+        // 鍒ゆ柇褰撳墠鍏虫槸涓嶆槸璇ユ灏嗚瘯鐐肩殑绗竴鍏炽��
+        bool isFirstLevel = HeroTrialConfig.TryGetFirstLevelConfig(config.HeroID, out var firstConfig) && firstConfig.CfgID == config.CfgID;
+        // 濡傛灉鏈嶅姟鍣ㄦ病鏈夎姝﹀皢杩涘害锛屽彧鏈夌涓�鍏冲厑璁告寫鎴樸��
+        if (!hasProgress)
+        {
+            passLevelNum = 0;
+            // 绗竴鍏充笖灏氭湭閫氬叧锛屽彲浠ユ寫鎴樸��
+            if (isFirstLevel && passLevelNum < config.LevelNum)
+                return true;
+
+            // 涓嶆槸绗竴鍏冲氨鎻愮ず鍓嶇疆鍏冲崱鏈�氥��
+            tip = Language.Get("HeroTrial05");
+            return false;
+        }
+
+        // 鏈夎繘搴︽椂锛岀涓�鍏冲鏋滆繕娌¢�氾紝涔熷厑璁告寫鎴樸��
+        if (isFirstLevel && passLevelNum < config.LevelNum)
             return true;
 
-        return heroConfig.IsActLimit <= 0 ||
-            heroConfig.OpenCollectionDay <= 0 ||
-            TimeUtility.OpenDay + 1 >= heroConfig.OpenCollectionDay;
+        // 鏅�氬叧鍗″繀椤荤瓑浜庘�滃凡閫氬叧鏁� + 1鈥濓紝涔熷氨鏄彧鑳芥墦涓嬩竴鍏筹紝涓嶈兘璺冲叧銆�
+        if (config.LevelNum != passLevelNum + 1)
+        {
+            tip = Language.Get("HeroTrial05");
+            return false;
+        }
+
+        // 鎵�鏈夋潯浠堕兘婊¤冻锛屽綋鍓嶅叧鍗″彲浠ユ寫鎴樸��
+        return true;
     }
+
+    bool IsTrialLevelLimitMet(HeroTrialConfig config, out string tip)
+    {
+        // tip 鐢ㄦ潵杩斿洖杩欏叧鏈揪鏍囧師鍥犮��
+        tip = string.Empty;
+        // 娌℃湁閰嶇疆鏃惰涓轰笉婊¤冻銆�
+        if (config == null)
+            return false;
+
+        // 杩欏叧閰嶇疆浜嗙瓑绾ч檺鍒讹紝涓旂帺瀹剁瓑绾т笉瓒虫椂涓嶈兘鎸戞垬銆�
+        if (config.LVLimit > 0 && PlayerDatas.Instance.baseData.LV < config.LVLimit)
+        {
+            tip = Language.Get("HeroTrial04", config.LVLimit);
+            return false;
+        }
+
+        // 杩欏叧閰嶇疆浜嗗畼鑱�/澧冪晫闄愬埗锛屼笖鐜╁瀹樿亴涓嶈冻鏃朵笉鑳芥寫鎴樸��
+        if (config.RealmLimit > 0 && PlayerDatas.Instance.baseData.realmLevel < config.RealmLimit)
+        {
+            // 鏌ラ厤缃彧鏄负浜嗗睍绀哄畼鑱屽悕绉帮紱鏌ヤ笉鍒版椂鐢ㄧ┖瀛楃涓插厹搴曘��
+            var realmConfig = RealmConfig.Get(config.RealmLimit);
+            tip = Language.Get("HeroTrial03", realmConfig != null ? realmConfig.Name : string.Empty);
+            return false;
+        }
+
+        // 绛夌骇鍜屽畼鑱岄兘婊¤冻銆�
+        return true;
+    }
+
+    #region 绾㈢偣
+    public Redpoint parentRedpoint = new Redpoint(MainRedDot.MainChallengeRedpoint, MainRedDot.HeroTrialRepoint);
+
+    public int nowShowRedHeroID;//褰撳墠灞曠ず鐨勫壇鏈孩鐐圭殑鐨勬灏咺D
+
+    public void ClearRedpoint()
+    {
+        parentRedpoint.state = RedPointState.None;
+        nowShowRedHeroID = 0;
+    }
+
+    public void UpdateRedpoint()
+    {
+        ClearRedpoint();
+
+        // 鍔熻兘鍏ュ彛鏈紑鏃朵篃缁存姢浠婃棩鐘舵�佸拰鍙彁绀哄揩鐓э紝淇濊瘉鍏ュ彛寮�鍚悗鑳借瘑鍒綋鍓嶇櫥褰曟湡闂寸殑杈炬爣寮�鏀俱��
+        EnsureTodayState();
+        RefreshTodayNewOpenHeroes();
+
+        if (!FuncOpen.Instance.IsFuncOpen(funcId))
+            return;
+
+        if (TryGetNextNewOpenRedHero(out int newHeroID))
+        {
+            ShowRed(newHeroID);
+            return;
+        }
+
+        if (todayNewOpenHeroIDs.Count > 0)
+            return;
+
+        // 娌℃湁鏂板紑鏀鹃槦鍒楁椂锛屼换鎰忎竴娆′粖鏃ユ寫鎴橀兘瑙嗕负鏃у壇鏈孩鐐瑰凡娑堣垂銆�
+        if (consumedRedHeroIDsToday.Count > 0)
+            return;
+
+        if (TryGetOldRedHero(out int oldHeroID))
+            ShowRed(oldHeroID);
+    }
+
+    void RefreshRedpointAndNotify(bool forceNotify = false)
+    {
+        int oldRedHeroID = nowShowRedHeroID;
+        UpdateRedpoint();
+
+        if (forceNotify || oldRedHeroID != nowShowRedHeroID)
+            OnUpdateHeroTrialInfoEvent?.Invoke();
+    }
+
+    void ShowRed(int heroID)
+    {
+        nowShowRedHeroID = heroID;
+        parentRedpoint.state = RedPointState.Simple;
+    }
+
+    bool TryGetLowestCanChallengeConfig(int heroID, out HeroTrialConfig config)
+    {
+        config = null;
+        if (!HeroTrialConfig.TryGetHeroConfigList(heroID, out var configs))
+            return false;
+
+        for (int i = 0; i < configs.Count; i++)
+        {
+            if (!CanChallenge(configs[i], out _))
+                continue;
+
+            if (config == null || configs[i].FightPower < config.FightPower)
+                config = configs[i];
+        }
+
+        return config != null;
+    }
+
+    public bool IsShowHeroItemRed(int heroID)
+    {
+        return heroID > 0 && heroID == nowShowRedHeroID;
+    }
+
+    public bool IsShowHeroTrialLevelRed(HeroTrialConfig config)
+    {
+        if (config == null || config.HeroID != nowShowRedHeroID)
+            return false;
+
+        return TryGetLowestCanChallengeConfig(config.HeroID, out var redConfig) &&
+            redConfig != null &&
+            redConfig.CfgID == config.CfgID;
+    }
+
+    void EnsureTodayState()
+    {
+        int currentTodayKey = GetTodayKey();
+
+        if (!hasLoadedLocalRecord)
+            LoadLocalRecord(currentTodayKey);
+
+        if (todayKey == currentTodayKey)
+            return;
+
+        todayKey = currentTodayKey;
+        hasAvailableSnapshot = false;
+        lastChallengeHeroIDToday = 0;
+        todayNewOpenHeroIDs.Clear();
+        consumedRedHeroIDsToday.Clear();
+        knownAvailableHeroIDs.Clear();
+        SaveLocalRecord();
+    }
+
+    int GetTodayKey()
+    {
+        DateTime now = TimeUtility.ServerNow;
+        return now.Year * 10000 + now.Month * 100 + now.Day;
+    }
+
+    string GetLocalRecordKey()
+    {
+        return StringUtility.Concat("HeroTrialRedpoint_", PlayerDatas.Instance.PlayerId.ToString());
+    }
+
+    void LoadLocalRecord(int currentTodayKey)
+    {
+        hasLoadedLocalRecord = true;
+        todayKey = currentTodayKey;
+        todayNewOpenHeroIDs.Clear();
+        consumedRedHeroIDsToday.Clear();
+
+        string json = LocalSave.GetString(GetLocalRecordKey());
+        if (string.IsNullOrEmpty(json))
+            return;
+
+        JsonData data;
+        try
+        {
+            data = JsonMapper.ToObject(json);
+        }
+        catch
+        {
+            return;
+        }
+
+        if (data == null || !data.IsObject || !data.ContainsKey("day"))
+            return;
+
+        if (!int.TryParse(data["day"].ToString(), out int recordDay) || recordDay != currentTodayKey)
+        {
+            // 杩囨湡璁板綍涓嶈縼绉伙紱鎸変粖澶╃┖璁板綍閲嶆柊璁$畻銆�
+            SaveLocalRecord();
+            return;
+        }
+
+        LoadIntSet(data, "new", todayNewOpenHeroIDs);
+        LoadIntSet(data, "consumed", consumedRedHeroIDsToday);
+    }
+
+    void SaveLocalRecord()
+    {
+        JsonData data = new JsonData();
+        data["day"] = todayKey != 0 ? todayKey : GetTodayKey();
+        data["new"] = ToJsonArray(todayNewOpenHeroIDs);
+        data["consumed"] = ToJsonArray(consumedRedHeroIDsToday);
+        LocalSave.SetString(GetLocalRecordKey(), data.ToJson());
+    }
+
+    void LoadIntSet(JsonData data, string field, HashSet<int> heroIDSet)
+    {
+        if (!data.ContainsKey(field) || data[field] == null || !data[field].IsArray)
+            return;
+
+        JsonData array = data[field];
+        for (int i = 0; i < array.Count; i++)
+        {
+            if (int.TryParse(array[i].ToString(), out int heroID) && heroID > 0)
+                heroIDSet.Add(heroID);
+        }
+    }
+
+    JsonData ToJsonArray(HashSet<int> heroIDSet)
+    {
+        JsonData array = new JsonData();
+        array.SetJsonType(JsonType.Array);
+        foreach (int heroID in heroIDSet)
+        {
+            array.Add(heroID);
+        }
+
+        return array;
+    }
+
+    void RefreshTodayNewOpenHeroes()
+    {
+        bool dirty = false;
+        List<int> heroIDList = GetHeroTrialHeroIDList();
+        HashSet<int> currentAvailableHeroIDs = new HashSet<int>();
+
+        for (int i = 0; i < heroIDList.Count; i++)
+        {
+            int heroID = heroIDList[i];
+            if (!IsHeroAvailableForRed(heroID))
+                continue;
+
+            currentAvailableHeroIDs.Add(heroID);
+
+            // 绛夌骇/瀹樿亴杈炬爣鍙潬褰撳墠鐧诲綍鏈熼棿鐨勫揩鐓у樊寮傦紱闄愬畾姝﹀皢鎸夊綋澶╁紑鏈嶅ぉ鍙嶅璇嗗埆锛屾敮鎸侀噸鐧绘仮澶嶃��
+            bool isNewBySnapshot = hasAvailableSnapshot && !knownAvailableHeroIDs.Contains(heroID);
+            bool isNewByOpenDay = IsActLimitHeroOpenToday(heroID);
+            if ((isNewBySnapshot || isNewByOpenDay) && todayNewOpenHeroIDs.Add(heroID))
+                dirty = true;
+        }
+
+        knownAvailableHeroIDs.Clear();
+        foreach (int heroID in currentAvailableHeroIDs)
+        {
+            knownAvailableHeroIDs.Add(heroID);
+        }
+
+        hasAvailableSnapshot = true;
+
+        if (dirty)
+            SaveLocalRecord();
+    }
+
+    bool IsHeroAvailableForRed(int heroID)
+    {
+        return IsHeroTrialHeroUnlocked(heroID) && HasChallengeableLevel(heroID);
+    }
+
+    bool IsActLimitHeroOpenToday(int heroID)
+    {
+        HeroConfig heroConfig = HeroConfig.Get(heroID);
+        return heroConfig != null &&
+            heroConfig.IsActLimit > 0 &&
+            heroConfig.OpenCollectionDay > 0 &&
+            heroConfig.OpenCollectionDay == TimeUtility.OpenDay + 1;
+    }
+
+    bool TryGetNextNewOpenRedHero(out int heroID)
+    {
+        heroID = 0;
+        List<int> heroIDList = GetHeroTrialHeroIDList();
+
+        for (int i = 0; i < heroIDList.Count; i++)
+        {
+            int candidateHeroID = heroIDList[i];
+            if (!todayNewOpenHeroIDs.Contains(candidateHeroID))
+                continue;
+            if (consumedRedHeroIDsToday.Contains(candidateHeroID))
+                continue;
+            if (!IsHeroAvailableForRed(candidateHeroID))
+                continue;
+
+            heroID = candidateHeroID;
+            return true;
+        }
+
+        return false;
+    }
+
+    bool TryGetOldRedHero(out int heroID)
+    {
+        heroID = 0;
+        long bestFightPower = long.MaxValue;
+        int bestSortIndex = int.MaxValue;
+
+        List<int> heroIDList = GetHeroTrialHeroIDList();
+        for (int i = 0; i < heroIDList.Count; i++)
+        {
+            int candidateHeroID = heroIDList[i];
+            if (!IsHeroAvailableForRed(candidateHeroID))
+                continue;
+            if (!TryGetLowestCanChallengeConfig(candidateHeroID, out var config))
+                continue;
+
+            if (config.FightPower < bestFightPower ||
+                config.FightPower == bestFightPower && i < bestSortIndex)
+            {
+                heroID = candidateHeroID;
+                bestFightPower = config.FightPower;
+                bestSortIndex = i;
+            }
+        }
+
+        return heroID != 0;
+    }
+
+    void ConsumeRedpointOnChallenge(int heroID)
+    {
+        if (heroID <= 0)
+            return;
+
+        EnsureTodayState();
+
+        // 鐐瑰嚮鎸戞垬鍗虫秷璐圭孩鐐癸紱涓嶇瓑寰呮垬鏂楃粨绠楋紝閬垮厤杩斿洖鐣岄潰鍚庢墠鏀瑰彉绾㈢偣鐘舵�併��
+        if (consumedRedHeroIDsToday.Add(heroID))
+            SaveLocalRecord();
+
+        lastChallengeHeroIDToday = heroID;
+        RefreshRedpointAndNotify();
+
+    }
+
+    #endregion
+
+    #region 璺宠浆
+    // 褰撳ぉ鏈�鍚庝竴娆″彂璧锋寫鎴樼殑姝﹀皢鍓湰锛岀敤浜庣晫闈㈤娆℃墦寮�鏃剁殑榛樿璺宠浆銆�
+    int lastChallengeHeroIDToday;
+
+    int GetLowestEnterHeroID(List<int> heroIDList)
+    {
+        // 杩欎釜鏂规硶鐢ㄤ簬鐣岄潰榛樿钀界偣锛屼笉鐩存帴鍐冲畾绾㈢偣锛涙病鏈夌孩鐐规椂涔熶細鎵句竴涓渶閫傚悎杩涘叆鐨勬灏嗐��
+        int bestHeroID = 0;
+        // 榛樿鏈�澶ф垬鍔涳紝鏂逛究鎵炬渶浣庢垬鍔涘叧鍗°��
+        long bestFightPower = long.MaxValue;
+        // 鎴樺姏鐩稿悓鏃讹紝鐢ㄩ厤缃『搴忓厹搴曘��
+        int bestSortIndex = int.MaxValue;
+
+        // 閬嶅巻鎵�鏈夋灏嗭紝鎵惧綋鍓嶅凡瑙i攣涓旀湁鍙寫鎴樺叧鍗$殑鏈�浣宠繘鍏ョ洰鏍囥��
+        for (int i = 0; i < heroIDList.Count; i++)
+        {
+            // 褰撳墠鍊欓�夋灏咺D銆�
+            int heroID = heroIDList[i];
+            // 娌¤В閿佺殑姝﹀皢涓嶈兘浣滀负榛樿杩涘叆鐩爣銆�
+            if (!IsHeroTrialHeroUnlocked(heroID))
+                continue;
+            // 娌℃湁鍙寫鎴樺叧鍗$殑姝﹀皢涔熶笉浣滀负榛樿杩涘叆鐩爣銆�
+            if (!HasChallengeableLevel(heroID))
+                continue;
+
+            // 鎵捐繖涓灏嗗綋鍓嶅彲鎸戞垬鍏冲崱閲屾垬鍔涙渶浣庣殑涓�鍏炽��
+            if (!TryGetLowestCanChallengeConfig(heroID, out var config))
+                continue;
+
+            // 鎴樺姏鏇翠綆锛屽垯鏇挎崲榛樿杩涘叆鐩爣銆�
+            if (config.FightPower < bestFightPower)
+            {
+                bestHeroID = heroID;
+                bestFightPower = config.FightPower;
+                bestSortIndex = i;
+                continue;
+            }
+
+            // 鎴樺姏鐩稿悓锛屽垯閰嶇疆椤哄簭闈犲墠鑰呬紭鍏堛��
+            if (config.FightPower == bestFightPower && i < bestSortIndex)
+            {
+                bestHeroID = heroID;
+                bestSortIndex = i;
+            }
+        }
+
+        // 杩斿洖 0 琛ㄧず娌℃湁鍚堥�傜殑榛樿杩涘叆姝﹀皢銆�
+        return bestHeroID;
+    }
+   /// <summary>
+    /// 鐣岄潰棣栨鎵撳紑鏃剁殑榛樿钀界偣锛�
+    /// 1. 褰撳墠绾㈢偣姝﹀皢锛�
+    /// 2. 涓婃鎸戞垬杩囦笖褰撳墠浠嶅彲鎸戞垬鐨勬灏嗭紱
+    /// 3. 鎵�鏈夊凡瑙i攣涓斿彲鎸戞垬鍏冲崱涓垬鍔涙渶浣庣殑姝﹀皢銆�
+    /// </summary>
+    public int GetHeroTrialEnterHeroID()
+    {
+        UpdateRedpoint();
+
+        // 濡傛灉鍔熻兘宸插紑锛屼紭鍏堣繘鍏ュ綋鍓嶇孩鐐规灏嗐��
+        if (FuncOpen.Instance.IsFuncOpen(funcId) && nowShowRedHeroID != 0)
+        {
+            // 鍐嶆鏍¢獙绾㈢偣姝﹀皢浠嶇劧鏈夋晥锛岄伩鍏嶈繘鍏ヤ竴涓凡缁忎笉璇ヤ寒绾㈢偣鐨勬灏嗐��
+            if (IsHeroAvailableForRed(nowShowRedHeroID))
+                return nowShowRedHeroID;
+        }
+
+        // 娌℃湁绾㈢偣鏃讹紝濡傛灉浠婂ぉ鎸戞垬杩囨煇涓灏嗕笖瀹冧粛鏈夊彲鎸戞垬鍏冲崱锛屽氨榛樿鍥炲埌瀹冦��
+        if (lastChallengeHeroIDToday != 0 && HasChallengeableLevel(lastChallengeHeroIDToday))
+            return lastChallengeHeroIDToday;
+
+        // 鍐嶆病鏈夌殑璇濓紝灏变粠鎵�鏈夊凡瑙i攣涓旀湁鍙寫鎴樺叧鍗$殑姝﹀皢閲岄�夋垬鍔涙渶浣庣殑浣滀负榛樿鍏ュ彛銆�
+        List<int> heroIDList = GetHeroTrialHeroIDList();
+        return GetLowestEnterHeroID(heroIDList);
+    }
+
+    #endregion
+
 }

--
Gitblit v1.8.0