diff --git a/Assets/PerfectWorld/Scripts/Managers/CECManager.cs b/Assets/PerfectWorld/Scripts/Managers/CECManager.cs
index d2c0fa11b0..bd10413956 100644
--- a/Assets/PerfectWorld/Scripts/Managers/CECManager.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/CECManager.cs
@@ -123,6 +123,8 @@ namespace BrewMonster.Managers
+
+
diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs
index 8137d2935b..8d862ae804 100644
--- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs
+++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs
@@ -1,4 +1,5 @@
+using System.Runtime.InteropServices;
using UnityEngine;
namespace CSNetwork.C2SCommand
diff --git a/Assets/PerfectWorld/Scripts/Skills/EC_HostSkillModel.cs b/Assets/PerfectWorld/Scripts/Skills/EC_HostSkillModel.cs
index 4a3fd88d91..233dd99e62 100644
--- a/Assets/PerfectWorld/Scripts/Skills/EC_HostSkillModel.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/EC_HostSkillModel.cs
@@ -34,6 +34,14 @@ namespace BrewMonster.Scripts.Skills
private bool m_bInitialized;
private Octets m_npcListData;
+ ///
+ /// 获取当前职业的全部技能映射(阶位 -> 技能列表) / Get rank-to-skills map for current profession.
+ ///
+ public IReadOnlyDictionary> GetAllRankProfSkills()
+ {
+ return m_allRankProfSkills;
+ }
+
public void Initialize()
{
// Çå¿ÕËùÓм¼ÄÜ£¬·ÀÖ¹ÒòΪ¶à¸ö½ÇÉ«µÇ¼µ¼ÖÂÖØ¸´¼ÓÔØ¼¼ÄÜ
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill177.cs b/Assets/PerfectWorld/Scripts/Skills/skill177.cs
index 93bab0e02e..bfa2b8ec42 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill177.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill177.cs
@@ -233,3 +233,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill178.cs b/Assets/PerfectWorld/Scripts/Skills/skill178.cs
index b4e747f6f9..190223f253 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill178.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill178.cs
@@ -233,3 +233,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill54.cs b/Assets/PerfectWorld/Scripts/Skills/skill54.cs
index 1cd866e88a..195c7cab7a 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill54.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill54.cs
@@ -330,3 +330,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill56.cs b/Assets/PerfectWorld/Scripts/Skills/skill56.cs
index 9a1f4ca568..b2c127e24a 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill56.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill56.cs
@@ -324,3 +324,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill57.cs b/Assets/PerfectWorld/Scripts/Skills/skill57.cs
index da9ca49426..7e30059c6c 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill57.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill57.cs
@@ -335,3 +335,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill58.cs b/Assets/PerfectWorld/Scripts/Skills/skill58.cs
index 5009a7cc28..a4c776189e 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill58.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill58.cs
@@ -328,3 +328,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/Skills/skill76.cs b/Assets/PerfectWorld/Scripts/Skills/skill76.cs
index be2d1cd204..6351f404d4 100644
--- a/Assets/PerfectWorld/Scripts/Skills/skill76.cs
+++ b/Assets/PerfectWorld/Scripts/Skills/skill76.cs
@@ -238,3 +238,5 @@ namespace BrewMonster
+
+
diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubList.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubList.cs
new file mode 100644
index 0000000000..a099392dd4
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubList.cs
@@ -0,0 +1,621 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BrewMonster;
+using BrewMonster.Scripts.Skills;
+using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace BrewMonster.UI
+{
+ [DisallowMultipleComponent]
+ public class CDlgSkillSubList : AUIDialog
+ {
+ [Header("Templates")]
+ [SerializeField] private AUISubDialog m_pSubRank;
+ [SerializeField] private AUISubDialog m_pSubSkill;
+
+ [Header("Layout")]
+ [SerializeField] private RectTransform m_contentRoot;
+ [SerializeField] private ScrollRect m_scrollRect;
+ [SerializeField] private float m_windowScale = 1f;
+
+ [Header("State")]
+ [SerializeField] private bool m_isEvil;
+
+ private readonly Dictionary m_rankSubDialogs = new();
+ private readonly List m_skillSubDialogs = new();
+ private readonly Dictionary m_skillSubDialogsMap = new();
+
+ private int m_skillSubCount; // 当前显示的技能数量 / Current shown skill count
+ private float m_curBottom; // 当前底部位置 / Current bottom position
+ private float m_skillHeight; // 技能子对话高度 / Skill sub dialog height
+ private float m_rankHeight; // 阶位对话高度 / Rank dialog height
+ private float m_originWidth; // 初始宽度 / Initial width
+ private float m_originHeight; // 初始高度 / Initial height
+ private float m_originBottom; // 初始底部位置 / Initial bottom position
+ private bool m_bAllocRankDlgs; // 是否已创建阶位子对话 / Whether rank sub dialogs are allocated
+
+ private int m_selectedSkillId;
+
+ private void Awake()
+ {
+ if (m_contentRoot == null)
+ {
+ m_contentRoot = transform as RectTransform;
+ }
+
+ CacheTemplateInfo();
+ HideTemplates();
+ }
+
+ public override void OnEnable()
+ {
+ base.OnEnable();
+ OnShowDialog();
+ }
+
+ private void OnShowDialog()
+ {
+ InitRankDlgs();
+ ResetDialog();
+
+ // 保留原始隐藏新图标逻辑 / Keep original "hide new" logic (pending port)
+ // GetGameUIMan()->m_pDlgSystem->ShowNewImg(false);
+ // GetGameUIMan()->m_pDlgSystemb->ShowNewImg(false);
+ // GetGameUIMan()->m_pDlgSystem5->ShowNewImg(false);
+ // GetGameUIMan()->m_pDlgSystem5b->ShowNewImg(false);
+ }
+
+ // 初始化模板尺寸 / Cache template sizing info
+ private void CacheTemplateInfo()
+ {
+ if (m_pSubRank != null)
+ {
+ m_rankHeight = m_pSubRank.GetSize().y;
+ m_originBottom = m_pSubRank.GetPos().y;
+ }
+
+ if (m_pSubSkill != null)
+ {
+ m_skillHeight = m_pSubSkill.GetSize().y;
+ }
+
+ if (m_contentRoot != null)
+ {
+ m_originWidth = m_contentRoot.rect.width;
+ m_originHeight = m_contentRoot.rect.height;
+ }
+ }
+
+ private void HideTemplates()
+ {
+ if (m_pSubRank != null)
+ {
+ m_pSubRank.Show(false);
+ }
+
+ if (m_pSubSkill != null)
+ {
+ m_pSubSkill.Show(false);
+ }
+ }
+
+ // 初始化所有阶位子对话框 / Initialize all rank sub dialogs
+ private void InitRankDlgs()
+ {
+ if (m_bAllocRankDlgs || m_pSubRank == null || m_contentRoot == null)
+ {
+ return;
+ }
+ m_bAllocRankDlgs = true;
+
+ foreach (int rankId in EnumerateAllRankIds())
+ {
+ CreateOneRankDlg(rankId);
+ }
+ }
+
+ // ��ʼ�����������ڶԻ���һ���Է�����ڴ� / Initialize rank sub-dialogs once to avoid realloc
+ private void CreateOneRankDlg(int rankID)
+ {
+ AUISubDialog pSubRank = Instantiate(m_pSubRank, m_contentRoot);
+ pSubRank.SetName($"{m_pSubRank.GetName()}{rankID}");
+ pSubRank.Show(false);
+
+ m_rankSubDialogs[rankID] = pSubRank;
+ }
+
+ // ���¶������Ի�����в��� / Reset dialog with all rank/skill sub dialogs
+ public void ResetDialog()
+ {
+ m_skillSubDialogsMap.Clear();
+ m_skillSubCount = 0;
+ m_curBottom = m_originBottom;
+
+ foreach (var rank in m_rankSubDialogs.Values)
+ {
+ rank.Show(false);
+ }
+
+ foreach (var skill in m_skillSubDialogs)
+ {
+ skill.Show(false);
+ }
+
+ IReadOnlyDictionary> allRankProfSkills = CECHostSkillModel.Instance?.GetAllRankProfSkills();
+
+ foreach (int rankId in EnumerateBaseRankIds())
+ {
+ AddDlgsOfOneRank(rankId, allRankProfSkills);
+ }
+
+ foreach (int rankId in EnumerateGodRankIds())
+ {
+ AddDlgsOfOneRank(rankId, allRankProfSkills);
+ }
+
+ foreach (int rankId in EnumerateEvilRankIds())
+ {
+ AddDlgsOfOneRank(rankId, allRankProfSkills);
+ }
+
+ if (m_contentRoot != null)
+ {
+ FitSize();
+ ShowLastSelectedSkill();
+ }
+ }
+
+ // ��鵯��ħ״̬ / Reset when switching between god/evil
+ public void ResetGodEvil()
+ {
+ if (isActiveAndEnabled)
+ {
+ ResetDialog();
+ }
+ }
+
+ // ��ijһ����������״̬ / Refresh a single skill sub dialog
+ private void UpdateOneSubDlg(int skillID)
+ {
+ if (!m_skillSubDialogsMap.TryGetValue(skillID, out var pSub))
+ {
+ return;
+ }
+
+ CDlgSkillSubListItem subListItem = pSub.GetComponent();
+ subListItem?.UpdateSkill(skillID);
+
+ if (GetSelectedSkillID() == skillID)
+ {
+ // 选中时可在此扩展树状展示 / Hook skill tree here if needed
+ }
+ }
+
+ // ����һ�����漶��Ի��� / Show a rank sub dialog
+ private void AddRankSubDig(int rankID)
+ {
+ if (!m_rankSubDialogs.TryGetValue(rankID, out var pSub))
+ {
+ return;
+ }
+
+ pSub.Show(true);
+ pSub.SetPos(0f, m_curBottom);
+ m_curBottom += m_rankHeight * m_windowScale;
+
+ TextMeshProUGUI label = pSub.GetComponentInChildren(true);
+ if (label != null)
+ {
+ label.text = GetRankName(rankID);
+ }
+ }
+
+ // ����һ�����ܶԻ����øú����������UpdateOneSubDlg / Add a skill sub dialog then update it
+ private void AddSkillSubDlg(int skillID)
+ {
+ if (m_pSubSkill == null || m_contentRoot == null)
+ {
+ return;
+ }
+
+ if (m_skillSubCount >= m_skillSubDialogs.Count)
+ {
+ AUISubDialog pSubSkill = Instantiate(m_pSubSkill, m_contentRoot);
+ pSubSkill.SetName($"{m_pSubSkill.GetName()}{m_skillSubDialogs.Count}");
+ m_skillSubDialogs.Add(pSubSkill);
+ }
+
+ AUISubDialog curSubSkill = m_skillSubDialogs[m_skillSubCount];
+ CDlgSkillSubListItem item = curSubSkill.GetComponent();
+ item?.SetHighlight(false);
+
+ curSubSkill.SetPos(0f, m_curBottom);
+ curSubSkill.Show(true);
+ curSubSkill.SetData(Mathf.RoundToInt(m_curBottom));
+ m_curBottom += m_skillHeight * m_windowScale;
+
+ m_skillSubDialogsMap[skillID] = curSubSkill;
+ m_skillSubCount++;
+ UpdateOneSubDlg(skillID);
+ }
+
+ // ��ijһ�����漶���Ӧ�����м���������Ի��� / Add dialogs for one rank
+ private void AddDlgsOfOneRank(int rankID, IReadOnlyDictionary> allRankProfSkills)
+ {
+ if (allRankProfSkills == null)
+ {
+ return;
+ }
+
+ if (IsEvil() && IsGodRank(rankID))
+ {
+ return;
+ }
+ else if (!IsEvil() && IsEvilRank(rankID))
+ {
+ return;
+ }
+
+ if (!allRankProfSkills.TryGetValue(rankID, out var rankSkills) || rankSkills == null || rankSkills.Count == 0)
+ {
+ return;
+ }
+
+ AddRankSubDig(rankID);
+ foreach (int skillID in rankSkills)
+ {
+ AddSkillSubDlg(skillID);
+ }
+ }
+
+ private string GetRankName(int rankID)
+ {
+ // 尝试使用阶位名字,否则使用 ID / Use rank name if available, otherwise ID
+ // TODO: wire into localization table once available
+ return $"Rank {rankID}";
+ }
+
+ private static IEnumerable EnumerateAllRankIds()
+ {
+ return CECTaoistRank.TaoistRankIDs;
+ }
+
+ private static IEnumerable EnumerateBaseRankIds()
+ {
+ return CECTaoistRank.TaoistRankIDs.Take((int)ToaistRank.BaseRankCount);
+ }
+
+ private static IEnumerable EnumerateGodRankIds()
+ {
+ return CECTaoistRank.TaoistRankIDs.Skip((int)ToaistRank.BaseRankCount)
+ .Take((int)ToaistRank.GodRankCount);
+ }
+
+ private static IEnumerable EnumerateEvilRankIds()
+ {
+ return CECTaoistRank.TaoistRankIDs.Skip((int)ToaistRank.BaseRankCount + (int)ToaistRank.GodRankCount);
+ }
+
+ // ����������洢��λ�õ���ȡֵ / Scroll helpers use the stored positions
+ public void ScrollToShowSelectedSkill()
+ {
+ if (m_scrollRect == null || GetSelectedSkillID() == 0)
+ {
+ return;
+ }
+
+ if (!m_skillSubDialogsMap.TryGetValue(GetSelectedSkillID(), out var sub))
+ {
+ return;
+ }
+
+ float subTop = sub.GetPos().y;
+ float subBottom = subTop + sub.GetSize().y;
+ float expandedHeight = m_curBottom;
+ float viewportHeight = m_scrollRect.viewport != null ? m_scrollRect.viewport.rect.height : expandedHeight;
+
+ if (viewportHeight >= expandedHeight || expandedHeight <= 0f)
+ {
+ return;
+ }
+
+ float mid = (subTop + subBottom) * 0.5f;
+ float target = Mathf.Clamp01(mid / expandedHeight);
+ m_scrollRect.verticalNormalizedPosition = 1f - target;
+ }
+
+ // �����Ľ��� / Prepare for layout change
+ public bool OnChangeLayoutBegin()
+ {
+ foreach (var kv in m_rankSubDialogs)
+ {
+ Destroy(kv.Value.gameObject);
+ }
+ m_rankSubDialogs.Clear();
+
+ foreach (var dlg in m_skillSubDialogs)
+ {
+ Destroy(dlg.gameObject);
+ }
+ m_skillSubDialogs.Clear();
+
+ m_skillSubDialogsMap.Clear();
+ m_skillSubCount = 0;
+ m_curBottom = 0f;
+
+ return true;
+ }
+
+ // 布局变更结束时 / After layout change
+ public void OnChangeLayoutEnd(bool bAllDone)
+ {
+ if (isActiveAndEnabled)
+ {
+ InitRankDlgs();
+ FitSize();
+ ShowLastSelectedSkill();
+ }
+ }
+
+ private void FitSize()
+ {
+ if (m_contentRoot == null)
+ {
+ return;
+ }
+
+ float height = Mathf.Max(m_curBottom, m_originHeight);
+ m_contentRoot.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
+ }
+
+ private void ShowLastSelectedSkill()
+ {
+ int originSelected = GetSelectedSkillID();
+ SetSelectedSkillID(0);
+ SelectSkill(originSelected);
+ ScrollToShowSelectedSkill();
+ }
+
+ public void SelectSkill(int skillID)
+ {
+ if (skillID != 0 && !m_skillSubDialogsMap.ContainsKey(skillID))
+ {
+ skillID = ConvertSkillID(skillID);
+ }
+
+ if (skillID != 0 && !m_skillSubDialogsMap.ContainsKey(skillID))
+ {
+ SetSelectedSkillID(0);
+ return;
+ }
+
+ int originSelectedSkillID = GetSelectedSkillID();
+ if (originSelectedSkillID != 0 && m_skillSubDialogsMap.TryGetValue(originSelectedSkillID, out var originSub))
+ {
+ originSub.GetComponent()?.SetHighlight(false);
+ }
+
+ if (skillID != 0 && m_skillSubDialogsMap.TryGetValue(skillID, out var newSub))
+ {
+ newSub.GetComponent()?.SetHighlight(true);
+ }
+
+ SetSelectedSkillID(skillID);
+ }
+
+ public void EnableUpgrade(bool bEnable)
+ {
+ foreach (var kv in m_skillSubDialogsMap)
+ {
+ kv.Value.GetComponent()?.EnableUpgrade(bEnable);
+ }
+ }
+
+ public int GetSelectedSkillID()
+ {
+ return m_selectedSkillId;
+ }
+
+ public void SetSelectedSkillID(int skillID)
+ {
+ m_selectedSkillId = skillID;
+ }
+
+ public bool IsEvil()
+ {
+ return m_isEvil;
+ }
+
+ public void SetEvil(bool value)
+ {
+ if (m_isEvil != value)
+ {
+ m_isEvil = value;
+ ResetGodEvil();
+ }
+ }
+
+ // ���ܱ������Ӧ�����Ƿ�����б��� / Check whether skill or its converted counterpart exists
+ public bool IsSkillOrConvertSkillExist(int skillID)
+ {
+ if (m_skillSubDialogsMap.ContainsKey(skillID))
+ {
+ return true;
+ }
+
+ int convertSkillID = ConvertSkillID(skillID);
+ return convertSkillID != 0 && m_skillSubDialogsMap.ContainsKey(convertSkillID);
+ }
+
+ private int ConvertSkillID(int skillID)
+ {
+ // 预留转换逻辑 / Placeholder for conversion logic
+ return skillID;
+ }
+
+ // �м��ܱ������ˣ���Ҫ������������ / Handle model change notifications
+ public void OnModelChange(CECHostSkillModel p, CECSkillPanelChange q)
+ {
+ if (q == null)
+ {
+ return;
+ }
+
+ if (q.m_changeMask == CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_OVERRIDDEN)
+ {
+ ResetDialog();
+ }
+ else if (q.m_changeMask == CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_LEVEL_UP)
+ {
+ if (q.m_skillLevel == 1)
+ {
+ foreach (var kv in m_skillSubDialogsMap)
+ {
+ UpdateOneSubDlg(kv.Key);
+ }
+ }
+ else
+ {
+ UpdateOneSubDlg(q.m_skillID);
+ }
+ }
+ else if (q.m_changeMask == CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC)
+ {
+ // NPC变化时原逻辑隐藏技能树 / Original logic hides skill tree when NPC changes
+ }
+ }
+ }
+
+ [DisallowMultipleComponent]
+ public class AUISubDialog : MonoBehaviour
+ {
+ [SerializeField] private AUIDialog m_subDialog;
+ [SerializeField] private RectTransform m_rectTransform;
+ private int m_data;
+
+ private void Reset()
+ {
+ if (m_rectTransform == null)
+ {
+ m_rectTransform = transform as RectTransform;
+ }
+ if (m_subDialog == null)
+ {
+ m_subDialog = GetComponent();
+ }
+ }
+
+ public void SetDialog(AUIDialog dialog)
+ {
+ m_subDialog = dialog;
+ }
+
+ public AUIDialog GetSubDialog()
+ {
+ return m_subDialog;
+ }
+
+ public string GetName()
+ {
+ return name;
+ }
+
+ public void SetName(string newName)
+ {
+ name = newName;
+ }
+
+ public Vector2Int GetSize()
+ {
+ if (m_rectTransform == null)
+ {
+ m_rectTransform = transform as RectTransform;
+ }
+
+ return m_rectTransform != null
+ ? Vector2Int.RoundToInt(m_rectTransform.rect.size)
+ : Vector2Int.zero;
+ }
+
+ public Vector2 GetPos()
+ {
+ if (m_rectTransform == null)
+ {
+ m_rectTransform = transform as RectTransform;
+ }
+
+ return m_rectTransform != null ? m_rectTransform.anchoredPosition : Vector2.zero;
+ }
+
+ public void SetPos(float x, float y)
+ {
+ if (m_rectTransform == null)
+ {
+ m_rectTransform = transform as RectTransform;
+ }
+
+ if (m_rectTransform != null)
+ {
+ m_rectTransform.anchoredPosition = new Vector2(x, y);
+ }
+ }
+
+ public void Show(bool value)
+ {
+ gameObject.SetActive(value);
+ }
+
+ public void SetData(int data)
+ {
+ m_data = data;
+ }
+
+ public int GetData()
+ {
+ return m_data;
+ }
+ }
+
+ [DisallowMultipleComponent]
+ public class CDlgSkillSubListItem : MonoBehaviour
+ {
+ [SerializeField] private TextMeshProUGUI m_nameText;
+ [SerializeField] private GameObject m_highlight;
+
+ private int m_skillID;
+
+ public void UpdateSkill(int skillID)
+ {
+ m_skillID = skillID;
+ if (m_nameText != null)
+ {
+ m_nameText.text = CECHostSkillModel.Instance?.GetSkillName(skillID) ?? $"Skill {skillID}";
+ }
+ }
+
+ public void SetHighlight(bool bHighlight)
+ {
+ if (m_highlight != null)
+ {
+ m_highlight.SetActive(bHighlight);
+ }
+ }
+
+ public void EnableUpgrade(bool bEnable)
+ {
+ Button btn = GetComponentInChildren