using Animancer; 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; } ElementSkill s = null; 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 enumSkillLearnedState GetSkillLearnedState(int skillID) { CECSkill pSkill = CECGameRun.Instance.GetHostPlayer().GetNormalSkill(skillID); if (pSkill != null) { if (pSkill.GetSkillLevel() < pSkill.GetMaxLevel()) { return enumSkillLearnedState.SKILL_LEARNED; } else { return enumSkillLearnedState.SKILL_FULL; } } else { if (ElementSkill.IsOverridden((uint)skillID)) { return enumSkillLearnedState.SKILL_OVERRIDDEN; } else { return enumSkillLearnedState.SKILL_NOT_LEARNED; } } } 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 string GetSkillIcon(int skillID) { CECSkill skill = new CECSkill(skillID, 1); return (skill.GetIconFile()); } 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 enumSkillFitLevelState GetSkillFitLevel(int skillID) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return default; } int maxLevel = CECGameRun.Instance.GetHostPlayer().GetMaxLevelSofar(); int rank = CECGameRun.Instance.GetHostPlayer().GetBasicProps().iLevel2; int realmLevel = CECGameRun.Instance.GetHostPlayer().GetRealmLevel(); return GetSkillFitLevel(skillID, maxLevel, rank, realmLevel); } enumSkillFitLevelState GetSkillFitLevel(int skillID, int maxLevel, int rank, int realmLevel) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return default; } int skillLevel = 1; //½«ÒªÑ§Ï°µÄ¼¼Äܼ¶±ð CECSkill pSkill = CECGameRun.Instance.GetHostPlayer().GetNormalSkill(skillID); if (pSkill != null) { skillLevel = pSkill.GetSkillLevel() + 1; if (skillLevel > pSkill.GetMaxLevel()) { return enumSkillFitLevelState.SKILL_NOT_FIT_LEVEL; } } s = ElementSkill.Create((uint)skillID, skillLevel); if (s.GetRequiredLevel() > maxLevel) { return enumSkillFitLevelState.SKILL_NOT_FIT_LEVEL; } // ÐÞÕæµÈ¼¶²»¹» CECTaoistRank curTaoistRank = CECTaoistRank.GetTaoistRank(rank); CECTaoistRank reqTaoistRank = CECTaoistRank.GetTaoistRank(s.GetRank()); if ((curTaoistRank.IsEvilRank() && reqTaoistRank.IsGodRank()) || (curTaoistRank.IsGodRank() && reqTaoistRank.IsEvilRank()) || (curTaoistRank.GetID() < reqTaoistRank.GetID())) { return enumSkillFitLevelState.SKILL_NOT_FIT_LEVEL; } // ¾³½ç²»¹» if (s.GetRequiredRealmLevel() > realmLevel) { return enumSkillFitLevelState.SKILL_NOT_FIT_LEVEL; } return enumSkillFitLevelState.SKILL_FIT_LEVEL; } public int CheckLearnCondition(int skillID) { return CECGameRun.Instance.GetHostPlayer().CheckSkillLearnCondition(skillID, true); } public int GetRequiredBook(int skillID, int level) { int itemId = ElementSkill.GetRequiredBook((uint)skillID, level); return itemId; } 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); } } public int GetSkillCurrentLevel(int skillID) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return 0; } CECSkill pSkill = CECGameRun.Instance.GetHostPlayer().GetNormalSkill(skillID); if (pSkill != null) { return pSkill.GetSkillLevel(); } else { return 0; } } 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)) { BMLogger.LogError($"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); } } BMLogger.LogError("CECHostSkillModel::InitAllSkillsOfCurProf: total prof skills = " + m_allRankProfSkills.Count); // --- 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 bool CheckPreItem(int itemID) { return CECGameRun.Instance.GetHostPlayer().GetPack().FindItem(itemID) != -1; } public bool IsSkillServedByNPC(int skillID) { return m_curServiceSkills.Contains(skillID); } public int GetSkillSp(int skillID, int level) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return 0; } return ElementSkill.GetRequiredSp((uint)skillID, level); } public int GetSkillMoney(int skillID, int level) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return 0; } return ElementSkill.GetRequiredMoney((uint)skillID, level); } public Dictionary GetRequiredSkill(int skillID, int level) { Dictionary requiredSkill = new(); ElementSkill s = ElementSkill.Create((uint)skillID, level); // giả định GetRequiredSkill() trả về List> hoặc IReadOnlyList<...> Dictionary skills = s.GetRequiredSkill(); if (skills == null || skills.Count == 0) return null; foreach (var skill in skills) { if (skill.Key != 0) { requiredSkill.Add(skill.Key, skill.Value); } } return requiredSkill; } public bool CheckPreSkillLevel(int skillID, int level) { if (GetSkillLearnedState(skillID) == enumSkillLearnedState.SKILL_OVERRIDDEN) { // Èç¹û¸ÃǰÌá¼¼Äܱ»¸²¸Ç£¬ÔòǰÌá¼¼ÄÜÒ»¶¨Âú×ã return true; } return GetSkillCurrentLevel(skillID) >= level; } // Called when a skill is learned / 当学习新技能后调用 public void OnLearnSkill(int skillID, int skillLevel) { if (!m_allProfSkills.ContainsKey(skillID)) { return; } // 检查学习新技能后是否有技能被覆盖 / Check if any skills are overridden after learning the new skill bool newOverridden = false; var juniors = GetJunior(skillID); if (juniors.Count > 0) { newOverridden = true; } if (newOverridden) { CECSkillPanelChange change = new CECSkillPanelChange( CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_OVERRIDDEN, skillID, skillLevel); EventBus.Publish(change); } else { CECSkillPanelChange change = new CECSkillPanelChange( CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_LEVEL_UP, skillID, skillLevel); EventBus.Publish(change); } } } 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 struct CECSkillPanelChange { 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; } } }