Main/Config/ConfigManager.cs
@@ -90,6 +90,8 @@ typeof(HeroLineupHaloConfig), typeof(HeroQualityLVConfig), typeof(HeroSkinAttrConfig), typeof(HeroTalentConfig), typeof(HeroTrialConfig), typeof(HorseClassConfig), typeof(HorseIDConfig), typeof(HorseSkinConfig), @@ -384,6 +386,10 @@ ClearConfigDictionary<HeroQualityLVConfig>(); // 清空 HeroSkinAttrConfig 字典 ClearConfigDictionary<HeroSkinAttrConfig>(); // 清空 HeroTalentConfig 字典 ClearConfigDictionary<HeroTalentConfig>(); // 清空 HeroTrialConfig 字典 ClearConfigDictionary<HeroTrialConfig>(); // 清空 HorseClassConfig 字典 ClearConfigDictionary<HorseClassConfig>(); // 清空 HorseIDConfig 字典 Main/Config/Configs/HeroTrialConfig.cs
New file @@ -0,0 +1,80 @@ //-------------------------------------------------------- // [Author]: YYL // [ Date ]: Tuesday, June 9, 2026 //-------------------------------------------------------- using System.Collections.Generic; using System; using UnityEngine; using LitJson; public partial class HeroTrialConfig : ConfigBase<int, HeroTrialConfig> { static HeroTrialConfig() { // 访问过静态构造函数 visit = true; } public int CfgID; public int HeroID; public int LevelNum; public int LVLimit; public int RealmLimit; public int[][] PassAwardList; public int[] LineupIDList; public int NPCLV; public float Difficulty; public long FightPower; public int SortOrder; public override int LoadKey(string _key) { int key = GetKey(_key); return key; } public override void LoadConfig(string input) { try { string[] tables = input.Split('\t'); int.TryParse(tables[0],out CfgID); int.TryParse(tables[1],out HeroID); int.TryParse(tables[2],out LevelNum); int.TryParse(tables[3],out LVLimit); int.TryParse(tables[4],out RealmLimit); PassAwardList = JsonMapper.ToObject<int[][]>(tables[5].Replace("(", "[").Replace(")", "]")); if (tables[6].Contains("[")) { LineupIDList = JsonMapper.ToObject<int[]>(tables[6]); } else { string[] LineupIDListStringArray = tables[6].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries); LineupIDList = new int[LineupIDListStringArray.Length]; for (int i=0;i<LineupIDListStringArray.Length;i++) { int.TryParse(LineupIDListStringArray[i],out LineupIDList[i]); } } int.TryParse(tables[7],out NPCLV); float.TryParse(tables[8],out Difficulty); long.TryParse(tables[9],out FightPower); int.TryParse(tables[10],out SortOrder); } catch (Exception exception) { Debug.LogError(exception); } } } Main/Config/Configs/HeroTrialConfig.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 86da543a944b1b94c8cfd3581f709bd8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/Config/PartialConfigs/HeroTrialConfig.cs
New file @@ -0,0 +1,109 @@ using System.Collections.Generic; using System.Linq; public partial class HeroTrialConfig : ConfigBase<int, HeroTrialConfig> { //<武将ID,<关卡编号,配置ID>> static Dictionary<int, Dictionary<int, int>> idDict = new Dictionary<int, Dictionary<int, int>>(); protected override void OnConfigParseCompleted() { if (!idDict.ContainsKey(HeroID)) { idDict[HeroID] = new Dictionary<int, int>(); } idDict[HeroID][LevelNum] = CfgID; } public static bool TryGetHeroLevelDict(int heroID, out Dictionary<int, int> dict) { dict = null; return idDict != null && idDict.TryGetValue(heroID, out dict); } public static bool TryGetConfigID(int heroID, int levelNum, out int cfgID) { cfgID = 0; return idDict != null && idDict.TryGetValue(heroID, out var dict) && dict.TryGetValue(levelNum, out cfgID); } public static bool TryGetHeroTrialConfig(int heroID, int levelNum, out HeroTrialConfig config) { config = null; if (TryGetConfigID(heroID, levelNum, out int cfgID) && HasKey(cfgID)) { config = Get(cfgID); return true; } return false; } public static bool TryGetHeroTrialConfig(int cfgID, out HeroTrialConfig config) { config = null; if (HasKey(cfgID)) { config = Get(cfgID); return true; } return false; } public static List<int> GetSortedHeroIDList() { if (idDict.IsNullOrEmpty()) return new List<int>(); return idDict.Keys .OrderBy(GetHeroSortOrder) .ThenBy(heroID => heroID) .ToList(); } public static bool TryGetHeroConfigList(int heroID, out List<HeroTrialConfig> configs) { configs = null; if (!TryGetHeroLevelDict(heroID, out var dict) || dict.IsNullOrEmpty()) return false; configs = dict.Keys .OrderBy(levelNum => levelNum) .Select(levelNum => TryGetHeroTrialConfig(heroID, levelNum, out var config) ? config : null) .Where(config => config != null) .ToList(); return !configs.IsNullOrEmpty(); } public static bool TryGetFirstLevelConfig(int heroID, out HeroTrialConfig config) { config = null; if (!TryGetHeroLevelDict(heroID, out var dict) || dict.IsNullOrEmpty()) return false; int levelNum = dict.Keys.Min(); return TryGetHeroTrialConfig(heroID, levelNum, out config); } public static bool TryGetMaxLevelNum(int heroID, out int maxLevelNum) { maxLevelNum = 0; if (!TryGetHeroLevelDict(heroID, out var dict) || dict.IsNullOrEmpty()) return false; maxLevelNum = dict.Keys.Max(); return true; } public static bool TryGetNextLevelConfig(int heroID, int passLevelNum, out HeroTrialConfig config) { config = null; int nextLevelNum = passLevelNum + 1; return TryGetHeroTrialConfig(heroID, nextLevelNum, out config); } static int GetHeroSortOrder(int heroID) { return TryGetFirstLevelConfig(heroID, out var config) ? config.SortOrder : int.MaxValue; } } Main/Config/PartialConfigs/HeroTrialConfig.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 8b0e016a7a404a9fa2d61f7327a1fb3f MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/Core/NetworkPackage/DTCFile/ServerPack/HB2_ActionMap/DTCB203_tagSCHeroTrialInfo.cs
New file @@ -0,0 +1,12 @@ using UnityEngine; using System.Collections; // B2 03 武将试炼信息 #tagSCHeroTrialInfo public class DTCB203_tagSCHeroTrialInfo : DtcBasic { public override void Done(GameNetPackBasic vNetPack) { base.Done(vNetPack); HB203_tagSCHeroTrialInfo vNetData = vNetPack as HB203_tagSCHeroTrialInfo; HeroTrialManager.Instance.UpdateHeroTrialInfo(vNetData); } } Main/Core/NetworkPackage/DTCFile/ServerPack/HB2_ActionMap/DTCB203_tagSCHeroTrialInfo.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 0b6aa926e39b4ec4aa2d86f794c9ed8a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/Core/NetworkPackage/DataToCtl/PackageRegedit.cs
@@ -141,6 +141,7 @@ Register(typeof(HB040_tagSCTravelInfo), typeof(DTCB040_tagSCTravelInfo)); Register(typeof(HA3C7_tagMCGubaoInfo), typeof(DTCA3C7_tagMCGubaoInfo)); Register(typeof(HB202_tagSCDingjungeInfo), typeof(DTCB202_tagSCDingjungeInfo)); Register(typeof(HB203_tagSCHeroTrialInfo), typeof(DTCB203_tagSCHeroTrialInfo)); Register(typeof(HB432_tagSCViewNPCAttrRet), typeof(DTCB432_tagSCViewNPCAttrRet)); Register(typeof(HA503_tagSCFamilyTaofaInfo), typeof(DTCA503_tagSCFamilyTaofaInfo)); Register(typeof(HA504_tagSCFamilyTaofaAtkRet), typeof(DTCA504_tagSCFamilyTaofaAtkRet)); @@ -259,4 +260,4 @@ } return null; } } } Main/Core/NetworkPackage/ServerPack/HB2_ActionMap/HB203_tagSCHeroTrialInfo.cs
New file @@ -0,0 +1,29 @@ using UnityEngine; using System.Collections; // B2 03 武将试炼信息 #tagSCHeroTrialInfo public class HB203_tagSCHeroTrialInfo : GameNetPackBasic { public byte Count; public tagSCHeroTrial[] HeroTrialList; public HB203_tagSCHeroTrialInfo () { _cmd = (ushort)0xB203; } public override void ReadFromBytes (byte[] vBytes) { TransBytes (out Count, vBytes, NetDataType.BYTE); HeroTrialList = new tagSCHeroTrial[Count]; for (int i = 0; i < Count; i ++) { HeroTrialList[i] = new tagSCHeroTrial(); TransBytes (out HeroTrialList[i].HeroID, vBytes, NetDataType.DWORD); TransBytes (out HeroTrialList[i].PassLevelNum, vBytes, NetDataType.BYTE); } } public class tagSCHeroTrial { public uint HeroID; // 武将ID public byte PassLevelNum; // 本武将已过关到的关卡编号 } } Main/Core/NetworkPackage/ServerPack/HB2_ActionMap/HB203_tagSCHeroTrialInfo.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: c0adf674df224db49ab40b4853819da8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/Main.cs
@@ -125,6 +125,7 @@ managers.Add(SuperVipManager.Instance); managers.Add(HeroSkinFlashSaleManager.Instance); managers.Add(GuildAtkDefBatManager.Instance); managers.Add(HeroTrialManager.Instance); foreach (var manager in managers) { Main/System/Battle/BattleConst.cs
@@ -13,6 +13,7 @@ typeof(BoneFieldBattleWin), typeof(TianziBillboradBattleWin), typeof(WarlordPavilionBattleWin), typeof(HeroTrialBattleWin), typeof(PreviewBattleWin), typeof(QYBattleWin), typeof(QYBattleWin), @@ -26,6 +27,7 @@ public const string BoneBattleField = "BoneBattleField"; public const string TianziBillboradBattleField = "TianziBillboradBattleField"; public const string WarlordPavilionBattleField = "WarlordPavilionBattleField"; public const string HeroTrialBattleField = "HeroTrialBattleField"; public const string PriviewBattleField = "PriviewBattleField"; //预览战斗 public const string QYBattleField = "QYBattleField"; public const string GuildAtkDefBatField = "GuildAtkDefBatField"; @@ -38,6 +40,7 @@ { BoneBattleField, "BoneFieldBattleWin" }, { TianziBillboradBattleField, "TianziBillboradBattleWin" }, { WarlordPavilionBattleField, "WarlordPavilionBattleWin" }, { HeroTrialBattleField, "HeroTrialBattleWin" }, { PriviewBattleField, "PreviewBattleWin" }, { QYBattleField, "QYBattleWin" }, { GuildAtkDefBatField, "GuildAtkDefBatBattleWin" }, @@ -54,6 +57,7 @@ { PriviewBattleField, 6 }, { QYBattleField, 7 }, { GuildAtkDefBatField, 8 }, { HeroTrialBattleField, 9 }, }; //和 CreateBattleField 里的对应 @@ -65,6 +69,7 @@ {30010, BoneBattleField}, {30020, TianziBillboradBattleField}, {30030, WarlordPavilionBattleField}, {30040, HeroTrialBattleField}, {30000, PriviewBattleField}, {32000, QYBattleField}, {33000, GuildAtkDefBatField}, @@ -294,4 +299,4 @@ public const int DodgeSoundID = 5999999; // 闪避音效ID #endregion } } Main/System/Battle/BattleField/HeroTrialBattleField.cs
New file @@ -0,0 +1,130 @@ using Cysharp.Threading.Tasks; using LitJson; using System.Collections.Generic; using System.Linq; public class HeroTrialBattleField : BattleField { HeroTrialConfig trialConfig; public HeroTrialBattleField(string _guid) : base(_guid) { } public override async UniTask Init(int MapID, int FuncLineID, JsonData _extendData, List<TeamBase> _redTeamList, List<TeamBase> _blueTeamList, byte turnMax) { await base.Init(MapID, FuncLineID, _extendData, _redTeamList, _blueTeamList, turnMax); extendData = _extendData; HeroTrialManager.Instance.TryGetConfigByFuncLineID(FuncLineID, out trialConfig); SetBattleMode(BattleMode.Record); } public override void AutoSetBattleMode() { SetBattleMode(BattleMode.Record); } public override void TurnFightState(int TurnNum, int State, uint FuncLineID, JsonData extendData) { base.TurnFightState(TurnNum, State, FuncLineID, extendData); switch (State) { case 0: break; case 1: break; case 2: break; case 3: break; case 4: break; case 5: break; default: BattleDebug.LogError("recieve a unknown State"); break; } } protected override void OnSettlement(JsonData turnFightStateData) { base.OnSettlement(turnFightStateData); } public override void WhaleFall() { AutoFightModel.Instance.isPause = false; Destroy(); if (UIManager.Instance.IsOpened<HeroTrialBattleWin>()) { UIManager.Instance.CloseWindow<HeroTrialBattleWin>(); UIManager.Instance.OpenWindowAsync<TowerBaseWin>(1).Forget(); } } public override void Run() { if (operationAgent == null) { return; } base.Run(); } public override void DistributeNextPackage() { if (IsBattleFinish) return; BattleManager.Instance.DistributeNextReportPackage(guid); } public override async void ShowWindow(HB424_tagSCTurnFightInit vNetData) { HeroTrialBattleWin battleWin = UIManager.Instance.GetUI<HeroTrialBattleWin>(); if (battleWin == null) { battleWin = await UIManager.Instance.OpenWindowAsync<HeroTrialBattleWin>(); } battleWin.SetBattleField(this); if (UIManager.Instance.IsOpened<TowerBaseWin>()) { UIManager.Instance.CloseWindow<TowerBaseWin>(); } } public NPCLineupConfig GetBossLineupConfig() { if (trialConfig == null && !HeroTrialManager.Instance.TryGetConfigByFuncLineID(FuncLineID, out trialConfig)) return null; int[] lineupIDList = trialConfig.LineupIDList; if (lineupIDList.IsNullOrEmpty()) return null; int lineupID = lineupIDList[0]; if (!NPCLineupConfig.HasKey(lineupID)) return null; return NPCLineupConfig.Get(lineupID); } public override BattleObject FindBoss() { var config = GetBossLineupConfig(); if (config != null) { int bossId = config.BossID; return battleObjMgr.allBattleObjDict.Values.FirstOrDefault(bo => bo.GetNPCID() == bossId); } return null; } } Main/System/Battle/BattleField/HeroTrialBattleField.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 51d9ddba2fa6f604497431ded91e9081 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/Battle/BattleFieldFactory.cs
@@ -32,6 +32,9 @@ case 30030: battleField = new WarlordPavilionBattleField(guid); break; case 30040: battleField = new HeroTrialBattleField(guid); break; case 30000: battleField = new PriviewBattleField(guid); break; @@ -52,4 +55,4 @@ } } } Main/System/ChallengeTab/ArenaTabHandler.cs
@@ -3,7 +3,8 @@ public class ArenaTabHandler : BaseChallengeTabHandler { protected override int GetIndex() => 1; protected override string GetIconKey() => "ChallengeTab1"; protected override string GetNameText() => Language.Get("ChallengeTab1"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => ArenaManager.Instance.funcId; protected override int GetRedpointId() => MainRedDot.ArenaRepoint; @@ -63,4 +64,4 @@ } } } } Main/System/ChallengeTab/BaseChallengeTabHandler.cs
@@ -17,7 +17,8 @@ // 初始化一次 DisplayData,之后只修改变化的字段 displayData = new ChallengeTabButton.DisplayData { Index = GetIndex(), IconKey = GetIconKey(), NameText = GetNameText(), RedpointId = GetRedpointId(), OpenState = GetOpenState(), FuncId = GetFuncId(), @@ -67,9 +68,14 @@ } /// <summary> /// 获取Tab的索引(用于Icon和Name) /// 获取Tab图标Key /// </summary> protected abstract int GetIndex(); protected abstract string GetIconKey(); /// <summary> /// 获取Tab显示名称 /// </summary> protected abstract string GetNameText(); /// <summary> /// 获取开启方式 (0=FuncID, 1=活动) @@ -105,4 +111,4 @@ /// 取消订阅此Tab特有的事件 /// </summary> protected abstract void UnsubscribeFromSpecificEvents(); } } Main/System/ChallengeTab/BoneFieldTabHandler.cs
@@ -3,7 +3,8 @@ public class BoneFieldTabHandler : BaseChallengeTabHandler { protected override int GetIndex() => 2; protected override string GetIconKey() => "ChallengeTab2"; protected override string GetNameText() => Language.Get("ChallengeTab2"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => (int)FuncOpenEnum.BoneBattle; protected override int GetRedpointId() => MainRedDot.BoneFieldRepoint; @@ -75,4 +76,4 @@ Refresh(); } } } } Main/System/ChallengeTab/ChallengeTabButton.cs
@@ -16,7 +16,8 @@ public struct DisplayData { public int Index; public string IconKey; public string NameText; public int RedpointId; public int OpenState;//0 FuncID 1 活动 public int FuncId; @@ -68,9 +69,8 @@ txtLockInfo.SetActive(!isOpen); // 设置图标和名称 string spriteAndLangKey = StringUtility.Concat("ChallengeTab", data.Index.ToString()); imgIcon.SetSprite(spriteAndLangKey); txtName.text = Language.Get(spriteAndLangKey); imgIcon.SetSprite(data.IconKey); txtName.text = data.NameText; // 设置TIPS文本 txtCount.text = data.CountInfo; @@ -79,4 +79,4 @@ // 存储点击回调 this._onClickAction = data.OnClickAction; } } } Main/System/ChallengeTab/HeroTrialTabHandler.cs
New file @@ -0,0 +1,57 @@ using System; using UnityEngine; using Cysharp.Threading.Tasks; public class HeroTrialTabHandler : BaseChallengeTabHandler { protected override string GetIconKey() => "ChallengeTab7"; protected override string GetNameText() => Language.Get("ChallengeTab7"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => (int)FuncOpenEnum.HeroTrial; protected override int GetRedpointId() => MainRedDot.HeroTrialRepoint; protected override string GetCountInfo() { return HeroTrialManager.Instance.HasAnyChallengeableLevel() ? Language.Get("HeroTrial06") : string.Empty; } protected override Action GetOnClickAction() { return HandleHeroTrialNavigation; } private async void HandleHeroTrialNavigation() { if (!FuncOpen.Instance.IsFuncOpen(GetFuncId(), true)) return; UIManager.Instance.CloseWindow<ChallengeTabWin>(); BattleField battleField = BattleManager.Instance.GetBattleFieldByMapID(HeroTrialManager.Instance.DataMapID); if (battleField != null) { HeroTrialBattleWin battleWin; if (!UIManager.Instance.IsOpened<HeroTrialBattleWin>()) { battleWin = await UIManager.Instance.OpenWindowAsync<HeroTrialBattleWin>(); } else { battleWin = UIManager.Instance.GetUI<HeroTrialBattleWin>(); } battleWin.SetBattleField(battleField as HeroTrialBattleField); } else { UIManager.Instance.OpenWindowAsync<TowerBaseWin>(1).Forget(); } } protected override void SubscribeToSpecificEvents() { HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent += Refresh; } protected override void UnsubscribeFromSpecificEvents() { HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent -= Refresh; } } Main/System/ChallengeTab/HeroTrialTabHandler.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: c410f4b7a26f00640b6cf94a2817d3c1 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/ChallengeTab/QunyingTabHandler.cs
@@ -3,7 +3,8 @@ public class QunyingTabHandler : BaseChallengeTabHandler { protected override int GetIndex() => 5; protected override string GetIconKey() => "ChallengeTab5"; protected override string GetNameText() => Language.Get("ChallengeTab5"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => (int)FuncOpenEnum.Qunying; protected override int GetRedpointId() => MainRedDot.Qunying; @@ -64,4 +65,4 @@ Refresh(); } } } } Main/System/ChallengeTab/TianziBillboradTabHandler.cs
@@ -3,7 +3,8 @@ public class TianziBillboradTabHandler : BaseChallengeTabHandler { protected override int GetIndex() => 3; protected override string GetIconKey() => "ChallengeTab3"; protected override string GetNameText() => Language.Get("ChallengeTab3"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => TianziBillboradManager.Instance.funcId; protected override int GetRedpointId() => MainRedDot.TianziBillboradRepoint; @@ -72,4 +73,4 @@ Refresh(); } } } } Main/System/ChallengeTab/WarlordPavilionTabHandler.cs
@@ -3,7 +3,8 @@ public class WarlordPavilionTabHandler : BaseChallengeTabHandler { protected override int GetIndex() => 4; protected override string GetIconKey() => "ChallengeTab4"; protected override string GetNameText() => Language.Get("ChallengeTab4"); protected override int GetOpenState() => 0; // 0=FuncID protected override int GetFuncId() => (int)FuncOpenEnum.WarlordPavilion; protected override int GetRedpointId() => MainRedDot.WarlordPavilionRepoint; @@ -60,4 +61,4 @@ Refresh(); } } } Main/System/HeroTrial.meta
New file @@ -0,0 +1,8 @@ fileFormatVersion: 2 guid: 4fe029ba0b48b2646908738defb90bca folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/CarouselView.cs
New file @@ -0,0 +1,236 @@ using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public interface ICarouselItem { RectTransform RectTransform { get; } void SetSelected(bool selected); } public abstract class CarouselView<TItem, TData> : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler where TItem : MonoBehaviour, ICarouselItem { public event Action OnSelectedIndexChanged; readonly List<TItem> items = new List<TItem>(); List<TData> dataList; float currentCenterOffset; float targetCenterOffset; bool isDragging; int currentSelectedIndex; protected abstract RectTransform Content { get; } protected abstract GameObject ItemPrefab { get; } protected abstract float ItemSpacing { get; } protected abstract float ScaleMax { get; } protected abstract float ScaleMin { get; } protected virtual int PoolSize => 7; protected int CurrentSelectedIndex => currentSelectedIndex; protected IReadOnlyList<TData> DataList => dataList; public void InitPool() { if (items.Count > 0) return; for (int i = 0; i < PoolSize; i++) { GameObject go = Instantiate(ItemPrefab, Content); TItem item = go.GetComponent<TItem>(); if (item == null) continue; items.Add(item); } } public void InitWithIndex(int index) { if (dataList.IsNullOrEmpty()) return; index = Mathf.Clamp(index, 0, dataList.Count - 1); currentSelectedIndex = index; currentCenterOffset = index; targetCenterOffset = index; UpdateItems(); } public void SelectIndex(int index) { if (dataList.IsNullOrEmpty()) return; index = GetRealIndex(index); int oldIndex = currentSelectedIndex; currentSelectedIndex = index; targetCenterOffset = index; currentCenterOffset = index; UpdateItems(); if (oldIndex != currentSelectedIndex) OnSelectedIndexChanged?.Invoke(); } public void SelectPrevious() { if (dataList.IsNullOrEmpty()) return; int newIndex = (currentSelectedIndex - 1 + dataList.Count) % dataList.Count; SelectIndex(newIndex); } public void SelectNext() { if (dataList.IsNullOrEmpty()) return; int newIndex = (currentSelectedIndex + 1) % dataList.Count; SelectIndex(newIndex); } public void RefreshAllItems() { if (!dataList.IsNullOrEmpty()) UpdateItems(); } protected void SetDataList(List<TData> newDataList, int selectedIndex) { dataList = newDataList; if (dataList.IsNullOrEmpty()) { HideAllItems(); return; } InitWithIndex(selectedIndex); } protected bool TryGetCurrentData(out TData data) { data = default(TData); if (dataList.IsNullOrEmpty() || currentSelectedIndex < 0) return false; int realIndex = GetRealIndex(currentSelectedIndex); data = dataList[realIndex]; return true; } protected void HideAllItems() { for (int i = 0; i < items.Count; i++) { if (items[i] != null) items[i].gameObject.SetActive(false); } } protected virtual void OnDestroy() { items.Clear(); } protected virtual void Update() { if (!isDragging && Mathf.Abs(currentCenterOffset - targetCenterOffset) > 0.001f) { currentCenterOffset = Mathf.Lerp(currentCenterOffset, targetCenterOffset, Time.deltaTime * 10f); if (Mathf.Abs(currentCenterOffset - targetCenterOffset) <= 0.001f) currentCenterOffset = targetCenterOffset; UpdateItems(); } } void UpdateItems() { if (dataList.IsNullOrEmpty()) return; int poolSize = items.Count; int startOffset = Mathf.FloorToInt(currentCenterOffset) - (poolSize / 2); int selectedLogicIndex = Mathf.RoundToInt(targetCenterOffset); for (int i = 0; i < poolSize; i++) { int logicIndex = startOffset + i; int realIndex = GetRealIndex(logicIndex); float distanceToCenter = logicIndex - currentCenterOffset; float posX = distanceToCenter * ItemSpacing; TItem itemUI = items[i]; if (itemUI == null || itemUI.RectTransform == null) continue; itemUI.gameObject.SetActive(true); itemUI.RectTransform.anchoredPosition = new Vector2(posX, 0); float absDist = Mathf.Abs(distanceToCenter); float scaleProgress = Mathf.Clamp01(1f - absDist); float currentScale = Mathf.Lerp(ScaleMin, ScaleMax, scaleProgress); itemUI.RectTransform.localScale = Vector3.one * currentScale; bool isSelected = logicIndex == selectedLogicIndex; RefreshItem(itemUI, dataList[realIndex], realIndex, isSelected); itemUI.SetSelected(isSelected); } } int GetRealIndex(int logicIndex) { return (logicIndex % dataList.Count + dataList.Count) % dataList.Count; } protected abstract void RefreshItem(TItem item, TData data, int realIndex, bool isSelected); public void OnBeginDrag(PointerEventData eventData) { isDragging = true; } public void OnDrag(PointerEventData eventData) { if (dataList.IsNullOrEmpty()) return; float dragDeltaX = eventData.delta.x; currentCenterOffset -= dragDeltaX / ItemSpacing; UpdateItems(); } public void OnEndDrag(PointerEventData eventData) { if (dataList.IsNullOrEmpty()) return; isDragging = false; float speedX = eventData.delta.x; if (speedX > 5f) { targetCenterOffset = Mathf.Floor(currentCenterOffset); } else if (speedX < -5f) { targetCenterOffset = Mathf.Ceil(currentCenterOffset); } else { targetCenterOffset = Mathf.Round(currentCenterOffset); } int oldIndex = currentSelectedIndex; currentSelectedIndex = GetRealIndex(Mathf.RoundToInt(targetCenterOffset)); UpdateItems(); if (oldIndex != currentSelectedIndex) OnSelectedIndexChanged?.Invoke(); } } Main/System/HeroTrial/CarouselView.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 552093c27b834d40ae2245fc840daa24 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialBattleWin.cs
New file @@ -0,0 +1,292 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Cysharp.Threading.Tasks; public class HeroTrialBattleWin : BaseBattleWin { [SerializeField] Transform transButtons; [SerializeField] BossLifeBar bossLifeBar; [SerializeField] SkillWordCell[] skillWordCells; [SerializeField] BossHeadCell bossHeadCell; [SerializeField] Text txtBossName; [SerializeField] List<BattleBuffCell> buffCells; [SerializeField] HeroCountryComponent myCountry; [SerializeField] HeroCountryComponent enemyCountry; [SerializeField] ButtonEx buffInfoButton; private BattleObject bossBattleObject = null; protected override void OnPreOpen() { base.OnPreOpen(); MainWin.TabChangeEvent += OnTabChangeEvent; UIManager.Instance.OnOpenWindow += OnOpenWindow; bool isOpenBattleChangeTab = IsOpenBattleChangeTab(); transButtons.localPosition = new Vector3(0, isOpenBattleChangeTab ? 130 : 0, 0); if (isOpenBattleChangeTab) { UIManager.Instance.GetUI<MainWin>()?.CloseSubUI(); } else { UIManager.Instance.CloseWindow<MainWin>(); } isClickSkip = false; } protected override void OnPreClose() { base.OnPreClose(); MainWin.TabChangeEvent -= OnTabChangeEvent; UIManager.Instance.OnOpenWindow -= OnOpenWindow; bool isOpenBattleChangeTab = IsOpenBattleChangeTab(); if (isOpenBattleChangeTab) { UIManager.Instance.GetUI<MainWin>()?.RestoreSubUI(); } else { UIManager.Instance.OpenWindowAsync<MainWin>().Forget(); } if (bossBattleObject != null) { var buffMgr = bossBattleObject.GetBuffMgr(); if (buffMgr != null) { buffMgr.onBuffChanged -= OnBuffChanged; } bossBattleObject = null; } if (isClickSkip) { isClickSkip = false; TryPass(); } } void OnOpenWindow(UIBase win) { if (win is HeroTrialVictoryWin || win is HeroTrialFailWin) { isClickSkip = false; } } bool isClickSkip = false; protected override void OnClickPass() { if (!IsPass()) return; isClickSkip = true; clickTime = Time.time; // 记录点击时间 battleField.ForceFinish(); } float stayTime = 2f; float clickTime = 0f; void LateUpdate() { if (isClickSkip && Time.time - clickTime >= stayTime) { isClickSkip = false; TryPass(); } } private void TryPass() { if (UIManager.Instance.IsOpened<HeroTrialVictoryWin>() || UIManager.Instance.IsOpened<HeroTrialFailWin>()) return; CloseWindow(); Debug.LogError($"OnBattleEnd 异常关闭"); BattleSettlementManager.Instance.WinShowOver(BattleConst.HeroTrialBattleField); } private void OnTabChangeEvent() { UIManager.Instance.CloseWindow<HeroTrialBattleWin>(); } protected override void OnCreateBattleField(string guid, BattleField field) { if (field is HeroTrialBattleField) { SetBattleField(field); } } protected override void RefreshSpecific() { HeroTrialBattleField heroTrialField = battleField as HeroTrialBattleField; if (heroTrialField == null) return; NPCLineupConfig lineupConfig = heroTrialField.GetBossLineupConfig(); if (bossBattleObject != null) { var buffMgr = bossBattleObject.GetBuffMgr(); if (buffMgr != null) { buffMgr.onBuffChanged -= OnBuffChanged; } bossBattleObject = null; } bossBattleObject = heroTrialField.FindBoss(); DisplaySkillWordsList(lineupConfig); if (null != bossBattleObject && bossBattleObject is HeroBattleObject heroBattleObject) { TeamHero teamHero = heroBattleObject.teamHero; bossHeadCell.SetTeamHero(teamHero); txtBossName.text = teamHero.name; NPCConfig npcConfig = NPCConfig.Get(teamHero.NPCID); bossLifeBar.SetBaseInfo(Mathf.Max(1, npcConfig.LifeBarCount), (ulong)teamHero.curHp, (ulong)teamHero.maxHp); var buffMgr = bossBattleObject.GetBuffMgr(); if (buffMgr != null) { buffMgr.onBuffChanged -= OnBuffChanged; buffMgr.onBuffChanged += OnBuffChanged; } } else { bossHeadCell.SetTeamHero(null); txtBossName.text = string.Empty; bossLifeBar.SetBaseInfo(2, 2, 2); Debug.LogError("找不到boss"); } OnRoundChange(battleField.round, battleField.turnMax); // 确保回合显示被调用 OnBuffChanged(); // 获取我方(红方)队伍数据 List<BattleObject> myTeam = battleField.battleObjMgr.GetBattleObjList(BattleCamp.Red); // 获取敌方(蓝方)队伍数据 List<BattleObject> enemyTeam = battleField.battleObjMgr.GetBattleObjList(BattleCamp.Blue); myCountry.RefreshOnTeamCountry(GetTeamHeroList(myTeam), true); enemyCountry.RefreshOnTeamCountry(GetTeamHeroList(enemyTeam), true); } private void OnBuffChanged() { var buffList = new List<HB428_tagSCBuffRefresh>(); if (null != bossBattleObject) { var buffMgr = bossBattleObject.GetBuffMgr(); buffList = buffMgr != null ? buffMgr.GetBuffIconList() : new List<HB428_tagSCBuffRefresh>(); } RefreshBuff(buffList); } private void RefreshHP() { if (null != bossBattleObject && bossBattleObject is HeroBattleObject heroBattleObject) { TeamHero teamHero = heroBattleObject.teamHero; bossLifeBar.Show((ulong)teamHero.curHp, (ulong)teamHero.maxHp); } } protected override void OnDamageTaken(BattleDmgInfo info) { // Debug.LogError("OnDamageTaken 被调用 调用者是 " + info.battleHurtParam.caster.casterObj?.teamHero.name + " 对象 " + info.battleHurtParam.hurter.hurtObj?.teamHero.name); base.OnDamageTaken(info); if (battleField == null || info.battleFieldGuid != battleField.guid) return; if (null == bossBattleObject) return; // ★★★ 完全使用 StoryBossBattleWin 的逻辑 ★★★ if (info.battleHurtParam.hurter.hurtObj != null && bossBattleObject.ObjID == info.battleHurtParam.hurter.hurtObj.ObjID) { bossLifeBar.Show((ulong)info.battleHurtParam.hurter.toHp, (ulong)bossBattleObject.GetMaxHp()); } else if (info.battleHurtParam.caster.casterObj != null && bossBattleObject.ObjID == info.battleHurtParam.caster.casterObj.ObjID) { bossLifeBar.Show((ulong)info.battleHurtParam.caster.toHp, (ulong)bossBattleObject.GetMaxHp()); } } protected override void OnClose() { base.OnClose(); } public void DisplaySkillWordsList(NPCLineupConfig lineUPConfig) { if (skillWordCells.IsNullOrEmpty()) return; if (null == lineUPConfig) return; for (int i = 0; i < skillWordCells.Length; i++) { if (i < lineUPConfig.SkillIDExList.Length) { skillWordCells[i].SetActive(true); int skillID = lineUPConfig.SkillIDExList[i]; skillWordCells[i].Init(skillID, () => { SmallTipWin.showText = Language.Get("SmallTipFomat", SkillConfig.Get(skillID)?.SkillName, SkillConfig.Get(skillID)?.Description); SmallTipWin.worldPos = CameraManager.uiCamera.ScreenToWorldPoint(Input.mousePosition); SmallTipWin.isDownShow = true; UIManager.Instance.OpenWindowAsync<SmallTipWin>().Forget(); }).Forget(); } else { skillWordCells[i].SetActive(false); } } } public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas) { RefreshBuffCells(buffCells, datas); buffInfoButton.SetListener(() => { if (bossBattleObject == null || datas.IsNullOrEmpty()) return; EventBroadcast.Instance.Broadcast(EventName.BATTLE_CLICK_BUFF, new BattleClickBuffData() { isMySide = false, heroID = (bossBattleObject as HeroBattleObject)?.teamHero?.heroId ?? 0, skinID = (bossBattleObject as HeroBattleObject)?.teamHero?.SkinID ?? 0, datas = datas, }); }); } bool IsOpenBattleChangeTab() { return FuncOpen.Instance.IsFuncOpen(ArenaManager.Instance.BattleChangeTabFuncId); } List<TeamHero> GetTeamHeroList(List<BattleObject> teams) { List<TeamHero> teamHeroes = new List<TeamHero>(); if (teams.IsNullOrEmpty()) return teamHeroes; foreach (var item in teams) { if (item is HeroBattleObject heroBattleObject) { teamHeroes.Add(heroBattleObject.teamHero); } } return teamHeroes; } } Main/System/HeroTrial/HeroTrialBattleWin.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 25165ebda169aa7428e88278f78606b8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialCarouselView.cs
New file @@ -0,0 +1,104 @@ using System.Collections.Generic; using UnityEngine; public class HeroTrialCarouselView : CarouselView<HeroTrialHeroItem, int> { [Header("UI References")] public RectTransform content; public GameObject heroTrialItemPrefab; [Header("Carousel Settings")] public float itemSpacing = 150f; public float scaleMax = 1.3f; public float scaleMin = 0.8f; bool isEventBound; protected override RectTransform Content => content; protected override GameObject ItemPrefab => heroTrialItemPrefab; protected override float ItemSpacing => itemSpacing; protected override float ScaleMax => scaleMax; protected override float ScaleMin => scaleMin; public uint CurrentHeroID { get { if (TryGetCurrentData(out int heroID)) return (uint)heroID; return 0; } } public byte CurrentPassLevelNum { get { uint heroID = CurrentHeroID; if (heroID != 0 && HeroTrialManager.Instance.TryGetHeroPassLevelNum(heroID, out byte passLevelNum)) return passLevelNum; return 0; } } protected override void OnDestroy() { UnbindEvents(); base.OnDestroy(); } public void BindEvents() { if (isEventBound) return; isEventBound = true; if (HeroTrialManager.Instance != null) HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent += OnHeroTrialDataUpdate; } public void UnbindEvents() { if (!isEventBound) return; isEventBound = false; if (HeroTrialManager.Instance != null) HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent -= OnHeroTrialDataUpdate; } void OnHeroTrialDataUpdate() { RefreshData(); } public void RefreshData() { uint prevHeroID = CurrentHeroID; List<int> heroIDs = HeroTrialManager.Instance.GetHeroTrialHeroIDList(); int newIndex = FindHeroIndex(heroIDs, prevHeroID); SetDataList(heroIDs, newIndex >= 0 ? newIndex : 0); } int FindHeroIndex(List<int> heroIDs, uint heroID) { if (heroID == 0 || heroIDs.IsNullOrEmpty()) return -1; for (int i = 0; i < heroIDs.Count; i++) { if ((uint)heroIDs[i] == heroID) return i; } return -1; } protected override void RefreshItem(HeroTrialHeroItem item, int heroID, int realIndex, bool isSelected) { HeroTrialManager.Instance.TryGetHeroPassLevelNum((uint)heroID, out byte passLevelNum); item.UpdateData((uint)heroID, passLevelNum, realIndex); item.onItemClick = SelectIndex; } } Main/System/HeroTrial/HeroTrialCarouselView.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 97cf454b383948fcb6b584596e6ac35d MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialCell.cs
New file @@ -0,0 +1,114 @@ using Cysharp.Threading.Tasks; using UnityEngine; public class HeroTrialCell : CellView { [SerializeField] ImageEx heroHeadImage; [SerializeField] TextEx fightPowerText; [SerializeField] TextEx heroNameText; [SerializeField] ItemCell[] itemCells; [SerializeField] TextEx lockText; [SerializeField] ImageEx passMark; [SerializeField] ButtonEx challengeBtn; [SerializeField] ImageEx challengeImg; [SerializeField] TextEx challengeText1; [SerializeField] TextEx challengeText2; [SerializeField] ImageEx challengeImage; [SerializeField] ImageEx redImage; HeroTrialConfig currentConfig; public void UpdateData(HeroTrialConfig config, byte passLevelNum, bool heroUnlocked, string heroUnlockTip) { if (config == null) return; currentConfig = config; RefreshHeroInfo(config.HeroID); RefreshAwards(config.PassAwardList); fightPowerText.text = UIHelper.ReplaceLargeArtNum(config.FightPower); bool isPassed = heroUnlocked && config.LevelNum <= passLevelNum; string challengeTip = string.Empty; bool canChallenge = heroUnlocked && !isPassed && HeroTrialManager.Instance.CanChallenge(config, out challengeTip); bool showRed = canChallenge && HeroTrialManager.Instance.IsShowHeroTrialLevelRed(config); string lockTip = GetLockTip(config, isPassed, canChallenge, heroUnlocked, heroUnlockTip, challengeTip); RefreshState(isPassed, canChallenge, lockTip, showRed); challengeBtn.SetListener(OnClickChallenge); } string GetLockTip(HeroTrialConfig config, bool isPassed, bool canChallenge, bool heroUnlocked, string heroUnlockTip, string challengeTip) { if (isPassed || canChallenge) return string.Empty; bool isFirstLevel = HeroTrialConfig.TryGetFirstLevelConfig(config.HeroID, out var firstConfig) && firstConfig.CfgID == config.CfgID; if (!heroUnlocked && isFirstLevel && !string.IsNullOrEmpty(heroUnlockTip)) return heroUnlockTip; if (!string.IsNullOrEmpty(challengeTip)) return challengeTip; return Language.Get("HeroTrial05"); } void RefreshHeroInfo(int heroID) { HeroConfig heroConfig = HeroConfig.Get(heroID); if (heroConfig == null) return; heroNameText.text = heroConfig.Name; if (!heroConfig.SkinIDList.IsNullOrEmpty()) { int skinID = heroConfig.SkinIDList[0]; var skinConfig = HeroSkinConfig.Get(skinID); if (skinConfig != null) { UILoader.LoadSprite("HeroHead", skinConfig.SquareIcon, heroHeadImage, "herohead_default").Forget(); } } } void RefreshAwards(int[][] awards) { if (itemCells.IsNullOrEmpty()) return; for (int i = 0; i < itemCells.Length; i++) { bool hasAward = awards != null && i < awards.Length && awards[i] != null && awards[i].Length >= 2; itemCells[i].SetActive(hasAward); if (hasAward) { int itemID = awards[i][0]; itemCells[i].Init(new ItemCellModel(awards[i][0], true, awards[i][1])); itemCells[i].SetClickListener(() => ItemTipUtility.Show(itemID)); } } } void RefreshState(bool isPassed, bool canChallenge, string lockTip, bool showRed) { bool isLocked = !string.IsNullOrEmpty(lockTip); lockText.SetActive(isLocked); lockText.text = lockTip; passMark.SetActive(isPassed); challengeBtn.SetActive(!isPassed); challengeBtn.interactable = canChallenge; challengeImg.gray = !canChallenge; challengeText1.SetActive(!canChallenge); challengeText2.SetActive(canChallenge); challengeImage.SetActive(canChallenge); redImage.SetActive(showRed); } void OnClickChallenge() { HeroTrialManager.Instance.SendChallenge(currentConfig); } } Main/System/HeroTrial/HeroTrialCell.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: b102d9604f5b5f040873d38526654381 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialFailWin.cs
New file @@ -0,0 +1,54 @@ using UnityEngine; using UnityEngine.UI; using Cysharp.Threading.Tasks; //战场结算界面,存在多个的情况 public class HeroTrialFailWin : UIBase { [SerializeField] TextEx txtFuncName; [SerializeField] Button tipHeroCultivateBtn; [SerializeField] Button tipEquipBtn; [SerializeField] Button tipHeroPosBtn; [SerializeField] ButtonEx detailBtn; string battleName = BattleConst.HeroTrialBattleField; protected override void InitComponent() { tipHeroCultivateBtn.AddListener(() => { CloseWindow(); }); tipEquipBtn.AddListener(() => { CloseWindow(); }); tipHeroPosBtn.AddListener(() => { CloseWindow(); UIManager.Instance.OpenWindowAsync<HeroPosWin>().Forget(); }); detailBtn.SetListener(() => { BattleSettlementManager.Instance.OpenBattleDetailWin(battleName); }); } protected override void OnPreOpen() { int funcId = (int)FuncOpenEnum.HeroTrial; txtFuncName.text = FuncOpenLVConfig.HasKey(funcId) ? FuncOpenLVConfig.Get(funcId).Name : string.Empty; if (!FirstChargeManager.Instance.GetLocalFail()) { FirstChargeManager.Instance.SetLocalFail(); } FirstChargeManager.Instance.TryPopWin(); } protected override void OnPreClose() { BattleSettlementManager.Instance.WinShowOver(battleName); } } Main/System/HeroTrial/HeroTrialFailWin.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: db691e748d974d246aecac137f9b0035 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialHeroItem.cs
New file @@ -0,0 +1,85 @@ using System; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UI; public class HeroTrialHeroItem : MonoBehaviour, ICarouselItem { [SerializeField] Image bgImage; [SerializeField] Image isActImage; [SerializeField] Image qualityBgImage; [SerializeField] Image heroHeadImage; [SerializeField] Transform lockRect; [SerializeField] TextEx lockTipText; [SerializeField] TextEx goText; [SerializeField] ButtonEx clickButton; [SerializeField] Image redImage; public RectTransform rectTransform; public Action<int> onItemClick; public int dataIndex; public RectTransform RectTransform { get { if (rectTransform == null) rectTransform = GetComponent<RectTransform>(); return rectTransform; } } void Awake() { rectTransform = GetComponent<RectTransform>(); } public void UpdateData(uint heroID, byte passLevelNum, int index) { dataIndex = index; HeroConfig heroConfig = HeroConfig.Get((int)heroID); if (heroConfig == null) return; isActImage.SetActive(heroConfig.IsActLimit > 0); qualityBgImage.SetSprite("heroheadBG" + heroConfig.Quality); if (!heroConfig.SkinIDList.IsNullOrEmpty()) { int skinID = heroConfig.SkinIDList[0]; var skinConfig = HeroSkinConfig.Get(skinID); if (skinConfig != null) UILoader.LoadSprite("HeroHead", skinConfig.SquareIcon, heroHeadImage, "herohead_default").Forget(); } UpdateLockState(heroConfig); RefreshRed(heroConfig.HeroID); clickButton.SetListener(() => onItemClick?.Invoke(dataIndex)); } void RefreshRed(int heroID) { redImage.SetActive(HeroTrialManager.Instance.IsShowHeroItemRed(heroID)); } /// <summary> /// 更新锁定状态 /// </summary> void UpdateLockState(HeroConfig heroConfig) { bool isLocked = !HeroTrialManager.Instance.IsHeroTrialHeroUnlocked(heroConfig.HeroID, out string tip); lockRect.SetActive(isLocked); goText.SetActive(!isLocked); lockTipText.text = tip; } public void SetSelected(bool selected) { string bgKey = selected ? "HeroTrialHeroBgSelect" : "HeroTrialHeroBgUnSelect"; bgImage.SetSprite(bgKey); } } Main/System/HeroTrial/HeroTrialHeroItem.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 3bb421ee0c4449d1a1f06efbc03bc29a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialManager.cs
New file @@ -0,0 +1,811 @@ using System; using System.Collections.Generic; public class HeroTrialManager : GameSystemManager<HeroTrialManager> { public readonly int funcId = (int)FuncOpenEnum.HeroTrial; // 68 武将试炼功能ID public readonly int DataMapID = 30040; /// <summary>武将试炼进度字典 <HeroID, PassLevelNum></summary> public Dictionary<uint, byte> heroTrialDict = new Dictionary<uint, byte>(); int challengeRecordDay; // 当天发起过挑战的武将不再显示红点,胜负无关。 HashSet<int> challengedHeroIDsToday = new HashSet<int>(); // 当天新开放的可提示武将,优先于普通候选排队顺延。 List<int> pendingNewRedHeroIDsToday = new List<int>(); int currentRedHeroID; bool isChallengeRecordLoaded; 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()); } } /// <summary>数据更新事件,UI订阅刷新</summary> public event Action OnUpdateHeroTrialInfoEvent; /// <summary>主挑战红点</summary> public Redpoint parentRedpoint = new Redpoint(MainRedDot.MainChallengeRedpoint, MainRedDot.HeroTrialRepoint); // 当天最后一次发起挑战的武将副本,用于界面首次打开时的默认跳转。 int lastChallengeHeroIDToday; // 普通红点当天只提示一次;当天新开放武将副本仍可独立排队提示。 bool normalRedConsumedToday; public override void Init() { DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin += OnBeforePlayerDataInitializeEventOnRelogin; FuncOpen.Instance.OnFuncStateChangeEvent += OnFuncStateChangeEvent; PlayerDatas.Instance.playerDataRefreshEvent += OnPlayerDataRefreshEvent; TimeUtility.OnServerOpenDayRefresh += OnServerOpenDayRefresh; TimeUtility.OnServerTimeRefresh += OnServerTimeRefresh; } public override void Release() { DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin -= OnBeforePlayerDataInitializeEventOnRelogin; FuncOpen.Instance.OnFuncStateChangeEvent -= OnFuncStateChangeEvent; PlayerDatas.Instance.playerDataRefreshEvent -= OnPlayerDataRefreshEvent; TimeUtility.OnServerOpenDayRefresh -= OnServerOpenDayRefresh; TimeUtility.OnServerTimeRefresh -= OnServerTimeRefresh; } void OnBeforePlayerDataInitializeEventOnRelogin() { heroTrialDict.Clear(); ResetTodayChallengeRecordCache(); parentRedpoint.state = RedPointState.None; } void OnFuncStateChangeEvent(int obj) { if (obj != funcId) return; RefreshHeroTrialState(); } void OnPlayerDataRefreshEvent(PlayerDataType type) { if (type != PlayerDataType.LV && type != PlayerDataType.RealmLevel) return; RefreshHeroTrialState(); } void OnServerOpenDayRefresh() { RefreshHeroTrialState(); } void OnServerTimeRefresh() { int oldDay = challengeRecordDay; EnsureTodayChallengeRecord(); if (oldDay != challengeRecordDay) RefreshHeroTrialState(); } /// <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(); 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. 今天没有新开放提示链路且普通红点未被挑战消费时,显示所有已解锁且有可挑战关卡的武将里,当前可挑战关卡战力最低的那个。 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)) { isDirty = true; } else if (HasChallengedTodayOpenHeroTrialToday() && pendingNewRedHeroIDsToday.Count == 0 && (currentRedHeroID == 0 || !IsHeroTrialHeroOpenToday(currentRedHeroID))) { if (currentRedHeroID != 0) { currentRedHeroID = 0; isDirty = true; } } else if (!normalRedConsumedToday && (currentRedHeroID == 0 || !IsHeroTrialHeroOpenToday(currentRedHeroID))) { int bestHeroID = GetLowestCanChallengeHeroID(heroIDList); if (currentRedHeroID != bestHeroID) { currentRedHeroID = bestHeroID; isDirty = true; } } if (isDirty) SaveTodayChallengeRecord(); } List<int> BuildCandidateHeroIDs(List<int> sortedHeroIDList) { List<int> candidateHeroIDs = new List<int>(); for (int i = 0; i < sortedHeroIDList.Count; i++) { int heroID = sortedHeroIDList[i]; if (!CanShowHeroItemRed(heroID)) continue; candidateHeroIDs.Add(heroID); } return candidateHeroIDs; } List<int> BuildTodayOpenHeroIDs(List<int> candidateHeroIDs) { 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; } bool HasChallengedTodayOpenHeroTrialToday() { foreach (int heroID in challengedHeroIDsToday) { if (IsHeroTrialHeroOpenToday(heroID)) return true; } return false; } bool IsHeroTrialHeroOpenToday(int heroID) { HeroConfig heroConfig = HeroConfig.Get(heroID); return heroConfig != null && heroConfig.OpenCollectionDay > 0 && heroConfig.OpenCollectionDay == TimeUtility.OpenDay + 1; } void RemoveHeroIDs(List<int> heroIDs, List<int> removeHeroIDs) { if (removeHeroIDs.IsNullOrEmpty()) return; for (int i = heroIDs.Count - 1; i >= 0; 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; } 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; } 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; } 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> /// 更新红点状态 /// </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) return 0; return GetFuncLineID(config.HeroID, config.LevelNum); } public int GetFuncLineID(int heroID, int levelNum) { return heroID * 1000 + levelNum; } public void AnalysisFuncLineID(int funcLineID, out int heroID, out int levelNum) { heroID = funcLineID / 1000; levelNum = funcLineID % 1000; } public bool TryGetConfigByFuncLineID(int funcLineID, out HeroTrialConfig config) { AnalysisFuncLineID(funcLineID, out int heroID, out int levelNum); return HeroTrialConfig.TryGetHeroTrialConfig(heroID, levelNum, out config); } public bool CanChallenge(HeroTrialConfig config, out string tip) { 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) return true; if (config.LevelNum != passLevelNum + 1) { tip = Language.Get("HeroTrial05"); return false; } return true; } 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)) return false; for (int i = 0; i < configs.Count; i++) { if (CanChallenge(configs[i], out _)) return true; } return false; } public void SendChallenge(HeroTrialConfig config) { 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 = pendingNewRedHeroIDsToday.Remove(config.HeroID); if (currentRedHeroID == config.HeroID) { if (!IsHeroTrialHeroOpenToday(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. 所有已解锁且可挑战关卡中战力最低的武将。 /// </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(); lastChallengeHeroIDToday = 0; normalRedConsumedToday = false; pendingNewRedHeroIDsToday.Clear(); currentRedHeroID = 0; SaveTodayChallengeRecord(); } void ResetTodayChallengeRecordCache() { challengeRecordDay = 0; challengedHeroIDsToday.Clear(); lastChallengeHeroIDToday = 0; normalRedConsumedToday = false; pendingNewRedHeroIDsToday.Clear(); currentRedHeroID = 0; isChallengeRecordLoaded = false; } void LoadTodayChallengeRecord() { isChallengeRecordLoaded = true; challengeRecordDay = LocalSave.GetInt(challengeRecordDayKey); if (challengeRecordDay != GetTodayID()) { challengeRecordDay = GetTodayID(); challengedHeroIDsToday.Clear(); lastChallengeHeroIDToday = 0; normalRedConsumedToday = false; pendingNewRedHeroIDsToday.Clear(); currentRedHeroID = 0; SaveTodayChallengeRecord(); return; } LoadHeroIDSet(challengedHeroIDsTodayKey, challengedHeroIDsToday); lastChallengeHeroIDToday = LocalSave.GetInt(lastChallengeHeroIDTodayKey); normalRedConsumedToday = LocalSave.GetInt(normalRedConsumedTodayKey) != 0; LoadHeroIDList(pendingNewRedHeroIDsTodayKey, pendingNewRedHeroIDsToday); currentRedHeroID = LocalSave.GetInt(currentRedHeroIDKey); } void SaveTodayChallengeRecord() { LocalSave.SetInt(challengeRecordDayKey, challengeRecordDay); LocalSave.SetIntArray(challengedHeroIDsTodayKey, ToArray(challengedHeroIDsToday)); LocalSave.SetInt(lastChallengeHeroIDTodayKey, lastChallengeHeroIDToday); LocalSave.SetInt(normalRedConsumedTodayKey, normalRedConsumedToday ? 1 : 0); LocalSave.SetIntArray(pendingNewRedHeroIDsTodayKey, pendingNewRedHeroIDsToday.ToArray()); LocalSave.SetInt(currentRedHeroIDKey, currentRedHeroID); } void LoadHeroIDSet(string key, HashSet<int> heroIDSet) { heroIDSet.Clear(); int[] heroIDs = LocalSave.GetIntArray(key); if (heroIDs == null) return; for (int i = 0; i < heroIDs.Length; i++) { if (heroIDs[i] != 0) heroIDSet.Add(heroIDs[i]); } } 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() { DateTime now = TimeUtility.ServerNow; return now.Year * 10000 + now.Month * 100 + now.Day; } /// <summary> /// 尝试获取某武将的通关进度 /// </summary> public bool TryGetHeroPassLevelNum(uint heroID, out byte passLevelNum) { return heroTrialDict.TryGetValue(heroID, out passLevelNum); } public bool IsHeroTrialHeroUnlocked(int heroID) { return IsHeroTrialHeroUnlocked(heroID, out _); } 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) { bool isOpenCollectionDayMet = heroConfig.OpenCollectionDay <= 0 || TimeUtility.OpenDay + 1 >= heroConfig.OpenCollectionDay; if (!isOpenCollectionDayMet) { lockTip = Language.Get("HeroTrial02", heroConfig.Name); return false; } } 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 true; } /// <summary> /// 获取武将试炼武将列表:按配置表排序,不依赖服务器进度数据 /// </summary> public List<int> GetHeroTrialHeroIDList() { return HeroTrialConfig.GetSortedHeroIDList(); } } Main/System/HeroTrial/HeroTrialManager.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 53a5e2b3e306f7e4eac9cb9750168c6a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialVictoryWin.cs
New file @@ -0,0 +1,85 @@ using System.Collections.Generic; using Cysharp.Threading.Tasks; using UnityEngine; public class HeroTrialVictoryWin : UIBase { [SerializeField] ButtonEx detailBtn; [SerializeField] TextEx txtFuncName; [SerializeField] ScrollerController scroller; string battleName = BattleConst.HeroTrialBattleField; protected override void InitComponent() { detailBtn.SetListener(() => { BattleSettlementManager.Instance.OpenBattleDetailWin(battleName); }); } protected override void OnPreOpen() { scroller.OnRefreshCell += OnRefreshCell; CreateScroller(); int funcId = (int)FuncOpenEnum.HeroTrial; txtFuncName.text = FuncOpenLVConfig.HasKey(funcId) ? FuncOpenLVConfig.Get(funcId).Name : string.Empty; } protected override void OnPreClose() { scroller.OnRefreshCell -= OnRefreshCell; BattleSettlementManager.Instance.WinShowOver(battleName); } List<Item> showItems = new List<Item>(); void CreateScroller() { var jsonData = BattleSettlementManager.Instance.GetBattleSettlement(battleName); if (jsonData == null) { DelayCloseWindow().Forget(); return; } showItems.Clear(); scroller.Refresh(); if (!jsonData.ContainsKey("itemInfo")) { return; } var resultStr = jsonData["itemInfo"]; for (int i = 0; i < resultStr.Count; i++) { showItems.Add(new Item((int)resultStr[i]["ItemID"], (long)resultStr[i]["Count"])); } showItems.Sort(SortItem); for (int i = 0; i < showItems.Count; i++) { scroller.AddCell(ScrollerDataType.Header, i); } scroller.Restart(); } int SortItem(Item itemA, Item itemB) { var itemConfigA = ItemConfig.Get(itemA.id); var itemConfigB = ItemConfig.Get(itemB.id); return itemConfigB.ItemColor - itemConfigA.ItemColor; } void OnRefreshCell(ScrollerDataType type, CellView cell) { var _cell = cell as SettlementAwardCell; var item = showItems[cell.index]; _cell?.Display(item.id, item.countEx); } } Main/System/HeroTrial/HeroTrialVictoryWin.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: 35108f3aae26e9e459e60e4368a648f3 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/HeroTrial/HeroTrialWin.cs
New file @@ -0,0 +1,171 @@ using System.Collections.Generic; using UnityEngine; public class HeroTrialWin : UIBase { [SerializeField] HeroTrialCarouselView carouselView; [SerializeField] ScrollerController scroller; List<HeroTrialConfig> currentConfigs = new List<HeroTrialConfig>(); uint currentHeroID; byte currentPassLevelNum; bool currentHeroUnlocked; string currentHeroUnlockTip = string.Empty; // OnPreOpen 首次打开时用于定位到红点关卡的 dataIndex;-1 表示不跳转。 int jumpTargetDataIndex = -1; protected override void InitComponent() { } protected override void OnPreOpen() { carouselView.InitPool(); carouselView.BindEvents(); carouselView.OnSelectedIndexChanged += OnSelectedIndexChanged; scroller.OnRefreshCell += OnRefreshCell; HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent += OnHeroTrialInfoUpdate; carouselView.RefreshData(); carouselView.InitWithIndex(GetDefaultHeroIndex()); RefreshScroller(); } protected override void OnPreClose() { carouselView.UnbindEvents(); carouselView.OnSelectedIndexChanged -= OnSelectedIndexChanged; scroller.OnRefreshCell -= OnRefreshCell; HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent -= OnHeroTrialInfoUpdate; } void OnSelectedIndexChanged() { RefreshScroller(); } void OnHeroTrialInfoUpdate() { int redHeroIndex = FindRedHeroIndex(); if (redHeroIndex >= 0) { List<int> heroIDList = HeroTrialManager.Instance.GetHeroTrialHeroIDList(); int redHeroID = heroIDList[redHeroIndex]; if (carouselView.CurrentHeroID != redHeroID) { carouselView.SelectIndex(redHeroIndex); return; } } RefreshScroller(); } int GetDefaultHeroIndex() { List<int> heroIDList = HeroTrialManager.Instance.GetHeroTrialHeroIDList(); if (heroIDList.IsNullOrEmpty()) return 0; int targetHeroID = HeroTrialManager.Instance.GetHeroTrialEnterHeroID(); if (targetHeroID != 0) { for (int i = 0; i < heroIDList.Count; i++) { if (heroIDList[i] == targetHeroID) return i; } } return 0; } int FindRedHeroIndex() { List<int> heroIDList = HeroTrialManager.Instance.GetHeroTrialHeroIDList(); if (heroIDList.IsNullOrEmpty()) return -1; for (int i = 0; i < heroIDList.Count; i++) { if (HeroTrialManager.Instance.IsShowHeroItemRed(heroIDList[i])) return i; } return -1; } /// <summary> /// 根据当前选中的武将,刷新下方的关卡Scroll /// </summary> void RefreshScroller() { currentConfigs.Clear(); currentHeroID = carouselView.CurrentHeroID; currentHeroUnlockTip = string.Empty; currentHeroUnlocked = currentHeroID != 0 && HeroTrialManager.Instance.IsHeroTrialHeroUnlocked((int)currentHeroID, out currentHeroUnlockTip); if (currentHeroID == 0 || !HeroTrialManager.Instance.TryGetHeroPassLevelNum(currentHeroID, out currentPassLevelNum)) { currentPassLevelNum = 0; } if (!currentHeroUnlocked) { currentPassLevelNum = 0; } scroller.Refresh(); if (currentHeroID != 0 && HeroTrialConfig.TryGetHeroConfigList((int)currentHeroID, out var configs)) { currentConfigs.AddRange(configs); for (int i = 0; i < currentConfigs.Count; i++) { scroller.AddCell(ScrollerDataType.Header, currentConfigs[i].CfgID); } } scroller.Restart(); // 首次打开时,定位到当前武将副本下第一个能挑战的关卡;没有则保持默认置顶。 jumpTargetDataIndex = FindJumpTargetDataIndex(); if (jumpTargetDataIndex >= 0) scroller.JumpIndex(jumpTargetDataIndex); } /// <summary> /// 在 currentConfigs 中查找当前武将副本第一个能挑战的关卡对应的 dataIndex; /// 若当前武将无任何可挑战关卡或数据异常则返回 -1。 /// </summary> int FindJumpTargetDataIndex() { if (currentHeroID == 0) return -1; if (currentConfigs.IsNullOrEmpty()) return -1; for (int i = 0; i < currentConfigs.Count; i++) { var config = currentConfigs[i]; if (config == null || config.HeroID != currentHeroID) continue; if (HeroTrialManager.Instance.CanChallenge(config, out _)) return i; } return -1; } /// <summary> /// 刷新关卡Cell显示 /// </summary> void OnRefreshCell(ScrollerDataType type, CellView cell) { var trialCell = cell as HeroTrialCell; if (trialCell == null) return; if (!HeroTrialConfig.TryGetHeroTrialConfig(cell.index, out var config)) return; trialCell.UpdateData(config, currentPassLevelNum, currentHeroUnlocked, currentHeroUnlockTip); } } Main/System/HeroTrial/HeroTrialWin.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: d6e3215d80964334f807ea63e0a306b8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Main/System/Redpoint/MainRedDot.cs
@@ -160,6 +160,7 @@ public const int HeroSkinFlashSaleRepoint = 486; public const int SuperVIPRepoint = 487; public const int GuildAtkDefBatRepoint = 488;//公会攻防战 public const int HeroTrialRepoint = 489;//武将试炼 public void Register() { Main/System/Settlement/BattleSettlementManager.cs
@@ -50,6 +50,9 @@ case BattleConst.BoneBattleField: PopupWindowsProcessor.Instance.Add(isWin ? "BoneBattleVictoryWin" : "BoneBattleFailWin", false, BattleConst.BoneBattleField); break; case BattleConst.HeroTrialBattleField: PopupWindowsProcessor.Instance.Add(isWin ? "HeroTrialVictoryWin" : "HeroTrialFailWin", false, BattleConst.HeroTrialBattleField); break; case BattleConst.TianziBillboradBattleField: PopupWindowsProcessor.Instance.Add("TianziBillboradVictoryWin", false, BattleConst.TianziBillboradBattleField); break; @@ -99,6 +102,16 @@ else { UIManager.Instance.OpenWindow<BoneBattleFailWin>(); } break; case BattleConst.HeroTrialBattleField: if (isWin) { UIManager.Instance.OpenWindowAsync<HeroTrialVictoryWin>().Forget(); } else { UIManager.Instance.OpenWindowAsync<HeroTrialFailWin>().Forget(); } break; case BattleConst.TianziBillboradBattleField: @@ -376,6 +389,7 @@ // 敌方名字是Boss名字 case BattleConst.StoryBossBattleField: case BattleConst.BoneBattleField: case BattleConst.HeroTrialBattleField: case BattleConst.TianziBillboradBattleField: case BattleConst.WarlordPavilionBattleField: BattleObject bossBattleObject = battleField.FindBoss(); @@ -488,4 +502,4 @@ { public int ItemID; public int Count; } } Main/System/WarlordPavilion/TowerBaseWin.cs
@@ -3,14 +3,20 @@ public class TowerBaseWin : OneLevelWin { [SerializeField] Transform WarlordPavilionWinTop; [SerializeField] Transform HeroTrialWinTop; protected override void OpenSubUIByTabIndex() { WarlordPavilionWinTop.SetActive(functionOrder == 0); HeroTrialWinTop.SetActive(functionOrder == 1); switch (functionOrder) { case 0: currentSubUI = UIManager.Instance.OpenWindow<WarlordPavilionWin>(); break; case 1: currentSubUI = UIManager.Instance.OpenWindow<HeroTrialWin>(); break; default: Debug.LogWarning("未知的标签索引: " + functionOrder); break; Main/Utility/EnumHelper.cs
@@ -862,6 +862,7 @@ FestivalActivity = 65,//节日活动 HeroSkinFlashSale = 66,//时装特卖 GuildAtkDefBat = 67,//公会攻防战 HeroTrial = 68,//武将试炼 }