using CSNetwork; using CSNetwork.GPDataType; using ModelRenderer.Scripts.GameData; using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace BrewMonster.Scripts.Skills { public class CECHostSkillModel { private static CECHostSkillModel instance; public static CECHostSkillModel Instance { get { if (instance == null) { instance = new CECHostSkillModel(); } return instance; } set => instance = value; } Dictionary m_allProfSkills = new Dictionary(); Dictionary> m_allRankProfSkills = new Dictionary>(); private HashSet m_allProfNPCs = new HashSet(); private Dictionary m_treeHeightMap = new Dictionary(); private Dictionary m_evilRootMap = new Dictionary(); private Dictionary m_godRootMap = new Dictionary(); private Dictionary m_baseRootMap = new Dictionary(); private int m_skillLearnNPCNID; 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() { // Çå¿ÕËùÓм¼ÄÜ£¬·ÀÖ¹ÒòΪ¶à¸ö½ÇÉ«µÇ¼µ¼ÖÂÖØ¸´¼ÓÔØ¼¼ÄÜ Release(); InitAllSkillsOfCurProf(); FindAllNPCsOfCurProf(); HashSet rootSkills = GetRootSkillSet(); InitSkillTreeHeightMap(rootSkills); InitSkillTreeRootMap(rootSkills); m_bInitialized = true; // ÖØÐ´¦ÀíNPCLIST ProcessServiceList(); } public void ProcessServiceList() { if (m_npcListData == null) { BMLogger.LogWarning("CECHostSkillModel::ProcessServiceList, m_npcListData is null."); return; } if (m_npcListData.Size > 0) { byte[] data = m_npcListData.RawBuffer; int headerSize = Marshal.SizeOf(typeof(cmd_header)); int offset = headerSize; int bodySize = m_npcListData.Size - offset; byte[] bodyBytes = new byte[bodySize]; Buffer.BlockCopy(data, offset, bodyBytes, 0, bodySize); cmd_scene_service_npc_list npcList = default; npcList.count = GPDataTypeHelper.FromBytes(bodyBytes); offset = sizeof(uint); int NpcEntrySize = Marshal.SizeOf(); npcList.list = new cmd_scene_service_npc_list.NpcEntry[npcList.count]; for (int z = 0; z < npcList.count; z++) { npcList.list[z] = GPDataTypeHelper.FromBytes(bodyBytes, offset); offset += NpcEntrySize; } int i; for (i = 0; i < npcList.count; i++) { int tid = npcList.list[i].tid; if (m_allProfNPCs.Contains(tid)) { if (m_skillLearnNPCNID != npcList.list[i].nid) { m_skillLearnNPCNID = npcList.list[i].nid; SetCurServiceSkills(tid); var change = new CECSkillPanelChange(CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC, 0, 0); //NotifyObservers(change); break; } } } if (i == npcList.count && m_skillLearnNPCNID != 0) { m_skillLearnNPCNID = 0; SetCurServiceSkills(0); var change = new CECSkillPanelChange(CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC, 0, 0); //NotifyObservers(change); } m_npcListData.Clear(); } } private readonly HashSet m_curServiceSkills = new HashSet(); public void SetCurServiceSkills(int tid) { m_curServiceSkills.Clear(); if (tid == 0) return; var pDB = ElementDataManProvider.GetElementDataMan(); // Read NPC_ESSENCE from element data DATA_TYPE dt = default; var dataprt = pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (dataprt == null) return; var npcEssence = (NPC_ESSENCE)dataprt; // Get skill service block from id_skill_service var dataprt2 = pDB.get_data_ptr(npcEssence.id_skill_service, ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (dataprt2 == null) return; var skillService = (NPC_SKILL_SERVICE)dataprt2; // Copy all non-zero skill ids into m_curServiceSkills foreach (int skillId in skillService.id_skills) { if (skillId != 0) m_curServiceSkills.Add(skillId); } } public void RecvNPCServiceList(Octets Data) { m_npcListData = Data; if (!m_bInitialized) { return; } else { ProcessServiceList(); } } private void InitSkillTreeRootMap(IEnumerable rootSkills) { foreach (int rootSkillID in rootSkills) { InitializeRootOfSkillTree(rootSkillID); } } private void InitializeRootOfSkillTree(int rootSkillID) { var skillRootMap = GetSkillRootMap(rootSkillID); var toTravelSkills = new Queue(); toTravelSkills.Enqueue(rootSkillID); while (toTravelSkills.Count > 0) { int skillID = toTravelSkills.Dequeue(); var juniors = GetJunior(skillID); foreach (var (id, level) in juniors) { int juniorSkillID = (int)id; skillRootMap[juniorSkillID] = rootSkillID; toTravelSkills.Enqueue(juniorSkillID); } } } private Dictionary GetSkillRootMap(int rootSkillID) { var skill = m_allProfSkills[rootSkillID]; var taoistRank = CECTaoistRank.GetTaoistRank(skill.GetRank()); if (taoistRank.IsEvilRank()) return m_evilRootMap; else if (taoistRank.IsGodRank()) return m_godRootMap; else return m_baseRootMap; } private void InitSkillTreeHeightMap(IEnumerable rootSkills) { foreach (var rootSkillID in rootSkills) { m_treeHeightMap[rootSkillID] = GetSkillTreeHeight(rootSkillID); } } private int GetSkillTreeHeight(int rootSkillID) { var juniors = GetJunior(rootSkillID); int maxHeight = 0; for (int i = 0; i < juniors.Count; i++) { int subHeight = GetSkillTreeHeight((int)juniors[i].id); if (subHeight > maxHeight) { maxHeight = subHeight; } } return 1 + maxHeight; } private List<(uint id, int level)> GetJunior(int skillID) { if (!m_allProfSkills.TryGetValue(skillID, out var skill)) { throw new Exception($"Skill {skillID} not found in m_allProfSkills"); } var juniors = skill.GetJunior(); var ret = new List<(uint id, int level)>(); foreach (var (id, level) in juniors) { if (id != 0) ret.Add((id, level)); } return ret; } private HashSet GetRootSkillSet() { var rootSkills = new HashSet(); foreach (var kvp in m_allProfSkills) if (kvp.Value.GetJunior().Count != 0) rootSkills.Add(kvp.Key); foreach (var kvp in m_allProfSkills) foreach (var (id, level) in kvp.Value.GetJunior()) rootSkills.Remove((int)id); return rootSkills; } private void FindAllNPCsOfCurProf() { DATA_TYPE dt = DATA_TYPE.DT_NPC_ESSENCE; elementdataman pDB = ElementDataManProvider.GetElementDataMan(); var map = pDB.GetAllDataTypeWithType(ID_SPACE.ID_SPACE_ESSENCE, dt); foreach (var obj in map) { NPC_ESSENCE npcEssence = (NPC_ESSENCE)obj; if (npcEssence.id_skill_service != 0 && (npcEssence.combined_switch & (uint)NPC_COMBINED_SWITCH.NCS_IGNORE_DISTANCE_CHECK) != 0) { NPC_SKILL_SERVICE skillService = (NPC_SKILL_SERVICE)pDB.get_data_ptr(npcEssence.id_skill_service, ID_SPACE.ID_SPACE_ESSENCE, ref dt); bool profCorrect = false; for (int i = 0; i < skillService.id_skills.Length; i++) { if (skillService.id_skills[i] != 0) { ElementSkill pSkill = ElementSkill.Create(skillService.id_skills[i], 1); if (pSkill == null) return; if (pSkill.GetCls() == CECGameRun.Instance.GetHostPlayer().GetProfession()) { // ��NPC������ǰְҵ���ܣ���Ҫ��¼��NPC��ID profCorrect = true; break; } } } if (profCorrect) { m_allProfNPCs.Add((int)npcEssence.id); } } } } public void InitAllSkillsOfCurProf() { // --- B1: Thu thập toàn bộ skill từ các NPC có cung cấp dịch vụ học skill --- HashSet npcSkills = new HashSet(); { elementdataman pDB = ElementDataManProvider.GetElementDataMan(); DATA_TYPE dt = DATA_TYPE.DT_NPC_ESSENCE; // uint id = pDB.get_id_with_data_type(ID_SPACE.ID_SPACE_ESSENCE, dt); var map = pDB.GetAllDataTypeWithType(ID_SPACE.ID_SPACE_ESSENCE, dt); foreach (var obj in map) { NPC_ESSENCE npcEssence = (NPC_ESSENCE)obj; if (npcEssence.id_skill_service != 0 && (npcEssence.combined_switch & (uint)NPC_COMBINED_SWITCH.NCS_IGNORE_DISTANCE_CHECK) != 0) { NPC_SKILL_SERVICE skillService = (NPC_SKILL_SERVICE)pDB.get_data_ptr( npcEssence.id_skill_service, ID_SPACE.ID_SPACE_ESSENCE, ref dt ); for (int i = 0; i < skillService.id_skills.Length; i++) { uint skillId = skillService.id_skills[i]; if (skillId != 0 && CECComboSkillState.Instance.GetInherentSkillByID(skillId) == null) { npcSkills.Add(skillId); } } } } } // --- B2: Duyệt tất cả skill, lọc skill theo class hiện tại của người chơi --- uint curID = 0; while ((curID = ElementSkill.NextSkill(curID)) != 0) { ElementSkill pSkill = ElementSkill.Create(curID, 1); int cls = pSkill.GetCls(); int playerCls = CECGameRun.Instance.GetHostPlayer().GetProfession(); bool isSameClass = (cls == playerCls || cls == 255); bool isProvidedByNPC = npcSkills.Contains(curID); if (isSameClass && isProvidedByNPC) { m_allProfSkills[(int)curID] = pSkill; if (!m_allRankProfSkills.ContainsKey(pSkill.GetRank())) m_allRankProfSkills[pSkill.GetRank()] = new List(); m_allRankProfSkills[pSkill.GetRank()].Add((int)curID); } } // --- B3: Sắp xếp skill trong từng rank theo thứ tự hiển thị --- foreach (var kvp in m_allRankProfSkills) { kvp.Value.Sort((lhs, rhs) => { var lSkill = ElementSkill.Create((uint)lhs, 1); var rSkill = ElementSkill.Create((uint)rhs, 1); bool result; if (lSkill.GetType() == (byte)skill_type.TYPE_PASSIVE && rSkill.GetType() != (byte)skill_type.TYPE_PASSIVE) { result = false; } else if (lSkill.GetType() != (byte)skill_type.TYPE_PASSIVE && rSkill.GetType() == (byte)skill_type.TYPE_PASSIVE) { result = true; } else { result = lSkill.GetShowOrder() < rSkill.GetShowOrder(); } return result ? -1 : 1; }); } } private void Release() { m_allProfSkills.Clear(); // Dọn sạch tất cả dictionary / map m_allProfSkills.Clear(); m_allRankProfSkills.Clear(); m_evilRootMap.Clear(); m_godRootMap.Clear(); m_baseRootMap.Clear(); m_treeHeightMap.Clear(); m_allProfNPCs.Clear(); m_curServiceSkills.Clear(); m_skillLearnNPCNID = 0; m_bInitialized = false; } public string GetSkillName(int skillID) { CECSkill skill = new CECSkill(skillID, 1); return (skill.GetNameDisplay()); } } public enum enumSkillFitLevelState { SKILL_FIT_LEVEL, // ��������ȼ������桢�������� / Skill meets level, cultivation, realm requirements SKILL_NOT_FIT_LEVEL, // ���ܲ�����ȼ������桢�������� / Skill does not meet level, cultivation, realm requirements } public enum enumSkillLearnedState { SKILL_NOT_LEARNED, // ����δѧϰ / Skill not learned SKILL_LEARNED, // ������ѧϰ���������� / Skill learned but not at max level SKILL_FULL, // ���������� / Skill at max level SKILL_OVERRIDDEN, // �����ѱ����� / Skill has been overridden } public enum enumEvilGod { SKILL_BASE, // ��ͨ���� / Normal skill SKILL_EVIL, // �ɼ��� / Immortal skill SKILL_GOD, // ħ���� / Demonic skill } public class CECSkillPanelChange : CECObservableChange { public enum enumChangeMask { CHANGE_SKILL_LEVEL_UP, // Skill level up CHANGE_SKILL_OVERRIDDEN, // Skill overridden CHANGE_SKILL_NPC // NPC providing skill changed } public enumChangeMask m_changeMask; public int m_skillID; public int m_skillLevel; public CECSkillPanelChange(enumChangeMask mask, int id, int level) { m_changeMask = mask; m_skillID = id; m_skillLevel = level; } } }