Files
test/Assets/PerfectWorld/Scripts/Skills/EC_HostSkillModel.cs
T
2025-11-11 17:12:51 +07:00

428 lines
16 KiB
C#

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
{
public static CECHostSkillModel instance;
public static CECHostSkillModel Instance
{
get
{
if (instance == null)
{
instance = new CECHostSkillModel();
}
return instance;
}
set => instance = value;
}
Dictionary<int, ElementSkill> m_allProfSkills = new Dictionary<int, ElementSkill>();
Dictionary<int, List<int>> m_allRankProfSkills = new Dictionary<int, List<int>>();
private HashSet<int> m_allProfNPCs = new HashSet<int>();
private Dictionary<int, int> m_treeHeightMap = new Dictionary<int, int>();
private Dictionary<int, int> m_evilRootMap = new Dictionary<int, int>();
private Dictionary<int, int> m_godRootMap = new Dictionary<int, int>();
private Dictionary<int, int> m_baseRootMap = new Dictionary<int, int>();
private int m_skillLearnNPCNID;
private bool m_bInitialized;
private Octets m_npcListData;
public void Initialize()
{
// Çå¿ÕËùÓм¼ÄÜ£¬·ÀÖ¹ÒòΪ¶à¸ö½ÇÉ«µÇ¼µ¼ÖÂÖØ¸´¼ÓÔØ¼¼ÄÜ
Release();
InitAllSkillsOfCurProf();
FindAllNPCsOfCurProf();
HashSet<int> 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<uint>(bodyBytes);
offset = sizeof(uint);
int NpcEntrySize = Marshal.SizeOf<cmd_scene_service_npc_list.NpcEntry>();
npcList.list = new cmd_scene_service_npc_list.NpcEntry[npcList.count];
for (int z = 0; z < npcList.count; z++)
{
npcList.list[z] = GPDataTypeHelper.FromBytes<cmd_scene_service_npc_list.NpcEntry>(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<int> m_curServiceSkills = new HashSet<int>();
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<int> rootSkills)
{
foreach (int rootSkillID in rootSkills)
{
InitializeRootOfSkillTree(rootSkillID);
}
}
private void InitializeRootOfSkillTree(int rootSkillID)
{
var skillRootMap = GetSkillRootMap(rootSkillID);
var toTravelSkills = new Queue<int>();
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<int, int> 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<int> 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<int> GetRootSkillSet()
{
var rootSkills = new HashSet<int>();
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ǰְҵܣҪ¼NPCID
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<uint> npcSkills = new HashSet<uint>();
{
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<int>();
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 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;
}
}
}