yyl
2025-12-24 3ab907a2386da505ccb960b4f761ad716ad8a648
Merge branch 'master' of http://192.168.1.20:10010/r/Project_SG_scripts
9个文件已修改
37个文件已添加
2121 ■■■■■ 已修改文件
Main/Config/ConfigManager.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HeroFatesConfig.cs 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HeroFatesConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HeroFatesQualityLVConfig.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HeroFatesQualityLVConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/PartialConfigs/HeroFatesQualityLVConfig.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/PartialConfigs/HeroFatesQualityLVConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB241_tagCSHeroFates.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB241_tagCSHeroFates.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HB1_Role/DTCB131_tagSCHeroFatesInfo.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HB1_Role/DTCB131_tagSCHeroFatesInfo.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DataToCtl/PackageRegedit.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ServerPack/HB1_Role/HB131_tagSCHeroFatesInfo.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ServerPack/HB1_Role/HB131_tagSCHeroFatesInfo.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Main.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Hero/UIHeroController.cs 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesCell.cs 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesFastPutPreviewCell.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesFastPutPreviewCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesFastPutPreviewWin.cs 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesFastPutPreviewWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesIHItem.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesIHItem.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesManager.cs 732 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesManager.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutCell.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutItem.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutItem.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutWin.cs 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesPutWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeAttrCell.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeAttrCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeHeadCell.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeHeadCell.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeWin.cs 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesUpgradeWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesWin.cs 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroFates/HeroFatesWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroUI/HeroBaseWin.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroUI/HeroHeadBaseCell.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Main/FightPowerManager.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Redpoint/MainRedDot.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/EnumHelper.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/ConfigManager.cs
@@ -54,6 +54,8 @@
            typeof(GoldRushCampConfig),
            typeof(GoldRushItemConfig),
            typeof(GoldRushWorkerConfig),
            typeof(HeroFatesConfig),
            typeof(HeroFatesQualityLVConfig),
            typeof(HeroLineupHaloConfig),
            typeof(HeroQualityLVConfig),
            typeof(HorseClassConfig),
@@ -266,6 +268,10 @@
        ClearConfigDictionary<GoldRushItemConfig>();
        // 清空 GoldRushWorkerConfig 字典
        ClearConfigDictionary<GoldRushWorkerConfig>();
        // 清空 HeroFatesConfig 字典
        ClearConfigDictionary<HeroFatesConfig>();
        // 清空 HeroFatesQualityLVConfig 字典
        ClearConfigDictionary<HeroFatesQualityLVConfig>();
        // 清空 HeroLineupHaloConfig 字典
        ClearConfigDictionary<HeroLineupHaloConfig>();
        // 清空 HeroQualityLVConfig 字典
Main/Config/Configs/HeroFatesConfig.cs
New file
@@ -0,0 +1,92 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年12月10日
//--------------------------------------------------------
using System.Collections.Generic;
using System;
using UnityEngine;
using LitJson;
public partial class HeroFatesConfig : ConfigBase<int, HeroFatesConfig>
{
    static HeroFatesConfig()
    {
        // 访问过静态构造函数
        visit = true;
    }
    public int FatesID;
    public string FatesName;
    public int FatesQuality;
    public int[] HeroIDList;
    public int[][] AwardItemList;
    public int[] AttrIDList;
    public int[] LVAttrValueList;
    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 FatesID);
            FatesName = tables[1];
            int.TryParse(tables[2],out FatesQuality);
            if (tables[3].Contains("["))
            {
                HeroIDList = JsonMapper.ToObject<int[]>(tables[3]);
            }
            else
            {
                string[] HeroIDListStringArray = tables[3].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                HeroIDList = new int[HeroIDListStringArray.Length];
                for (int i=0;i<HeroIDListStringArray.Length;i++)
                {
                     int.TryParse(HeroIDListStringArray[i],out HeroIDList[i]);
                }
            }
            AwardItemList = JsonMapper.ToObject<int[][]>(tables[4].Replace("(", "[").Replace(")", "]"));
            if (tables[5].Contains("["))
            {
                AttrIDList = JsonMapper.ToObject<int[]>(tables[5]);
            }
            else
            {
                string[] AttrIDListStringArray = tables[5].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                AttrIDList = new int[AttrIDListStringArray.Length];
                for (int i=0;i<AttrIDListStringArray.Length;i++)
                {
                     int.TryParse(AttrIDListStringArray[i],out AttrIDList[i]);
                }
            }
            if (tables[6].Contains("["))
            {
                LVAttrValueList = JsonMapper.ToObject<int[]>(tables[6]);
            }
            else
            {
                string[] LVAttrValueListStringArray = tables[6].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                LVAttrValueList = new int[LVAttrValueListStringArray.Length];
                for (int i=0;i<LVAttrValueListStringArray.Length;i++)
                {
                     int.TryParse(LVAttrValueListStringArray[i],out LVAttrValueList[i]);
                }
            }
        }
        catch (Exception exception)
        {
            Debug.LogError(exception);
        }
    }
}
Main/Config/Configs/HeroFatesConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de1bf25d84fa4414a9c579dfa3b636fc
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Config/Configs/HeroFatesQualityLVConfig.cs
New file
@@ -0,0 +1,50 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年12月10日
//--------------------------------------------------------
using System.Collections.Generic;
using System;
using UnityEngine;
using LitJson;
public partial class HeroFatesQualityLVConfig : ConfigBase<int, HeroFatesQualityLVConfig>
{
    static HeroFatesQualityLVConfig()
    {
        // 访问过静态构造函数
        visit = true;
    }
    public int Id;
    public int FatesQuality;
    public int FatesLV;
    public int NeedStarTotal;
    public int NeedHeroCnt;
    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 Id);
            int.TryParse(tables[1],out FatesQuality);
            int.TryParse(tables[2],out FatesLV);
            int.TryParse(tables[3],out NeedStarTotal);
            int.TryParse(tables[4],out NeedHeroCnt);
        }
        catch (Exception exception)
        {
            Debug.LogError(exception);
        }
    }
}
Main/Config/Configs/HeroFatesQualityLVConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04ded98ba28951c4db9c8281c43ae86b
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Config/PartialConfigs/HeroFatesQualityLVConfig.cs
New file
@@ -0,0 +1,35 @@
using System.Collections.Generic;
public partial class HeroFatesQualityLVConfig : ConfigBase<int, HeroFatesQualityLVConfig>
{
    // <宿缘品质,<宿缘等级,唯一ID>
    static Dictionary<int, Dictionary<int, int>> idDict = new Dictionary<int, Dictionary<int, int>>();
    protected override void OnConfigParseCompleted()
    {
        if (!idDict.ContainsKey(FatesQuality))
        {
            idDict[FatesQuality] = new Dictionary<int, int>();
        }
        idDict[FatesQuality][FatesLV] = Id;
    }
    public static bool TryGetDictByFatesQuality(int fatesQuality, out Dictionary<int, int> dict)
    {
        return idDict.TryGetValue(fatesQuality, out dict);
    }
    public static bool TryGetId(int fatesQuality, int fatesLV, out int id)
    {
        id = 0;
        return idDict.TryGetValue(fatesQuality, out var dict) && dict.TryGetValue(fatesLV, out id);
    }
    public static bool TryGetHeroFatesQualityLVConfig(int fatesQuality, int fatesLV, out HeroFatesQualityLVConfig config)
    {
        config = null;
        if (!TryGetId(fatesQuality, fatesLV, out int id) || !HasKey(id))
            return false;
        config = Get(id);
        return true;
    }
}
Main/Config/PartialConfigs/HeroFatesQualityLVConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 892ed1407bdeb8a42b61f323ff60f3a6
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB241_tagCSHeroFates.cs
New file
@@ -0,0 +1,24 @@
using UnityEngine;
using System.Collections;
// B2 41 武将宿缘 #tagCSHeroFates
public class CB241_tagCSHeroFates : GameNetPackBasic {
    public byte FatesID;    // 宿缘ID
    public byte OPType;    // 0-激活领奖;1-升级
    public byte IndexCnt;
    public  ushort[] ItemIndexList;    // 升级时消耗的材料卡在武将背包索引列表,升级时才发
    public CB241_tagCSHeroFates () {
        combineCmd = (ushort)0x03FE;
        _cmd = (ushort)0xB241;
    }
    public override void WriteToBytes () {
        WriteBytes (FatesID, NetDataType.BYTE);
        WriteBytes (OPType, NetDataType.BYTE);
        WriteBytes (IndexCnt, NetDataType.BYTE);
        WriteBytes (ItemIndexList, NetDataType.WORD, IndexCnt);
    }
}
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB241_tagCSHeroFates.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 109782f5f047e6b40aa4acf5bee3caa3
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/DTCFile/ServerPack/HB1_Role/DTCB131_tagSCHeroFatesInfo.cs
New file
@@ -0,0 +1,12 @@
using UnityEngine;
using System.Collections;
// B1 31 宿缘信息 #tagSCHeroFatesInfo
public class DTCB131_tagSCHeroFatesInfo : DtcBasic {
    public override void Done(GameNetPackBasic vNetPack) {
        base.Done(vNetPack);
        HB131_tagSCHeroFatesInfo vNetData = vNetPack as HB131_tagSCHeroFatesInfo;
        HeroFatesManager.Instance.OnUpdateHeroFatesInfo(vNetData);
    }
}
Main/Core/NetworkPackage/DTCFile/ServerPack/HB1_Role/DTCB131_tagSCHeroFatesInfo.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f32287c05ce489141b839fa73fb39327
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/DataToCtl/PackageRegedit.cs
@@ -135,6 +135,7 @@
        Register(typeof(HA319_tagMCPackDownloadRecord), typeof(DTCA319_tagMCPackDownloadRecord));
        Register(typeof(HB129_tagSCLineupRecommendInfo), typeof(DTCB129_tagSCLineupRecommendInfo));
        Register(typeof(HAB05_tagSCOSACelebrationInfo), typeof(DTCAB05_tagSCOSACelebrationInfo));
        Register(typeof(HB131_tagSCHeroFatesInfo), typeof(DTCB131_tagSCHeroFatesInfo));
    }
    //主工程注册封包
Main/Core/NetworkPackage/ServerPack/HB1_Role/HB131_tagSCHeroFatesInfo.cs
New file
@@ -0,0 +1,31 @@
using UnityEngine;
using System.Collections;
// B1 31 宿缘信息 #tagSCHeroFatesInfo
public class HB131_tagSCHeroFatesInfo : GameNetPackBasic {
    public byte Count;
    public  tagSCHeroFates[] FatesList;
    public HB131_tagSCHeroFatesInfo () {
        _cmd = (ushort)0xB131;
    }
    public override void ReadFromBytes (byte[] vBytes) {
        TransBytes (out Count, vBytes, NetDataType.BYTE);
        FatesList = new tagSCHeroFates[Count];
        for (int i = 0; i < Count; i ++) {
            FatesList[i] = new tagSCHeroFates();
            TransBytes (out FatesList[i].FatesID, vBytes, NetDataType.BYTE);
            TransBytes (out FatesList[i].State, vBytes, NetDataType.BYTE);
            TransBytes (out FatesList[i].FatesLV, vBytes, NetDataType.BYTE);
        }
    }
    public class tagSCHeroFates {
        public byte FatesID;        // 宿缘ID
        public byte State;        // 宿缘状态:0-未激活;1-已激活已领奖
        public byte FatesLV;        // 宿缘等级,激活时为0级,升级后有升级属性
    }
}
Main/Core/NetworkPackage/ServerPack/HB1_Role/HB131_tagSCHeroFatesInfo.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9ee46f7d4246af45a7fcdca58ae94db
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Main.cs
@@ -94,7 +94,7 @@
        managers.Add(GuildBossManager.Instance);
        managers.Add(LineupRecommendManager.Instance);
        managers.Add(OSActivityManager.Instance);
        managers.Add(HeroFatesManager.Instance);
        foreach (var manager in managers)
        {
            manager.Init();
Main/System/Hero/UIHeroController.cs
@@ -22,6 +22,7 @@
            if (skeletonGraphic != null)
            {
                SetMaterialNone();
                if (isLh)
                {
                    var skinConfigTmp = HeroSkinConfig.Get(skinID);
@@ -128,6 +129,7 @@
        skeletonGraphic.Initialize(true);
        skeletonGraphic.enabled = true;
        SetMaterialNone();
        spineAnimationState = skeletonGraphic.AnimationState;
        spineAnimationState.Data.DefaultMix = 0f;
        if (motionName == "")
@@ -158,7 +160,7 @@
    /// <param name="motionName">动作名</param>
    /// <param name="loop">循环</param>
    /// <param name="replay">如果相同动作是否再次重播,比如跑步重播就会跳帧不顺滑</param>
    public virtual void PlayAnimation(string motionName, bool loop = false, bool replay=true)
    public virtual void PlayAnimation(string motionName, bool loop = false, bool replay = true)
    {
        if (spineAnimationState == null) return;
@@ -214,11 +216,20 @@
    }
    public void SetEnabled(bool isEnable)
    {
    {
        if (skeletonGraphic == null)
        {
            return;
        }
        skeletonGraphic.enabled = isEnable;
    }
    public void SetGray()
    {
        skeletonGraphic.material = MaterialUtility.GetDefaultSpriteGrayMaterial();
    }
    public void SetMaterialNone()
    {
        skeletonGraphic.material = null;
    }
}
Main/System/HeroFates.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 87b403c587f7df14899f863d5427edac
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesCell.cs
New file
@@ -0,0 +1,175 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HeroFatesCell : MonoBehaviour
{
    [SerializeField] ImageEx imgBG;
    [SerializeField] TextEx txtFateName;
    [SerializeField] OutlineEx outlineFateName;
    [SerializeField] TextEx txtFateLV;
    [SerializeField] TextEx txtFateNeedHeroHasCnt;
    [SerializeField] TextEx txtHasHeroTotalStar;
    [SerializeField] ItemCell itemCell;
    [SerializeField] ButtonEx btnHave;
    [SerializeField] ImageEx imgHave;
    [SerializeField] Image imgHaveRed;
    [SerializeField] ButtonEx btnUpgrade;
    [SerializeField] TextEx txtUpgrade;
    [SerializeField] ImageEx imgUpgrade;
    [SerializeField] Image imgUpgradeRed;
    [SerializeField] HorizontalLayoutGroup horizontalLayoutGroup;
    [SerializeField] List<HeroFatesIHItem> heroFatesIHItems;
    int[] spacingArr = new int[5] { 150, 150, 99, 68, 37 };
    [SerializeField] Transform transAttr;
    [SerializeField] List<TextEx> attrs;
    [SerializeField] Transform transItems;
    [SerializeField] List<ItemCell> items;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(int index, List<int> showList)
    {
        if (showList.IsNullOrEmpty() || index >= showList.Count || index < 0)
        {
            return;
        }
        int fatesId = showList[index];
        if (!manager.TryGetHeroFatesConfig(fatesId, out HeroFatesConfig config) || config.HeroIDList.IsNullOrEmpty())
        {
            return;
        }
        HeroFatesState state = manager.GetHeroFatesState(fatesId);
        bool isNoActive = state == HeroFatesState.Locked || state == HeroFatesState.Activatable;
        txtFateLV.SetActive(!isNoActive);
        txtFateNeedHeroHasCnt.SetActive(isNoActive);
        txtHasHeroTotalStar.SetActive(state == HeroFatesState.ActiveNotUpgradable || state == HeroFatesState.ActiveUpgradable);
        btnHave.SetActive(isNoActive);
        btnHave.interactable = state == HeroFatesState.Activatable;
        imgHave.gray = state == HeroFatesState.Locked;
        imgHaveRed.SetActive(state == HeroFatesState.Activatable);
        btnUpgrade.SetActive(!isNoActive);
        btnUpgrade.interactable = state != HeroFatesState.MaxLevel;
        imgUpgrade.gray = state == HeroFatesState.MaxLevel;
        txtUpgrade.text = Language.Get(state == HeroFatesState.MaxLevel ? "L1110" : "L1109");
        imgUpgradeRed.SetActive(state == HeroFatesState.ActiveUpgradable);
        imgBG.SetSprite(manager.GetCellBgByFatesQuality(config.FatesQuality));
        txtFateName.text = config.FatesName;
        txtFateName.color = UIHelper.GetUIColorByFunc(config.FatesQuality);
        outlineFateName.OutlineColor = UIHelper.GetUIOutlineColor(config.FatesQuality);
        int needHeroHasCnt = manager.GetFateNeedHeroHasCnt(fatesId);
        int needHeroCnt = config.HeroIDList.Length;
        txtFateNeedHeroHasCnt.text = Language.Get("BoneField09", needHeroHasCnt, needHeroCnt);
        bool hasNowLVAndNextLVConfig = manager.TryGetNowLVAndNextLVConfig(fatesId, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig);
        if (hasNowLVAndNextLVConfig)
        {
            int fatesNowStarCnt = manager.GetFatesNowStarCnt(fatesId);
            int nextNeedStarCnt = nextLVConfig.NeedStarTotal;
            txtHasHeroTotalStar.text = StringUtility.Concat(Language.Get("HeroFates05"), UIHelper.AppendColor(nextNeedStarCnt <= fatesNowStarCnt ? TextColType.LightGreen : TextColType.Red, Language.Get("HeroFates11", fatesNowStarCnt, nextNeedStarCnt)));
        }
        DisplayLH(config);
        transAttr.SetActive(!isNoActive);
        DisplayAttrs(fatesId);
        transItems.SetActive(!config.AwardItemList.IsNullOrEmpty() && isNoActive);
        DisplayAwardItems(config);
        bool hasInfo = manager.TryGetHeroFates(fatesId, out HeroFates info);
        txtFateLV.text = Language.Get("L1113", hasInfo ? info.FatesLV : 0);
        btnHave.SetListener(() => manager.SendHeroFates(fatesId, 0));
        btnUpgrade.SetListener(() =>
        {
            if (!FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.HeroFatesUpgrade, true))
                return;
            manager.chooseHeroFatesId = fatesId;
            manager.chooseHeroFatesQuality = config.FatesQuality;
            UIManager.Instance.OpenWindow<HeroFatesUpgradeWin>();
        });
    }
    void DisplayLH(HeroFatesConfig config)
    {
        if (manager.TryGetNowMaxStarHeroDict(out Dictionary<int, HeroInfo> nowMaxStarHeroDict) || !nowMaxStarHeroDict.IsNullOrEmpty())
        {
            for (int i = 0; i < heroFatesIHItems.Count; i++)
            {
                if (i < config.HeroIDList.Length)
                {
                    int heroID = config.HeroIDList[i];
                    heroFatesIHItems[i].SetActive(true);
                    heroFatesIHItems[i].Display(config.HeroIDList, i, nowMaxStarHeroDict);
                }
                else
                {
                    heroFatesIHItems[i].SetActive(false);
                }
            }
            int index = Mathf.Max(config.HeroIDList.Length - 1, 0);
            if (index >= spacingArr.Length)
            {
                index = spacingArr.Length - 1;
            }
            int spacing = spacingArr[index];
            horizontalLayoutGroup.spacing = spacing;
        }
    }
    void DisplayAttrs(int fatesId)
    {
        if (attrs.IsNullOrEmpty())
            return;
        bool hasConfig = manager.TryGetAttrIDListAndLVAttrValueList(fatesId, out int[] attrIDList, out int[] lvAttrValueList);
        if (!hasConfig)
            return;
        for (int i = 0; i < attrs.Count; i++)
        {
            if (i < attrIDList.Length)
            {
                int attrID = attrIDList[i];
                int attrValue = manager.GetNowAttrValue(fatesId, i);
                attrs[i].text = PlayerPropertyConfig.GetFullDescription(attrID, attrValue);
                attrs[i].SetActive(true);
            }
            else
            {
                attrs[i].SetActive(false);
            }
        }
    }
    void DisplayAwardItems(HeroFatesConfig config)
    {
        if (items.IsNullOrEmpty() || config.AwardItemList.IsNullOrEmpty())
            return;
        for (int i = 0; i < items.Count; i++)
        {
            if (i < config.AwardItemList.Length)
            {
                int itemID = config.AwardItemList[i][0];
                int count = config.AwardItemList[i][1];
                items[i].Init(new ItemCellModel(itemID, false, count));
                items[i].button.SetListener(() => ItemTipUtility.Show(itemID));
                items[i].SetActive(true);
            }
            else
            {
                items[i].SetActive(false);
            }
        }
    }
}
Main/System/HeroFates/HeroFatesCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a00cea9f743c4124381d8ae7426329d5
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesFastPutPreviewCell.cs
New file
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using UnityEngine;
public class HeroFatesFastPutPreviewCell : MonoBehaviour
{
    [SerializeField] List<HeroFatesUpgradeHeadCell> headCells;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(int rowIndex, List<HeroInfo> list)
    {
        if (list.IsNullOrEmpty())
        {
            return;
        }
        for (int i = 0; i < headCells.Count; i++)
        {
            int index = rowIndex * manager.rowCountMax + i;
            if (index < list.Count)
            {
                headCells[i].SetActive(true);
                headCells[i].Display(list[index]);
            }
            else
            {
                headCells[i].SetActive(false);
            }
        }
    }
}
Main/System/HeroFates/HeroFatesFastPutPreviewCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb39d470ad837b74fba100a954f52ce7
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesFastPutPreviewWin.cs
New file
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using UnityEngine;
public class HeroFatesFastPutPreviewWin : UIBase
{
    [SerializeField] ScrollerController scroller;
    [SerializeField] ButtonEx btnClose;
    [SerializeField] ButtonEx btnOk;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    protected override void InitComponent()
    {
        btnClose.SetListener(CloseWindow);
        btnOk.SetListener(() =>
        {
            manager.FastAddList(putPreviewHeroList);
            CloseWindow();
        });
    }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent += OnUpdateHeroFatesInfo;
        CreateScoller();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent -= OnUpdateHeroFatesInfo;
    }
    private void OnUpdateHeroFatesInfo()
    {
        RefeshScoller();
    }
    List<HeroInfo> putPreviewHeroList = new List<HeroInfo>();
    private void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<HeroFatesFastPutPreviewCell>();
        _cell?.Display(cell.index, putPreviewHeroList);
    }
    void CreateScoller()
    {
        scroller.Refresh();
        putPreviewHeroList = manager.GetPutPreviewHeroList(manager.chooseHeroFatesId);
        if (!putPreviewHeroList.IsNullOrEmpty())
        {
            int rowCount = Mathf.CeilToInt((float)putPreviewHeroList.Count / manager.rowCountMax);
            for (int i = 0; i < rowCount; i++)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
        }
        scroller.Restart();
    }
    void RefeshScoller()
    {
        scroller.m_Scorller.RefreshActiveCellViews();
    }
}
Main/System/HeroFates/HeroFatesFastPutPreviewWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9641203a901f43a4d9ca3110fa8fec3c
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesIHItem.cs
New file
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HeroFatesIHItem : MonoBehaviour
{
    [SerializeField] UIHeroController uiHeroController;
    [SerializeField] List<Image> starImgList;
    [SerializeField] ImageEx imgHeroNameBg;
    [SerializeField] TextEx txtHeroName;
    [SerializeField] float lhSize = 0.8f;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(int[] heroIDList, int index, Dictionary<int, HeroInfo> nowMaxStarHeroDict)
    {
        if (heroIDList.IsNullOrEmpty() || index >= heroIDList.Length || index < 0)
            return;
        int heroId = heroIDList[index];
        if (!manager.TryGetHeroAndSkinConfigByHeroID(heroId, out HeroConfig heroConfig, out HeroSkinConfig heroSkinConfig))
            return;
        uiHeroController.Create(heroSkinConfig.SkinID, lhSize);
        bool isHasHero = HeroManager.Instance.HasHero(heroId);
        if (isHasHero)
        {
            uiHeroController.SetMaterialNone();
            int starCnt = !nowMaxStarHeroDict.IsNullOrEmpty() && nowMaxStarHeroDict.ContainsKey(heroId) ? nowMaxStarHeroDict[heroId].heroStar : 0;
            manager.DisplayStars(starImgList, starCnt);
        }
        else
        {
            uiHeroController.SetGray();
            manager.DisplayStars(starImgList, -1);
        }
        imgHeroNameBg.SetSprite(manager.GetHeroFatesNameBGByFatesQuality(heroConfig.Quality));
        txtHeroName.text = heroConfig.Name;
    }
}
Main/System/HeroFates/HeroFatesIHItem.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35905f3d0d176724a91c3188027ed5fb
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesManager.cs
New file
@@ -0,0 +1,732 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UI;
public class HeroFatesManager : GameSystemManager<HeroFatesManager>
{
    public readonly int rowCountMax = 5;
    public override void Init()
    {
        DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin += OnBeforePlayerDataInitializeEventOnRelogin;
        DTC0403_tagPlayerLoginLoadOK.playerLoginOkEvent += OnPlayerLoginOk;
        HeroUIManager.Instance.OnHeroCollectEvent += OnHeroCollectEvent;
        HeroManager.Instance.onHeroChangeEvent += OnHeroChangeEvent;
        FuncOpen.Instance.OnFuncStateChangeEvent += OnFuncStateChangeEvent;
    }
    public override void Release()
    {
        DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin -= OnBeforePlayerDataInitializeEventOnRelogin;
        DTC0403_tagPlayerLoginLoadOK.playerLoginOkEvent -= OnPlayerLoginOk;
        HeroUIManager.Instance.OnHeroCollectEvent -= OnHeroCollectEvent;
        HeroManager.Instance.onHeroChangeEvent -= OnHeroChangeEvent;
        FuncOpen.Instance.OnFuncStateChangeEvent -= OnFuncStateChangeEvent;
    }
    private void OnFuncStateChangeEvent(int obj)
    {
        if (obj == (int)FuncOpenEnum.Hero || obj == (int)FuncOpenEnum.HeroFatesUpgrade)
        {
            UpdateRedpoint();
        }
    }
    private void OnBeforePlayerDataInitializeEventOnRelogin()
    {
        heroFatesInfos.Clear();
    }
    private void OnPlayerLoginOk()
    {
        UpdateRedpoint();
        RefreshAttr();
    }
    private void OnHeroCollectEvent()
    {
        UpdateRedpoint();
    }
    private void OnHeroChangeEvent(HeroInfo info)
    {
        UpdateRedpoint();
    }
    List<int> allFatesHeroID = new List<int>();
    public List<int> GetAllFatesHeroID()
    {
        if (allFatesHeroID.IsNullOrEmpty())
        {
            foreach (var info in HeroFatesConfig.GetValues())
            {
                if (info.HeroIDList.IsNullOrEmpty())
                {
                    continue;
                }
                foreach (var HeroID in info.HeroIDList)
                {
                    if (!allFatesHeroID.Contains(HeroID))
                    {
                        allFatesHeroID.Add(HeroID);
                    }
                }
            }
        }
        return allFatesHeroID;
    }
    Redpoint redpoint = new Redpoint(MainRedDot.MainHerosRedpoint, MainRedDot.HeroFatesRepoint);
    public void UpdateRedpoint()
    {
        redpoint.state = RedPointState.None;
        //武将没开不刷红点
        if (!FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.Hero))
            return;
        bool hasShowRedDotFates = HasShowRedDotFates(out int firstIndex);
        if (hasShowRedDotFates)
        {
            redpoint.state = RedPointState.Simple;
        }
    }
    //<宿缘ID,宿缘信息>
    public Dictionary<int, HeroFates> heroFatesInfos = new Dictionary<int, HeroFates>();
    public bool TryGetHeroFates(int fatesID, out HeroFates info)
    {
        return heroFatesInfos.TryGetValue(fatesID, out info);
    }
    public event Action OnUpdateHeroFatesInfoEvent;
    public event Action OnUpgradeHeroFatesEvent;
    public void OnUpdateHeroFatesInfo(HB131_tagSCHeroFatesInfo vNetData)
    {
        if (vNetData == null || vNetData.FatesList.IsNullOrEmpty())
        {
            return;
        }
        bool isUpgrade = false;
        foreach (var vInfo in vNetData.FatesList)
        {
            if (!heroFatesInfos.ContainsKey(vInfo.FatesID))
            {
                heroFatesInfos[vInfo.FatesID] = new HeroFates();
            }
            heroFatesInfos[vInfo.FatesID].FatesID = vInfo.FatesID;
            heroFatesInfos[vInfo.FatesID].State = vInfo.State;
            if (heroFatesInfos[vInfo.FatesID].FatesLV != vInfo.FatesLV)
            {
                isUpgrade = true;
            }
            heroFatesInfos[vInfo.FatesID].FatesLV = vInfo.FatesLV;
        }
        RefreshAttr();
        UpdateRedpoint();
        OnUpdateHeroFatesInfoEvent?.Invoke();
        if (isUpgrade)
        {
            OnUpgradeHeroFatesEvent?.Invoke();
        }
    }
    public ushort[] GetItemIndexList(List<HeroInfo> heroInfos)
    {
        return heroInfos.Select(heroInfo => (ushort)heroInfo.itemHero.gridIndex).ToArray();
    }
    public void SendHeroFates(int fatesID, int opType, ushort[] itemIndexList = null)
    {
        CB241_tagCSHeroFates pack = new CB241_tagCSHeroFates();
        pack.FatesID = (byte)fatesID;   // 宿缘ID
        pack.OPType = (byte)opType;     // 0-激活领奖;1-升级
        // 升级时消耗的材料卡在武将背包索引列表,升级时才发
        bool isNullOrEmpty = itemIndexList.IsNullOrEmpty();
        pack.ItemIndexList = isNullOrEmpty ? null : itemIndexList;
        pack.IndexCnt = isNullOrEmpty ? (byte)0 : (byte)itemIndexList.Length;
        GameNetSystem.Instance.SendInfo(pack);
    }
    public HeroFatesState GetHeroFatesState(int fatesID)
    {
        //没有封包
        if (!TryGetHeroFates(fatesID, out HeroFates info))
        {
            return IsHeroIDListAllActive(fatesID) ? HeroFatesState.Activatable : HeroFatesState.Locked;
        }
        // 封包宿缘状态:0-未激活;1-已激活已领奖
        if (info.State == 0)
        {
            //武将ID组合列表中的武将的图鉴有没解锁的?- 未解锁
            return IsHeroIDListAllActive(fatesID) ? HeroFatesState.Activatable : HeroFatesState.Locked;
        }
        else if (info.State == 1)
        {
            // 宿缘升级功能没开时视为不可升级
            if (!FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.HeroFatesUpgrade))
            {
                return HeroFatesState.ActiveNotUpgradable;
            }
            if (IsHeroFatesStarMax(fatesID))
            {
                return HeroFatesState.MaxLevel;
            }
            return IsHeroFatesCanUp(fatesID, out int fromFatesLv, out int toFatesLv) ? HeroFatesState.ActiveUpgradable : HeroFatesState.ActiveNotUpgradable;
        }
        return HeroFatesState.Locked;
    }
    public bool IsHeroFatesCanUp(int fatesID, out int fromFatesLv, out int toFatesLv)
    {
        fromFatesLv = 0;
        toFatesLv = 0;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
        {
            return false;
        }
        if (!TryGetHeroFates(fatesID, out HeroFates info))
        {
            return false;
        }
        fromFatesLv = info.FatesLV;
        toFatesLv = info.FatesLV + 1;
        int fatesQuality = config.FatesQuality;
        if (!HeroFatesQualityLVConfig.TryGetHeroFatesQualityLVConfig(fatesQuality, toFatesLv, out HeroFatesQualityLVConfig heroFatesQualityLVConfig))
        {
            return false;
        }
        // 武将总星级是否满足升级需求
        int needStarCnt = heroFatesQualityLVConfig.NeedStarTotal;
        int hasStarCnt = GetFatesNowStarCnt(fatesID);
        if (hasStarCnt < needStarCnt)
        {
            return false;
        }
        // 消耗武将数量是否满足需求
        List<HeroInfo> consumableHeroInfos = GetConsumableHeroInfoList(heroFatesQualityLVConfig.FatesQuality);
        int hasHeroCnt = consumableHeroInfos == null ? 0 : consumableHeroInfos.Count;
        int needHeroCnt = heroFatesQualityLVConfig.NeedHeroCnt;
        if (hasHeroCnt < needHeroCnt)
        {
            return false;
        }
        return true;
    }
    // 获得背包中的所有指定品质的可消耗武将 宿缘品质就是武将品质
    public List<HeroInfo> GetConsumableHeroInfoList(int fatesQuality)
    {
        List<HeroInfo> result = new List<HeroInfo>();
        List<HeroInfo> allHeroesInBag = HeroManager.Instance.GetHeroList();
        foreach (var item in allHeroesInBag)
        {
            if (item == null)
                continue;
            //不是所需品质
            if (item.Quality != fatesQuality)
                continue;
            // 生效中的武将
            if (item.isAttrActive)
                continue;
            // 在任何阵容中上阵
            bool isInAnyTeam = item.IsInAnyTeam();
            if (isInAnyTeam)
                continue;
            // 锁定中
            if (item.isLock)
                continue;
            // 升级过
            if (item.heroLevel > 1)
                continue;
            // 生星过
            if (item.heroStar > 0)
                continue;
            // 觉醒过
            if (item.awakeLevel > 0)
                continue;
            result.Add(item);
        }
        result.Sort((a, b) => a.heroId.CompareTo(b.heroId));
        return result;
    }
    // 当前宿缘的当前最高总星级是否已满级
    public bool IsHeroFatesStarMax(int fatesID)
    {
        if (!TryGetHeroFates(fatesID, out HeroFates info))
            return false;
        if (!TryGetMaxFatesLVConfigByFatesID(fatesID, out HeroFatesQualityLVConfig heroFatesQualityLVConfig))
            return false;
        int maxLv = heroFatesQualityLVConfig.FatesLV;
        int nowLv = info.FatesLV;
        return nowLv >= maxLv;
    }
    // 武将ID组合列表中的武将的图鉴都解锁了?
    public bool IsHeroIDListAllActive(int fatesID)
    {
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config) || config.HeroIDList.IsNullOrEmpty())
            return false;
        int activeHeroCnt = GetFateNeedHeroHasCnt(fatesID);
        return config.HeroIDList.Length <= activeHeroCnt;
    }
    public int GetFateNeedHeroHasCnt(int fatesID)
    {
        int res = 0;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config) || config.HeroIDList.IsNullOrEmpty())
            return 0;
        foreach (var heroID in config.HeroIDList)
        {
            if (!TryGetHeroConfig(heroID, out HeroConfig heroConfig))
                continue;
            // heroBookState: 0未获得、1可激活、2常规、3突破升级、4星升级、5已满级
            int heroBookState = HeroUIManager.Instance.GetHeroBookState(heroID, heroConfig.Quality);
            if (heroBookState > 0)
                res += 1;
        }
        return res;
    }
    public bool TryGetNowMaxStarHeroDict(out Dictionary<int, HeroInfo> nowMaxStarHeroDict)
    {
        nowMaxStarHeroDict = new Dictionary<int, HeroInfo>();
        List<HeroInfo> allHeroesInBag = HeroManager.Instance.GetHeroList();
        if (allHeroesInBag.IsNullOrEmpty())
            return false;
        foreach (var item in allHeroesInBag)
        {
            if (item == null)
                continue;
            if (!nowMaxStarHeroDict.TryGetValue(item.heroId, out HeroInfo heroInfo))
            {
                nowMaxStarHeroDict[item.heroId] = item;
            }
            else
            {
                if (item.heroStar > heroInfo.heroStar)
                    nowMaxStarHeroDict[item.heroId] = item;
            }
        }
        return true;
    }
    public Dictionary<int, HeroInfo> GetFatesNowStarHeroInfoDict(int fatesID)
    {
        if (!IsHeroIDListAllActive(fatesID))
            return null;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config) || config.HeroIDList.IsNullOrEmpty())
            return null;
        if (!TryGetNowMaxStarHeroDict(out Dictionary<int, HeroInfo> nowMaxStarHeroDict) || nowMaxStarHeroDict.IsNullOrEmpty())
            return null;
        Dictionary<int, HeroInfo> heroInfoDict = new Dictionary<int, HeroInfo>();
        foreach (var heroID in config.HeroIDList)
        {
            if (nowMaxStarHeroDict.ContainsKey(heroID))
            {
                heroInfoDict[heroID] = nowMaxStarHeroDict[heroID];
            }
        }
        return heroInfoDict;
    }
    // 获取当前宿缘的当前最高总星级
    public int GetFatesNowStarCnt(int fatesID)
    {
        Dictionary<int, HeroInfo> dict = GetFatesNowStarHeroInfoDict(fatesID);
        if (dict.IsNullOrEmpty())
            return 0;
        // 遍历宿缘配置中所需的武将ID,从maxStarsInBag中获取最高星级并累加
        int res = 0;
        foreach (var item in dict.Values)
        {
            res += item.heroStar;
        }
        return res;
    }
    public void DisplayStars(List<Image> starImgList, int starCnt)
    {
        for (int i = 0; i < starImgList.Count; i++)
        {
            //不显示星级
            if (starCnt == -1)
            {
                starImgList[i].SetActive(false);
            }
            else if (starCnt == 0 && i == 0)
            {
                // 无星级 特殊处理
                starImgList[i].SetActive(true);
                starImgList[i].SetSprite("herostar" + starCnt);
            }
            else if ((starCnt - 1) % starImgList.Count >= i)
            {
                starImgList[i].SetActive(true);
                starImgList[i].SetSprite("herostar" + (((starCnt - 1) / starImgList.Count) + 1) * starImgList.Count);
            }
            else
            {
                starImgList[i].SetActive(false);
            }
        }
    }
    public bool IsShowRedDot(int fatesID)
    {
        HeroFatesState state = GetHeroFatesState(fatesID);
        return state == HeroFatesState.Activatable || state == HeroFatesState.ActiveUpgradable;
    }
    public bool HasShowRedDotFates(out int firstIndex)
    {
        firstIndex = 0;
        List<int> list = GetFatesIDList();
        if (list.IsNullOrEmpty())
            return false;
        for (int i = 0; i < list.Count; i++)
        {
            int fatesID = list[i];
            if (IsShowRedDot(fatesID))
            {
                firstIndex = i;
                return true;
            }
        }
        return false;
    }
    public List<HeroInfo> GetPutPreviewHeroList(int fatesID)
    {
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
        {
            return null;
        }
        int fatesQuality = config.FatesQuality;
        if (!TryGetNowLVAndNextLVConfig(fatesID, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig))
        {
            return null;
        }
        int needHeroCnt = nextLVConfig.NeedHeroCnt;
        List<HeroInfo> consumableHeroInfoList = GetConsumableHeroInfoList(fatesQuality);
        if (consumableHeroInfoList.IsNullOrEmpty())
        {
            return null;
        }
        List<HeroInfo> putPreviewHeroList = new List<HeroInfo>();
        int count = Math.Min(needHeroCnt, consumableHeroInfoList.Count);
        for (int i = 0; i < count; i++)
        {
            putPreviewHeroList.Add(consumableHeroInfoList[i]);
        }
        return putPreviewHeroList;
    }
    #region 玩家选中内容
    public int chooseHeroFatesId = 0;
    public int chooseHeroFatesQuality = 0;
    public HeroInfo chooseHeroInfo = null;
    // 给玩家操作选中的列表
    public List<HeroInfo> chooseCostHeroInfos = new List<HeroInfo>();
    // 玩家实际选中的列表
    public List<HeroInfo> realCostHeroInfos = new List<HeroInfo>();
    public event Action OnRealChooseChangeEvent;
    public event Action OnChooseChangeEvent;
    public void InitChooseList()
    {
        chooseCostHeroInfos.Clear();
        foreach (var heroInfo in realCostHeroInfos)
        {
            chooseCostHeroInfos.Add(heroInfo);
        }
        chooseHeroInfo = null;
    }
    public void ChooseAdd(HeroInfo heroInfo)
    {
        if (chooseCostHeroInfos.Contains(heroInfo))
        {
            chooseCostHeroInfos.Remove(heroInfo);
            chooseHeroInfo = null;
        }
        else
        {
            chooseCostHeroInfos.Add(heroInfo);
            chooseHeroInfo = heroInfo;
        }
        OnChooseChangeEvent?.Invoke();
    }
    public void ClearAllList()
    {
        chooseCostHeroInfos.Clear();
        realCostHeroInfos.Clear();
        chooseHeroInfo = null;
        OnRealChooseChangeEvent?.Invoke();
    }
    public void PutList()
    {
        realCostHeroInfos.Clear();
        foreach (var heroInfo in chooseCostHeroInfos)
        {
            realCostHeroInfos.Add(heroInfo);
        }
        OnRealChooseChangeEvent?.Invoke();
    }
    public void FastAddList(List<HeroInfo> heroInfos)
    {
        if (heroInfos.IsNullOrEmpty())
            return;
        chooseCostHeroInfos.Clear();
        realCostHeroInfos.Clear();
        foreach (var heroInfo in heroInfos)
        {
            chooseCostHeroInfos.Add(heroInfo);
            realCostHeroInfos.Add(heroInfo);
        }
        OnRealChooseChangeEvent?.Invoke();
    }
    #endregion
    #region 属性计算
    public Dictionary<int, long> attrDic = new Dictionary<int, long>();
    public Dictionary<int, long> GetTotalAttr()
    {
        return attrDic;
    }
    public long GetAttrValue(int attrID)
    {
        attrDic.TryGetValue(attrID, out long value);
        return value;
    }
    public int GetAttrPer(int attrID)
    {
        if (PlayerPropertyConfig.baseAttr2perDict.ContainsKey(attrID))
        {
            var pertype = PlayerPropertyConfig.baseAttr2perDict[attrID];
            attrDic.TryGetValue(pertype, out long value);
            return (int)(value);
        }
        return 0;
    }
    public void RefreshAttr()
    {
        attrDic.Clear();
        List<int> allIds = HeroFatesConfig.GetKeys();
        foreach (var fatesID in allIds)
        {
            HeroFatesState state = GetHeroFatesState(fatesID);
            if (!TryGetAttrIDListAndLVAttrValueList(fatesID, out int[] attrIDList, out int[] lvAttrValueList))
                continue;
            for (int i = 0; i < attrIDList.Length; i++)
            {
                if (!attrDic.ContainsKey(attrIDList[i]))
                {
                    attrDic[attrIDList[i]] = 0;
                }
                if (state == HeroFatesState.Locked || state == HeroFatesState.Activatable)
                    continue;
                int value = GetNowAttrValue(fatesID, i);
                attrDic[attrIDList[i]] += value;
            }
        }
    }
    //最终属性 = 等级 * 每级属性值
    public int GetNowAttrValue(int fatesID, int attrIndex)
    {
        if (!TryGetAttrIDListAndLVAttrValueList(fatesID, out int[] attrIDList, out int[] lvAttrValueList))
            return 0;
        if (attrIndex < 0 || attrIndex > attrIDList.Length)
            return 0;
        if (!TryGetHeroFates(fatesID, out HeroFates info))
            return 0;
        int lv = info.FatesLV;
        return lv * lvAttrValueList[attrIndex];
    }
    //下一级属性 = (等级 + 1) * 每级属性值
    public int GetNextAttrValue(int fatesID, int attrIndex)
    {
        if (!TryGetAttrIDListAndLVAttrValueList(fatesID, out int[] attrIDList, out int[] lvAttrValueList))
            return 0;
        if (attrIndex < 0 || attrIndex > attrIDList.Length)
            return 0;
        if (!TryGetHeroFates(fatesID, out HeroFates info))
            return 0;
        int lv = info.FatesLV + 1;
        return lv * lvAttrValueList[attrIndex];
    }
    #endregion
    #region 读表
    // 0级时,不返回nowLVConfig,只返回nowLVConfig
    public bool TryGetNowLVAndNextLVConfig(int fatesID, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig)
    {
        nowLv = 0;
        nowLVConfig = null;
        nextLVConfig = null;
        if (!TryGetHeroFates(fatesID, out HeroFates info))
            return false;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
            return false;
        int quality = config.FatesQuality;
        nowLv = info.FatesLV;
        if (nowLv != 0)
        {
            if (!HeroFatesQualityLVConfig.TryGetHeroFatesQualityLVConfig(quality, nowLv, out nowLVConfig))
                return false;
        }
        int nextLv = nowLv + 1;
        return HeroFatesQualityLVConfig.TryGetHeroFatesQualityLVConfig(quality, nextLv, out nextLVConfig);
    }
    public bool TryGetMaxFatesLVConfigByFatesID(int fatesID, out HeroFatesQualityLVConfig heroFatesQualityLVConfig)
    {
        heroFatesQualityLVConfig = null;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
            return false;
        int fatesQuality = config.FatesQuality;
        if (!HeroFatesQualityLVConfig.TryGetDictByFatesQuality(fatesQuality, out Dictionary<int, int> dict) || dict.IsNullOrEmpty())
            return false;
        int maxFatesLV = dict.Keys.Max();
        if (!HeroFatesQualityLVConfig.TryGetHeroFatesQualityLVConfig(fatesQuality, maxFatesLV, out heroFatesQualityLVConfig))
            return false;
        return true;
    }
    public bool TryGetHeroFatesConfig(int fatesID, out HeroFatesConfig config)
    {
        config = null;
        if (!HeroFatesConfig.HasKey(fatesID))
            return false;
        config = HeroFatesConfig.Get(fatesID);
        return true;
    }
    public bool TryGetAttrIDListAndLVAttrValueList(int fatesID, out int[] attrIDList, out int[] lvAttrValueList)
    {
        attrIDList = null;
        lvAttrValueList = null;
        if (!TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
            return false;
        attrIDList = config.AttrIDList;
        lvAttrValueList = config.LVAttrValueList;
        if (attrIDList.IsNullOrEmpty() || lvAttrValueList.IsNullOrEmpty())
            return false;
        return attrIDList.Length == lvAttrValueList.Length;
    }
    public bool TryGetHeroConfig(int heroID, out HeroConfig config)
    {
        config = null;
        if (!HeroConfig.HasKey(heroID))
            return false;
        config = HeroConfig.Get(heroID);
        return true;
    }
    public bool TryGetHeroAndSkinConfigByHeroID(int heroID, out HeroConfig heroConfig, out HeroSkinConfig heroSkinConfig)
    {
        heroConfig = null;
        heroSkinConfig = null;
        if (!HeroConfig.HasKey(heroID))
            return false;
        heroConfig = HeroConfig.Get(heroID);
        int[] skinIDList = heroConfig.SkinIDList;
        if (skinIDList.IsNullOrEmpty())
            return false;
        int skinID = skinIDList[0];
        if (!HeroSkinConfig.HasKey(skinID))
            return false;
        heroSkinConfig = HeroSkinConfig.Get(skinID);
        return true;
    }
    List<int> fatesIDList = null;
    public List<int> GetFatesIDList()
    {
        if (fatesIDList.IsNullOrEmpty())
        {
            fatesIDList = HeroFatesConfig.GetKeys();
            // 按品质升序排序,相同品质按ID升序排序
            fatesIDList.Sort((id1, id2) =>
            {
                if (!TryGetHeroFatesConfig(id1, out HeroFatesConfig config1) ||
                    !TryGetHeroFatesConfig(id2, out HeroFatesConfig config2))
                {
                    return id1.CompareTo(id2); // 如果获取配置失败,按ID排序
                }
                // 先比较品质
                int qualityCompare = config1.FatesQuality.CompareTo(config2.FatesQuality);
                if (qualityCompare != 0)
                {
                    return qualityCompare; // 品质不同,按品质排序
                }
                // 品质相同,按ID排序
                return id1.CompareTo(id2);
            });
        }
        return fatesIDList;
    }
    public string GetCellBgByFatesQuality(int fatesQuality)
    {
        return StringUtility.Concat("HeroFatesQualityBG", fatesQuality.ToString());
    }
    public string GetHeroFatesNameBGByFatesQuality(int fatesQuality)
    {
        return StringUtility.Concat("HeroFatesNameBG", fatesQuality.ToString());
    }
    #endregion
}
public enum HeroFatesState
{
    Locked,             // 未激活
    Activatable,        // 可激活 (未激活但材料足够)
    ActiveNotUpgradable,// 已激活但不可升级 (材料不足或配置不支持)
    ActiveUpgradable,   // 已激活且可升级 (材料足够升级下一档)
    MaxLevel,           // 已升到满级
}
public class HeroFates
{
    public int FatesID;        // 宿缘ID
    public int State;        // 宿缘状态:0-未激活;1-已激活已领奖
    public int FatesLV;        // 宿缘等级,激活时为0级,升级后有升级属性
}
Main/System/HeroFates/HeroFatesManager.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43d2eef891a1efc45b86bf65cbc5a010
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesPutCell.cs
New file
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using UnityEngine;
public class HeroFatesPutCell : MonoBehaviour
{
    [SerializeField] List<HeroFatesPutItem> items = new List<HeroFatesPutItem>();
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(int rowIndex, List<HeroInfo> list)
    {
        if (list.IsNullOrEmpty())
        {
            return;
        }
        for (int i = 0; i < items.Count; i++)
        {
            int index = rowIndex * manager.rowCountMax + i;
            if (index < list.Count)
            {
                items[i].SetActive(true);
                items[i].Display(list[index]);
            }
            else
            {
                items[i].SetActive(false);
            }
        }
    }
}
Main/System/HeroFates/HeroFatesPutCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9f56162b0d350048812d05a806629f4
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesPutItem.cs
New file
@@ -0,0 +1,42 @@
using UnityEngine;
using UnityEngine.UI;
public class HeroFatesPutItem : MonoBehaviour
{
    [SerializeField] HeroHeadBaseCell heroHeadBaseCell;
    [SerializeField] Image jobImg;
    [SerializeField] Text nameText;
    [SerializeField] Transform select;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    HeroInfo hero;
    public void Display(HeroInfo hero)
    {
        this.hero = hero;
        heroHeadBaseCell.Init(hero.heroId, hero.SkinID, hero.heroStar, hero.awakeLevel, hero.heroLevel, OnClick);
        nameText.text = hero.breakLevel == 0 ? hero.heroConfig.Name : Language.Get("herocardbreaklv", hero.heroConfig.Name, hero.breakLevel);
        jobImg.SetSprite(HeroUIManager.Instance.GetJobIconName(hero.heroConfig.Class));
        bool isChoose = manager.chooseCostHeroInfos.Contains(hero);
        select?.SetActive(isChoose);
    }
    public void OnClick()
    {
        if (!manager.TryGetNowLVAndNextLVConfig(manager.chooseHeroFatesId, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig))
        {
            return;
        }
        bool isChoose = manager.chooseCostHeroInfos.Contains(hero);
        if (!isChoose)
        {
            int nextCnt = manager.chooseCostHeroInfos.Count + 1;
            // 当前选中武将已达上限
            if (nextCnt > nextLVConfig.NeedHeroCnt)
            {
                SysNotifyMgr.Instance.ShowTip("HeroFates03");
                return;
            }
        }
        manager.ChooseAdd(hero);
    }
}
Main/System/HeroFates/HeroFatesPutItem.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 549bc7cdfe32a6648a40368a19551861
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesPutWin.cs
New file
@@ -0,0 +1,100 @@
using System.Collections.Generic;
using UnityEngine;
public class HeroFatesPutWin : UIBase
{
    [SerializeField] Transform transChooseHero;
    [SerializeField] HeroFatesPutItem chooseHero;
    [SerializeField] GiftBaseCell[] giftBaseCells;
    [SerializeField] Transform transNoChooseHero;
    [SerializeField] ScrollerController scroller;
    [SerializeField] Transform transNoCanChooseHero;
    [SerializeField] ButtonEx btnPut;
    [SerializeField] TextEx txtChooseCnt;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    protected override void InitComponent()
    {
        btnPut.SetListener(() =>
        {
            manager.PutList();
            CloseWindow();
        });
    }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        manager.OnChooseChangeEvent += OnChooseChangeEvent;
        CreateScoller();
        DisplayChooseHero();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        manager.OnChooseChangeEvent -= OnChooseChangeEvent;
    }
    private void OnChooseChangeEvent()
    {
        RefeshScoller();
        DisplayChooseHero();
    }
    List<HeroInfo> list = new List<HeroInfo>();
    private void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<HeroFatesPutCell>();
        _cell?.Display(cell.index, list);
    }
    void CreateScoller()
    {
        scroller.Refresh();
        if (manager.TryGetHeroFatesConfig(manager.chooseHeroFatesId, out HeroFatesConfig config))
        {
            int fatesQuality = config.FatesQuality;
            list = manager.GetConsumableHeroInfoList(fatesQuality);
            transNoCanChooseHero.SetActive(list.IsNullOrEmpty());
            if (!list.IsNullOrEmpty())
            {
                int rowCount = Mathf.CeilToInt((float)list.Count / manager.rowCountMax);
                for (int i = 0; i < rowCount; i++)
                {
                    scroller.AddCell(ScrollerDataType.Header, i);
                }
            }
        }
        scroller.Restart();
    }
    void RefeshScoller()
    {
        scroller.m_Scorller.RefreshActiveCellViews();
    }
    void DisplayChooseHero()
    {
        HeroInfo heroInfo = manager.chooseHeroInfo;
        transNoChooseHero.SetActive(heroInfo == null);
        transChooseHero.SetActive(heroInfo != null);
        if (heroInfo != null)
        {
            chooseHero.Display(heroInfo);
            HeroUIManager.Instance.RefreshGiftCell(giftBaseCells, heroInfo);
        }
        if (!manager.TryGetNowLVAndNextLVConfig(manager.chooseHeroFatesId, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig))
        {
            return;
        }
        int needCnt = nextLVConfig.NeedHeroCnt;
        int chooseCnt = manager.chooseCostHeroInfos.Count;
        txtChooseCnt.text = Language.Get("HeroFates04", UIHelper.AppendColor(chooseCnt >= needCnt ? TextColType.DarkGreen : TextColType.Red, Language.Get("BoneField09", chooseCnt, needCnt)));
    }
}
Main/System/HeroFates/HeroFatesPutWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e38b8e8bec1da9945b8ba306696a828a
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesUpgradeAttrCell.cs
New file
@@ -0,0 +1,43 @@
using UnityEngine;
public class HeroFatesUpgradeAttrCell : MonoBehaviour
{
    [SerializeField] TextEx txtAttrName;
    [SerializeField] TextEx txtAttrFromValue;
    [SerializeField] TextEx txtAttrToValue;
    [SerializeField] ImageEx imgAdd;
    [SerializeField] UIEffectPlayer uIEffectPlayer1;
    [SerializeField] UIEffectPlayer uIEffectPlayer2;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(int index, CellView cell, bool isPlayer = false)
    {
        uIEffectPlayer1.Stop();
        uIEffectPlayer2.Stop();
        int fatesID = cell.info.Value.infoInt1;
        if (!manager.TryGetAttrIDListAndLVAttrValueList(fatesID, out int[] attrIDList, out int[] lvAttrValueList))
            return;
        if (index < 0 || index >= attrIDList.Length)
            return;
        int attrID = attrIDList[index];
        long attrValue = lvAttrValueList[index];
        if (!PlayerPropertyConfig.HasKey(attrID))
            return;
        PlayerPropertyConfig config = PlayerPropertyConfig.Get(attrID);
        txtAttrName.text = config.ShowName;
        long fromValue = manager.GetNowAttrValue(fatesID, index);
        txtAttrFromValue.text = PlayerPropertyConfig.GetValueDescription(attrID, fromValue);
        long toValue = manager.GetNextAttrValue(fatesID, index);
        txtAttrToValue.text = PlayerPropertyConfig.GetValueDescription(attrID, toValue);
        imgAdd.SetActive(toValue > fromValue);
        if (isPlayer)
        {
            uIEffectPlayer1.Play();
            uIEffectPlayer2.Play();
        }
    }
}
Main/System/HeroFates/HeroFatesUpgradeAttrCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3300269fab35f104bad377e0345e51ce
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesUpgradeHeadCell.cs
New file
@@ -0,0 +1,19 @@
using UnityEngine;
using UnityEngine.UI;
public class HeroFatesUpgradeHeadCell : MonoBehaviour
{
    [SerializeField] HeroHeadBaseCell heroHeadBaseCell;
    [SerializeField] Image jobImg;
    [SerializeField] Text nameText;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    public void Display(HeroInfo hero)
    {
        heroHeadBaseCell.Init(hero.heroId, hero.SkinID, hero.heroStar, hero.awakeLevel, hero.heroLevel);
        nameText.text = hero.breakLevel == 0 ? hero.heroConfig.Name : Language.Get("herocardbreaklv", hero.heroConfig.Name, hero.breakLevel);
        jobImg.SetSprite(HeroUIManager.Instance.GetJobIconName(hero.heroConfig.Class));
    }
}
Main/System/HeroFates/HeroFatesUpgradeHeadCell.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3e4cfdc83ca8f2429a55b307166a0a2
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesUpgradeWin.cs
New file
@@ -0,0 +1,252 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HeroFatesUpgradeWin : UIBase
{
    [SerializeField] List<HeroFatesUpgradeHeadCell> headCells;
    [SerializeField] TextEx txtTotalStarCnt;
    [SerializeField] TextEx txtFromLv;
    [SerializeField] TextEx txtToLv;
    [SerializeField] ScrollerController scroller;
    [SerializeField] ButtonEx btnFastPut;
    [SerializeField] ButtonEx btnUpdate;
    [SerializeField] ButtonEx btnPut;
    [SerializeField] ImageEx imgQualityHeroBg;
    [SerializeField] ImageEx imgQualityHeroIcon;
    [SerializeField] ImageEx imgQualityHeroNull;
    [SerializeField] Image imgPutRed;
    [SerializeField] ButtonEx btnQualityHeroClear;
    [SerializeField] TextEx txtQualityHeroName;
    [SerializeField] TextEx txtNeedQualityHeroCnt;
    int fatesID;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    protected override void InitComponent()
    {
        btnPut.SetListener(() =>
        {
            manager.InitChooseList();
            UIManager.Instance.OpenWindow<HeroFatesPutWin>();
        });
        btnQualityHeroClear.SetListener(() =>
        {
            manager.ClearAllList();
            DisplayChoose(fatesID);
        });
        btnFastPut.SetListener(() =>
        {
            var list = manager.GetPutPreviewHeroList(manager.chooseHeroFatesId);
            if (list.IsNullOrEmpty())
            {
                //符合升级条件的武将不足
                SysNotifyMgr.Instance.ShowTip("HeroFates02");
                return;
            }
            UIManager.Instance.OpenWindow<HeroFatesFastPutPreviewWin>();
        });
        btnUpdate.SetListener(() =>
        {
            if (!manager.TryGetNowLVAndNextLVConfig(fatesID, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig))
            {
                return;
            }
            int fatesNowStarCnt = manager.GetFatesNowStarCnt(fatesID);
            int nextNeedStarCnt = nextLVConfig.NeedStarTotal;
            if (fatesNowStarCnt < nextNeedStarCnt)
            {
                //武将总星级不足
                SysNotifyMgr.Instance.ShowTip("HeroFates01");
                return;
            }
            ushort[] arr = manager.GetItemIndexList(manager.realCostHeroInfos);
            //放入的消耗武将数量不足
            if (arr.IsNullOrEmpty() || nextLVConfig.NeedHeroCnt > manager.realCostHeroInfos.Count)
            {
                SysNotifyMgr.Instance.ShowTip("HeroFates04");
                return;
            }
            manager.SendHeroFates(manager.chooseHeroFatesId, 1, arr);
            manager.ClearAllList();
        });
    }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent += OnUpdateHeroFatesInfo;
        manager.OnRealChooseChangeEvent += OnRealChooseChangeEvent;
        manager.ClearAllList();
        fatesID = manager.chooseHeroFatesId;
        DisplayHeadCells(fatesID);
        DisplayTotalStarCnt(fatesID);
        CreateScoller(fatesID);
        DisplayLV(fatesID);
        DisplayChoose(fatesID);
    }
    void DisplayTotalStarCnt(int fatesID)
    {
        bool hasNowLVAndNextLVConfig = manager.TryGetNowLVAndNextLVConfig(fatesID, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig);
        if (hasNowLVAndNextLVConfig)
        {
            int fatesNowStarCnt = manager.GetFatesNowStarCnt(fatesID);
            int nextNeedStarCnt = nextLVConfig.NeedStarTotal;
            txtTotalStarCnt.text = UIHelper.AppendColor(nextNeedStarCnt <= fatesNowStarCnt ? TextColType.DarkGreen : TextColType.Red, StringUtility.Concat(Language.Get("HeroFates05"), Language.Get("HeroFates11", fatesNowStarCnt, nextNeedStarCnt)));
        }
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent -= OnUpdateHeroFatesInfo;
        manager.OnRealChooseChangeEvent -= OnRealChooseChangeEvent;
    }
    private void OnRealChooseChangeEvent()
    {
        RefreshAll();
    }
    private void OnUpdateHeroFatesInfo()
    {
        if (manager.IsHeroFatesStarMax(manager.chooseHeroFatesId))
        {
            SysNotifyMgr.Instance.ShowTip("HeroFates02");
            CloseWindow();
            return;
        }
        RefreshAll();
        var smallList = scroller.m_Scorller.GetActiveCellViews(); // 修正拼写错误
        for (int i = 0; i < smallList.Count; i++)
        {
            var item = smallList[i];
            var cellView = item as CellView;
            if (cellView != null)
            {
                var _cell = cellView.GetComponent<HeroFatesUpgradeAttrCell>();
                _cell?.Display(cellView.index, cellView, true);
            }
        }
    }
    void RefreshAll()
    {
        fatesID = manager.chooseHeroFatesId;
        DisplayHeadCells(fatesID);
        DisplayTotalStarCnt(fatesID);
        DisplayLV(fatesID);
        DisplayChoose(fatesID);
        RefeshScoller();
    }
    private void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<HeroFatesUpgradeAttrCell>();
        _cell?.Display(cell.index, cell);
    }
    void CreateScoller(int fatesID)
    {
        scroller.Refresh();
        if (manager.TryGetAttrIDListAndLVAttrValueList(fatesID, out var attrIDList, out var lvAttrValueList))
        {
            for (int i = 0; i < attrIDList.Length; i++)
            {
                CellInfo cellInfo = new CellInfo();
                cellInfo.infoInt1 = fatesID;
                scroller.AddCell(ScrollerDataType.Header, i, cellInfo);
            }
        }
        scroller.Restart();
    }
    void RefeshScoller()
    {
        scroller.m_Scorller.RefreshActiveCellViews();
    }
    private void DisplayLV(int fatesID)
    {
        int fromLv = 0;
        int toLv = 0;
        if (manager.TryGetHeroFates(fatesID, out HeroFates info))
        {
            fromLv = info.FatesLV;
            toLv = info.FatesLV + 1;
        }
        txtFromLv.text = Language.Get("L1113", fromLv);
        txtToLv.text = Language.Get("L1113", toLv);
    }
    public void DisplayHeadCells(int fatesID)
    {
        if (!HeroFatesConfig.HasKey(fatesID))
            return;
        if (!manager.TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config) || config.HeroIDList.IsNullOrEmpty())
            return;
        Dictionary<int, HeroInfo> dict = manager.GetFatesNowStarHeroInfoDict(fatesID);
        if (dict.IsNullOrEmpty())
            return;
        for (int i = 0; i < headCells.Count; i++)
        {
            if (i < config.HeroIDList.Length)
            {
                int heroID = config.HeroIDList[i];
                if (dict.TryGetValue(heroID, out HeroInfo info))
                {
                    headCells[i].Display(info);
                    headCells[i].SetActive(true);
                }
                else
                {
                    headCells[i].SetActive(false);
                }
            }
            else
            {
                headCells[i].SetActive(false);
            }
        }
    }
    public void DisplayChoose(int fatesID)
    {
        if (!manager.TryGetHeroFatesConfig(fatesID, out HeroFatesConfig config))
        {
            return;
        }
        if (!manager.TryGetNowLVAndNextLVConfig(fatesID, out int nowLv, out HeroFatesQualityLVConfig nowLVConfig, out HeroFatesQualityLVConfig nextLVConfig))
        {
            return;
        }
        int chooseCnt = manager.realCostHeroInfos.Count;
        bool isChoose = chooseCnt > 0;
        imgQualityHeroIcon.SetActive(isChoose);
        btnQualityHeroClear.SetActive(isChoose);
        imgQualityHeroNull.SetActive(!isChoose);
        imgQualityHeroBg.SetItemBackGround(config.FatesQuality);
        imgQualityHeroIcon.SetSprite(StringUtility.Concat("HeroFatesQualityHead", config.FatesQuality.ToString()));
        txtQualityHeroName.text = StringUtility.Concat(RichTextMsgReplaceConfig.GetRichReplace("HeroQuality", config.FatesQuality), Language.Get("herocard3"));
        txtQualityHeroName.color = UIHelper.GetUIColorByFunc(config.FatesQuality);
        int needCnt = nextLVConfig.NeedHeroCnt;
        txtNeedQualityHeroCnt.text = UIHelper.AppendColor(chooseCnt >= needCnt ? TextColType.DarkGreen : TextColType.Red, Language.Get("BoneField09", chooseCnt, needCnt));
        bool isShowRed = manager.IsShowRedDot(fatesID);
        imgPutRed.SetActive(isShowRed && !isChoose);
    }
}
Main/System/HeroFates/HeroFatesUpgradeWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36a41763eeea97f4284585b5e64c3fa8
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroFates/HeroFatesWin.cs
New file
@@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class HeroFatesWin : UIBase
{
    [SerializeField] ScrollerController scroller;
    [SerializeField] List<TextEx> attrs;
    HeroFatesManager manager { get { return HeroFatesManager.Instance; } }
    protected override void InitComponent()
    {
    }
    protected override void OnPreOpen()
    {
        scroller.OnRefreshCell += OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent += OnUpdateHeroFatesInfo;
        CreateScoller();
        int firstIndex = 0;
        manager.HasShowRedDotFates(out firstIndex);
        scroller.JumpIndex(firstIndex);
        DisplayAttrs();
    }
    protected override void OnPreClose()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        manager.OnUpdateHeroFatesInfoEvent -= OnUpdateHeroFatesInfo;
    }
    private void OnUpdateHeroFatesInfo()
    {
        RefeshScoller();
        DisplayAttrs();
    }
    List<int> fatesIDList = new List<int>();
    private void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell.GetComponent<HeroFatesCell>();
        _cell?.Display(cell.index, fatesIDList);
    }
    void CreateScoller()
    {
        scroller.Refresh();
        fatesIDList = manager.GetFatesIDList();
        if (!fatesIDList.IsNullOrEmpty())
        {
            for (int i = 0; i < fatesIDList.Count; i++)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
        }
        scroller.Restart();
    }
    void RefeshScoller()
    {
        scroller.m_Scorller.RefreshActiveCellViews();
    }
    void DisplayAttrs()
    {
        Dictionary<int, long> totalAttrDict = manager.GetTotalAttr();
        if (attrs.IsNullOrEmpty() || totalAttrDict.IsNullOrEmpty())
            return;
        List<int> totalAttrIdList = totalAttrDict.Keys
            .OrderByDescending(attrID => totalAttrDict[attrID] > 0)  // 属性值>0的排前面
            .ThenBy(attrID => attrID)  // 然后按attrID升序
            .ToList();
        for (int i = 0; i < attrs.Count; i++)
        {
            if (i < totalAttrDict.Count)
            {
                int attrID = totalAttrIdList[i];
                long attrValue = totalAttrDict[attrID];
                attrs[i].text = PlayerPropertyConfig.GetFullDescription(attrID, attrValue);
                attrs[i].SetActive(true);
            }
            else
            {
                attrs[i].SetActive(false);
            }
        }
    }
}
Main/System/HeroFates/HeroFatesWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65869ccc1efcb1741ae8394076307030
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/HeroUI/HeroBaseWin.cs
@@ -16,8 +16,8 @@
        base.InitComponent();
        //招募为另外一个界面,避免关闭时显示空白
        callBtn.AddListener(()=>
        {
        callBtn.AddListener(() =>
        {
            //打开招募界面
            UIManager.Instance.OpenWindow<HeroCallWin>();
        });
@@ -51,6 +51,7 @@
                currentSubUI = UIManager.Instance.OpenWindow<HeroCollectionWin>();
                break;
            case 2:
                currentSubUI = UIManager.Instance.OpenWindow<HeroFatesWin>();
                break;
            default:
                Debug.LogWarning("未知的标签索引: " + functionOrder);
Main/System/HeroUI/HeroHeadBaseCell.cs
@@ -140,7 +140,10 @@
    public void Init(int heroID, int skinID, int star = 0, int awakelv = 0, int lv = 0, UnityAction onclick = null)
    {
        LoadPrefab();   //存在被卸载的可能,重新加载
        clickBtn.AddListener(onclick);
        if (onclick != null)
        {
            clickBtn.AddListener(onclick);
        }
        var heroConfig = HeroConfig.Get(heroID);
        qualityBG.SetSprite("heroheadBG" + heroConfig.Quality);
        // int skinID = 0;
@@ -186,7 +189,7 @@
        }
        countryImg.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
        lvText.text = lv == 0 ? "": Language.Get("L1094") + lv;
        lvText.text = lv == 0 ? "" : Language.Get("L1094") + lv;
        awakeLvRect.SetActive(awakelv > 0);
        awakeLvText.text = awakelv.ToString();
@@ -216,7 +219,7 @@
                cellContainer.transform.SetAsFirstSibling();
            }
        }
        //缩放到和父rect一样大
        var scale = 1f;
        var rect = cellContainer.GetComponent<RectTransform>();
@@ -227,10 +230,10 @@
            //外部控制了尺寸获取为0
            GridLayoutGroup grid = GetComponentInParent<GridLayoutGroup>();
            if (grid != null)
            {
            {
                width = grid.cellSize.x;
            }
        }
        scale = width / rect.sizeDelta.x;
        cellContainer.transform.localScale = cellContainer.transform.localScale * scale;
Main/System/Main/FightPowerManager.cs
@@ -318,8 +318,8 @@
        propertyVariables[HORSE_PER] = HorseManager.Instance.GetAttrPer(attrType) / 10000.0f;
        propertyVariables[BEAUTY_VALUE] = 0;
        propertyVariables[BEAUTY_PER] = 0;
        propertyVariables[FATES_VALUE] = 0;
        propertyVariables[FATES_PER] = 0;
        propertyVariables[FATES_VALUE] = HeroFatesManager.Instance.GetAttrValue(attrType);
        propertyVariables[FATES_PER] = HeroFatesManager.Instance.GetAttrPer(attrType) / 10000.0f;
        //全体卡牌加成
        propertyVariables[HERO_CARDPER] = allHeroAddPer;
Main/System/Redpoint/MainRedDot.cs
@@ -135,7 +135,8 @@
    public const int TianziBillboradRepoint = 471; //天子的考验
    public const int PhantasmPavilionRepoint = 472; //幻境阁
    public const int LineupRecommendRepoint = 473; //阵容推荐
     public const int FunctionPreviewRepoint = 474; //功能预告
    public const int FunctionPreviewRepoint = 474; //功能预告
    public const int HeroFatesRepoint = 475;//宿缘
    public void Register()
    {
Main/Utility/EnumHelper.cs
@@ -843,6 +843,7 @@
    OSHeroCall = 46, //开服武将召唤榜活动
    OSGala = 47, //开服盛典活动
    FunctionPreview = 48, //功能预览
    HeroFatesUpgrade = 49, //宿缘升级
}