using Animancer; using BrewMonster; using CSNetwork; using CSNetwork.GPDataType; using ModelRenderer.Scripts.GameData; using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using UnityEngine; 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_bReceivedNPCGreeting; private bool m_bInitialized; private Octets m_npcListData; #if UNITY_EDITOR [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void BeforeSceneLoad() { Instance = null; } #endif public IReadOnlyDictionary> GetAllRankProfSkills() { return m_allRankProfSkills; } /// /// Same skill ID set as — walks base/god/evil ranks, /// applies god/evil path filter and skips overridden skills. /// 与 CDlgSkillSubList 相同的技能 ID 集合。 /// public List CollectSkillSubListSkillIds(bool isEvil) { var result = new List(); var seen = new HashSet(); void CollectRankRange(CECTaoistRank begin, CECTaoistRank end) { for (CECTaoistRank taoistRank = begin; taoistRank != end; taoistRank = taoistRank.GetNext()) { if (isEvil && taoistRank.IsGodRank()) continue; if (!isEvil && taoistRank.IsEvilRank()) continue; int rankId = taoistRank.GetID(); if (!m_allRankProfSkills.TryGetValue(rankId, out List rankSkills) || rankSkills == null || rankSkills.Count == 0) { continue; } foreach (int skillId in rankSkills) { if (ElementSkill.IsOverridden((uint)skillId)) continue; if (seen.Add(skillId)) result.Add(skillId); } } } CollectRankRange(CECTaoistRank.GetBaseRankBegin(), CECTaoistRank.GetBaseRankEnd()); CollectRankRange(CECTaoistRank.GetGodRankBegin(), CECTaoistRank.GetGodRankEnd()); CollectRankRange(CECTaoistRank.GetEvilRankBegin(), CECTaoistRank.GetEvilRankEnd()); result.Sort(); return result; } public CECHostSkillModel() { m_allProfSkills = new Dictionary(); m_skillLearnNPCNID = 0; m_bReceivedNPCGreeting = false; m_bInitialized = false; } // NPC技能学习相关 / Skill-learn NPC helpers public bool IsSkillLearnNPC(int nid) => nid == m_skillLearnNPCNID; public bool IsSkillLearnNPCExsit() => m_skillLearnNPCNID != 0; public bool IsReceivedNPCGreeting() => m_bReceivedNPCGreeting; public void SetReceivedNPCGreeting(bool received) => m_bReceivedNPCGreeting = received; public void OnNpcGreeting(int idObject) { // cmd_npc_greeting.idObject is the NPC/player id (nid) if (idObject == m_skillLearnNPCNID && m_skillLearnNPCNID != 0) { m_bReceivedNPCGreeting = true; //BMLogger.LogError($"[Skill] Received NPC_GREETING from skill-learn NPC nid={m_skillLearnNPCNID}"); } } public void SendHelloToSkillLearnNPC() { if (m_skillLearnNPCNID != 0) { // C++: g_pGame->GetGameSession()->c2s_CmdNPCSevHello(m_skillLearnNPCNID); Network.UnityGameSession.c2s_CmdNPCSevHello(m_skillLearnNPCNID); //BMLogger.LogError($"[Skill] Sent SEVNPC_HELLO to skill-learn NPC nid={m_skillLearnNPCNID}"); } } public enumEvilGod GetSkillEvilGod(int skillID) { CECSkill skill = new CECSkill(skillID, 1); int rank = skill.GetRank(); CECTaoistRank taoistRank = CECTaoistRank.GetTaoistRank(rank); if (taoistRank.IsGodRank()) { return enumEvilGod.SKILL_GOD; } else if (taoistRank.IsEvilRank()) { return enumEvilGod.SKILL_EVIL; } else { return enumEvilGod.SKILL_BASE; } } static CECHostPlayer TryGetHostPlayer() => CECGameRun.Instance?.GetHostPlayer(); public enumSkillLearnedState GetSkillLearnedState(int skillID) { CECHostPlayer host = TryGetHostPlayer(); if (host == null) { return ElementSkill.IsOverridden((uint)skillID) ? enumSkillLearnedState.SKILL_OVERRIDDEN : enumSkillLearnedState.SKILL_NOT_LEARNED; } CECSkill pSkill = host.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() { Initialize(null); } /// When set (anim test scene), filters catalog by this profession instead of querying host. public void Initialize(int? professionOverride) { // Çå¿ÕËùÓм¼ÄÜ£¬·ÀÖ¹ÒòΪ¶à¸ö½ÇÉ«µÇ¼µ¼ÖÂÖØ¸´¼ÓÔØ¼¼ÄÜ Release(); InitAllSkillsOfCurProf(professionOverride); FindAllNPCsOfCurProf(); HashSet rootSkills = GetRootSkillSet(); InitSkillTreeHeightMap(rootSkills); InitSkillTreeRootMap(rootSkills); m_bInitialized = true; // ÖØÐ´¦ÀíNPCLIST ProcessServiceList(); } public void ProcessServiceList() { if (m_npcListData == null) { BMLogger.LogError("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; } //BMLogger.LogError("ProcessServiceList npcList.count:" + npcList.count); //BMLogger.LogError("ProcessServiceList m_allProfNPCs.count:" + m_allProfNPCs.Count); int i; bool sawProfSkillNpcInList = false; for (i = 0; i < npcList.count; i++) { int tid = npcList.list[i].tid; //BMLogger.LogError("ProcessServiceList tid:" + tid); if (m_allProfNPCs.Contains(tid)) { sawProfSkillNpcInList = true; //BMLogger.LogError("m_skillLearnNPCNID : " + m_skillLearnNPCNID); //BMLogger.LogError("npcList.list[i].nid : " + npcList.list[i].nid); if (m_skillLearnNPCNID != npcList.list[i].nid) { m_skillLearnNPCNID = npcList.list[i].nid; m_bReceivedNPCGreeting = false; // new NPC -> need greeting again SetCurServiceSkills(tid); var change = new CECSkillPanelChange(CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC, 0, 0); //NotifyObservers(change); break; } else { // Same trainer instance as before — still refresh service skills (re-enter world / list refresh). SetCurServiceSkills(tid); var change = new CECSkillPanelChange(CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC, 0, 0); break; } } } if (i == npcList.count && m_skillLearnNPCNID != 0 && !sawProfSkillNpcInList) { m_skillLearnNPCNID = 0; SetCurServiceSkills(0); var change = new CECSkillPanelChange(CECSkillPanelChange.enumChangeMask.CHANGE_SKILL_NPC, 0, 0); //NotifyObservers(change); } // Do not Clear m_npcListData: Initialize() may run ProcessServiceList again after Release(); // clearing here left npcDataSize 0 on world re-entry. New packets replace m_npcListData in RecvNPCServiceList. } } 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) { //BMLogger.LogError("SetCurServiceSkills " + 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; 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); } } //BMLogger.LogError("SetCurServiceSkills m_curServiceSkills count:" + m_curServiceSkills.Count); //BMLogger.LogError("SetCurServiceSkills skillService.id_skills count:" + skillService.id_skills.Length); } public enumSkillFitLevelState GetSkillFitLevel(int skillID) { if (!m_allProfSkills.ContainsKey(skillID)) { BMLogger.LogError("skillID not exist in m_allProfSkills"); return default; } CECHostPlayer host = TryGetHostPlayer(); if (host == null) { return enumSkillFitLevelState.SKILL_NOT_FIT_LEVEL; } int maxLevel = host.GetMaxLevelSofar(); int rank = host.GetBasicProps().iLevel2; int realmLevel = host.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 = TryGetHostPlayer()?.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) { CECHostPlayer host = TryGetHostPlayer(); return host != null ? host.CheckSkillLearnCondition(skillID, true) : 0; } public StringBuilder GetSkillDescription(int skillID, int level) { StringBuilder tmp = new StringBuilder(); return CECSkill.GetDesc(skillID, level, tmp, 1024); } 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; } 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 = TryGetHostPlayer()?.GetNormalSkill(skillID); if (pSkill != null) { return pSkill.GetSkillLevel(); } 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; foreach (var skill in juniors) { int subHeight = GetSkillTreeHeight((int)skill.Key); if (subHeight > maxHeight) { maxHeight = subHeight; } } return 1 + maxHeight; } public Dictionary 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 Dictionary(); foreach (var (id, level) in juniors) { if (id != 0) ret[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); //BMLogger.LogError("Hoang Dev map Count :" + map.Length); 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) { //BMLogger.LogError($"Hoang Dev pSkill is null for skill {i} :" + skillService.id_skills[i]); continue; } CECHostPlayer host = TryGetHostPlayer(); if (host != null && pSkill.GetCls() == host.GetProfession()) { // ��NPC������ǰְҵ���ܣ���Ҫ��¼��NPC��ID profCorrect = true; break; } } } if (profCorrect) { //BMLogger.LogError("m_allProfNPCs.Add " + (int)npcEssence.id); m_allProfNPCs.Add((int)npcEssence.id); } //BMLogger.LogError("Hoang Dev skillService.id_skills.Length :" + skillService.id_skills.Length); } } } public void InitAllSkillsOfCurProf() { InitAllSkillsOfCurProf(null); } public void InitAllSkillsOfCurProf(int? professionOverride) { // --- 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); } } } } } var hostPlayer = TryGetHostPlayer(); int playerProfession = professionOverride ?? (hostPlayer != null ? hostPlayer.GetProfession() : -1); // --- 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 = playerProfession; 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); } } 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) { CECHostPlayer host = TryGetHostPlayer(); return host != null && host.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; } } }