275 lines
9.2 KiB
C#
275 lines
9.2 KiB
C#
using BrewMonster;
|
|
using CSNetwork;
|
|
using CSNetwork.GPDataType;
|
|
using DG.Tweening;
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
|
|
public class CECNPCMan : CECObject, IMsgHandler
|
|
{
|
|
private Dictionary<int, CECNPC> m_NPCTab = new Dictionary<int, CECNPC>(512);
|
|
private Dictionary<int, int> m_UkNPCTab = new Dictionary<int, int>(32);
|
|
Vector3 m_vServerPos;
|
|
public int HandlerId => (int)MANAGER_INDEX.MAN_NPC;
|
|
|
|
public CECNPCMan()
|
|
{
|
|
m_vServerPos = Vector3.zero;
|
|
m_iCID = (int)Class_ID.OCID_MONSTER;
|
|
}
|
|
public bool ProcessMessage(ECMSG Msg)
|
|
{
|
|
if (Msg.iSubID == 0)
|
|
{
|
|
switch (Msg.dwMsg)
|
|
{
|
|
case long value when value == EC_MsgDef.MSG_NM_NPCINFO: OnMsgNPCInfo(Msg); break;
|
|
case long value when value == EC_MsgDef.MSG_NM_NPCMOVE: OnMsgNPCMove(Msg); break;
|
|
case long value when value == EC_MsgDef.MSG_NM_NPCSTOPMOVE: OnMsgNPCStopMove(Msg); break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool OnMsgNPCStopMove(ECMSG msg)
|
|
{
|
|
cmd_object_stop_move pCmd = EC_Utility.ByteArrayToStructure<cmd_object_stop_move>((byte[])msg.dwParam1);
|
|
|
|
CECNPC pNPC = SeekOutNPC(pCmd.id);
|
|
if (pNPC)
|
|
pNPC.StopMoveTo(pCmd);
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool OnMsgNPCMove(ECMSG msg)
|
|
{
|
|
var buffer = (byte[])msg.dwParam1;
|
|
cmd_object_move pCmd = MemoryMarshal.Read<cmd_object_move>(buffer);
|
|
|
|
if (pCmd.use_time == 0)
|
|
return true;
|
|
|
|
CECNPC pNPC = SeekOutNPC(pCmd.id);
|
|
|
|
|
|
if (pNPC)
|
|
pNPC.MoveTo(pCmd);
|
|
|
|
return true;
|
|
}
|
|
public CECNPC SeekOutNPC(int nid)
|
|
{
|
|
if (!m_NPCTab.TryGetValue(nid, out var npc))
|
|
{
|
|
// Couldn't find this NPC, put it into unknown NPC table
|
|
m_UkNPCTab[nid] = nid;
|
|
return null;
|
|
}
|
|
|
|
return npc;
|
|
}
|
|
private bool OnMsgNPCInfo(ECMSG msg)
|
|
{
|
|
switch (Convert.ToInt32(msg.dwParam2))
|
|
{
|
|
case CommandID.NPC_INFO_LIST:
|
|
{
|
|
|
|
// msg.dwParam1 chính là buffer chứa placeholder data (không có header cmd_npc_info_list)
|
|
cmd_npc_info_list pCmd = MemoryMarshal.Read<cmd_npc_info_list>(
|
|
((byte[])msg.dwParam1).AsSpan());
|
|
|
|
int offset = Marshal.OffsetOf<cmd_npc_info_list>("placeholder").ToInt32();
|
|
byte[] buffer = (byte[])msg.dwParam1;
|
|
Span<byte> pDataBuf = buffer.AsSpan(offset);
|
|
|
|
for (int i = 0; i < pCmd.count; i++)
|
|
{
|
|
// giống const info_npc& Info = *(const info_npc*)pDataBuf;
|
|
info_npc info = MemoryMarshal.Read<info_npc>(pDataBuf);
|
|
|
|
int iSize = info_npc.HEADER_SIZE;
|
|
if ((info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0)
|
|
iSize += sizeof(uint) * NumberDWORDsPlayerNPC.OBJECT_EXT_STATE_COUNT;
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_PET) != 0)
|
|
iSize += sizeof(int);
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_NAME) != 0)
|
|
{
|
|
byte len = pDataBuf[iSize];
|
|
iSize += 1 + len;
|
|
}
|
|
if ((info.state & PlayerNPCState.GP_STATE_MULTIOBJ_EFFECT) != 0)
|
|
{
|
|
int countEff = BinaryPrimitives.ReadInt32LittleEndian(
|
|
pDataBuf.Slice(iSize, sizeof(int)));
|
|
iSize += sizeof(int) + countEff * (sizeof(int) + 1);
|
|
}
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_MAFIA) != 0)
|
|
iSize += sizeof(int);
|
|
|
|
NPCEnter(info, false, buffer, offset);
|
|
|
|
// dịch pDataBuf về sau (giống pDataBuf += iSize)
|
|
pDataBuf = pDataBuf.Slice(iSize);
|
|
offset += iSize;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CommandID.NPC_ENTER_SLICE:
|
|
{
|
|
var buffer = (byte[])msg.dwParam1;
|
|
info_npc info = MemoryMarshal.Read<info_npc>(buffer.AsSpan(0, info_npc.HEADER_SIZE));
|
|
NPCEnter(info, false, buffer, info_npc.HEADER_SIZE);
|
|
break;
|
|
}
|
|
|
|
case CommandID.NPC_ENTER_WORLD:
|
|
{
|
|
var buffer = (byte[])msg.dwParam1;
|
|
info_npc info = MemoryMarshal.Read<info_npc>(buffer.AsSpan(0, info_npc.HEADER_SIZE));
|
|
NPCEnter(info, true, buffer, info_npc.HEADER_SIZE);
|
|
break;
|
|
}
|
|
case CommandID.NPC_INFO_00:
|
|
{
|
|
var buffer = (byte[])msg.dwParam1;
|
|
cmd_npc_info_00 pCmd = GPDataTypeHelper.FromBytes<cmd_npc_info_00>(buffer);
|
|
//cmd_npc_info_00 pCmd = MemoryMarshal.Read<cmd_npc_info_00>(buffer.AsSpan(0, cmd_npc_info_00.));
|
|
CECNPC pNPC = SeekOutNPC(pCmd.idNPC);
|
|
if (pNPC)
|
|
{
|
|
ROLEBASICPROP bp = pNPC.GetBasicProps();
|
|
ROLEEXTPROP ep = pNPC.GetExtendProps();
|
|
|
|
bp.iCurHP = pCmd.iHP;
|
|
ep.bs.max_hp = pCmd.iMaxHP;
|
|
pNPC.SetSelectedTarget(pCmd.iTargetID);
|
|
}
|
|
break;
|
|
}
|
|
case CommandID.NPC_VISIBLE_TID_NOTIFY:
|
|
{
|
|
cmd_npc_visible_tid_notify pCmd = (cmd_npc_visible_tid_notify)msg.dwParam1;
|
|
CECNPC pNPC = SeekOutNPC(pCmd.nid);
|
|
if (pNPC)
|
|
pNPC.TransformShape(pCmd.vis_tid);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool NPCEnter(in info_npc Info, bool bBornInSight, ReadOnlySpan<byte> packet, int infoOffset)
|
|
{
|
|
var npc = GetNPC(Info.nid);
|
|
if (npc != null)
|
|
{
|
|
m_NPCTab.Remove(Info.nid);
|
|
}
|
|
|
|
// Nếu id này có trong bảng unknown thì xóa nó
|
|
if (m_UkNPCTab.ContainsKey(Info.nid))
|
|
{
|
|
m_UkNPCTab.Remove(Info.nid);
|
|
}
|
|
|
|
// Tạo NPC mới
|
|
npc = CreateNPC(Info, bBornInSight, packet, infoOffset);
|
|
if(npc != null)
|
|
{
|
|
npc.SetUpCECNPC(this);
|
|
}
|
|
if (object.ReferenceEquals(npc, null))
|
|
{
|
|
BrewMonster.BMLogger.LogError($"Failed to create NPC ({Info.tid})");
|
|
return false;
|
|
}
|
|
|
|
// Thêm NPC vào bảng
|
|
m_NPCTab[Info.nid] = npc;
|
|
|
|
return true;
|
|
}
|
|
// Get NPC by id and optional bornStamp
|
|
public CECNPC GetNPC(int nid, uint bornStamp = 0)
|
|
{
|
|
if (!m_NPCTab.TryGetValue(nid, out var npc))
|
|
return null;
|
|
|
|
return npc;
|
|
}
|
|
public CECNPC CreateNPC(info_npc Info, bool bBornInSight, ReadOnlySpan<byte> packet, int infoOffset)
|
|
{
|
|
CECNPC pNPC = null;
|
|
|
|
int tid = Info.tid;
|
|
bool bPet = (Info.state & PlayerNPCState.GP_STATE_NPC_PET) != 0;
|
|
|
|
// Get data type from database
|
|
var edm = ElementDataManProvider.GetElementDataMan(); // tương đương g_pGame->GetElementDataMan()
|
|
DATA_TYPE dataType = edm.get_data_type((uint)tid, ID_SPACE.ID_SPACE_ESSENCE);
|
|
|
|
if (dataType != DATA_TYPE.DT_NPC_ESSENCE &&
|
|
dataType != DATA_TYPE.DT_MONSTER_ESSENCE &&
|
|
dataType != DATA_TYPE.DT_PET_ESSENCE)
|
|
{
|
|
// Try default npc
|
|
tid = 4249;
|
|
dataType = edm.get_data_type((uint)tid, ID_SPACE.ID_SPACE_ESSENCE);
|
|
}
|
|
if (bPet)
|
|
{
|
|
//pNPC = new CECPet(this);
|
|
}
|
|
else
|
|
{
|
|
switch (dataType)
|
|
{
|
|
case DATA_TYPE.DT_NPC_ESSENCE: /*pNPC = new CECNPCServer(this);*/ break;
|
|
case DATA_TYPE.DT_MONSTER_ESSENCE:
|
|
|
|
pNPC = GameController.Instance.GetMonster();
|
|
|
|
break;
|
|
case DATA_TYPE.DT_PET_ESSENCE:/* pNPC = new CECPet(this);*/ break;
|
|
default:
|
|
UnityEngine.Debug.Assert(false, "Invalid DATA_TYPE in CreateNPC");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Set born stamp & born-in-sight (giữ nguyên semantics)
|
|
uint bornStamp = CECWorld.Instance.GetBornStamp();
|
|
|
|
if (!object.ReferenceEquals(pNPC, null))
|
|
{
|
|
pNPC.SetBornStamp(bornStamp);
|
|
pNPC.SetBornInSight(bBornInSight);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Init với tid + Info như C++
|
|
if (!pNPC.Init(tid, Info, packet, infoOffset))
|
|
{
|
|
|
|
// đảm bảo giải phóng nếu bạn có tài nguyên kèm theo
|
|
//pNPC?.Release();
|
|
pNPC = null;
|
|
|
|
// log lỗi tương tự glb_ErrorOutput
|
|
UnityEngine.Debug.LogError($"CECNPCMan::CreateNPC failed, tid={tid}");
|
|
return null;
|
|
}
|
|
|
|
return pNPC;
|
|
}
|
|
}
|