432 lines
14 KiB
C#
432 lines
14 KiB
C#
using CSNetwork.GPDataType;
|
|
using System.Text;
|
|
using System;
|
|
using UnityEditor.Rendering;
|
|
using UnityEngine;
|
|
using BrewMonster;
|
|
using CSNetwork;
|
|
using ModelRenderer.Scripts.Common;
|
|
using System.IO;
|
|
|
|
public class CECNPC : CECObject
|
|
{
|
|
protected INFO m_NPCInfo;
|
|
protected private uint m_dwStates;
|
|
protected private uint m_dwStates2;
|
|
protected private A3DVECTOR3 m_vServerPos;
|
|
protected private int m_iRandomProp;
|
|
protected private int m_iMoveEnv;
|
|
protected int m_idMaster;
|
|
protected string m_strName;
|
|
protected int m_idOwnerFaction;
|
|
protected float m_fDistToHost;
|
|
protected float m_fDistToHostH;
|
|
protected OtherPlayer_Move_Info m_cdr;
|
|
protected float m_fTouchRad;
|
|
protected ROLEBASICPROP m_BasicProps;
|
|
protected A3DVECTOR3 m_vMoveDir;
|
|
protected int m_iPassiveMove;
|
|
protected bool m_bStopMove;
|
|
protected int[] m_aWorks = new int[4];
|
|
protected int m_iAction;
|
|
[SerializeField] protected float m_fMoveSpeed;
|
|
|
|
protected static CECStringTab m_ActionNames;
|
|
|
|
|
|
public virtual void SetUpCECNPC(CECNPCMan pNPCMan)
|
|
{
|
|
base.SetUpCECObject();
|
|
m_vServerPos = new A3DVECTOR3();
|
|
m_iCID = (int)ClassID.OCID_NPC;
|
|
}
|
|
public virtual bool Init(int tid, in info_npc info, ReadOnlySpan<byte> packet, int infoOffset)
|
|
{
|
|
BrewMonster.Logger.Log("HoangDev: NPCInit");
|
|
|
|
m_NPCInfo.nid = info.nid;
|
|
m_NPCInfo.tid = tid;
|
|
m_NPCInfo.vis_tid = info.vis_tid;
|
|
m_dwStates = (uint)info.state;
|
|
m_dwStates2 = (uint)info.state2;
|
|
m_vServerPos = info.pos;
|
|
m_iRandomProp = (info.state & 0x0f00) >> 8;
|
|
|
|
m_iMoveEnv = (int)((info.state & PlayerNPCState.GP_STATE_NPC_FLY) != 0 ? EnviromentMoveType.MOVEENV_AIR
|
|
: (info.state & PlayerNPCState.GP_STATE_NPC_SWIM) != 0 ? EnviromentMoveType.MOVEENV_WATER
|
|
: EnviromentMoveType.MOVEENV_GROUND);
|
|
|
|
// 2) Cắt “đuôi” ngay sau phần cố định info_npc
|
|
int fixedSize = System.Runtime.InteropServices.Marshal.SizeOf<info_npc>();
|
|
var tail = packet.Slice(infoOffset);
|
|
|
|
var r = new ByteReader(tail);
|
|
|
|
// 3) Đọc theo cờ state, giống C++ (pData tăng dần)
|
|
// EXTEND_PROPERTY
|
|
var ojexitStateCount = (uint)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT;
|
|
|
|
var ext = new uint[ojexitStateCount];
|
|
if ((info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0)
|
|
r.ReadInto(ext);
|
|
//SetNewExtendStates(0, ext, ojexitStateCount );
|
|
|
|
// PET
|
|
m_idMaster = 0;
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_PET) != 0)
|
|
m_idMaster = r.ReadInt32();
|
|
|
|
// NAME
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_NAME) != 0)
|
|
{
|
|
byte len = r.ReadByte();
|
|
if (len > 0)
|
|
{
|
|
// ACHAR thường là UTF-16LE → len là số byte
|
|
var nameBytes = r.ReadBytes(len);
|
|
m_strName = System.Text.Encoding.Unicode.GetString(nameBytes);
|
|
}
|
|
}
|
|
|
|
SetSelectable((info.state & PlayerNPCState.GP_STATE_FORBIDBESELECTED) == 0);
|
|
|
|
// MULTIOBJ_EFFECT
|
|
if ((info.state & PlayerNPCState.GP_STATE_MULTIOBJ_EFFECT) != 0)
|
|
{
|
|
int n = r.ReadInt32();
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
int idTarget = r.ReadInt32();
|
|
char cType = (char)r.ReadByte();
|
|
//AddMultiObjectEffect(idTarget, cType);
|
|
}
|
|
}
|
|
|
|
// MAFIA
|
|
m_idOwnerFaction = 0;
|
|
if ((info.state & PlayerNPCState.GP_STATE_NPC_MAFIA) != 0)
|
|
m_idOwnerFaction = r.ReadInt32();
|
|
|
|
m_cdr.fStepHeight = 0.4f;
|
|
m_cdr.vVelocity.Clear();
|
|
|
|
var pHost = GameController.Instance.GetHostPlayer();
|
|
if (pHost != null)
|
|
{
|
|
m_fDistToHost = Vector3.Distance(EC_Utility.ToVector3(m_vServerPos), pHost.transform.position);
|
|
m_fDistToHostH = Vector2.Distance(
|
|
new Vector2(m_vServerPos.x, m_vServerPos.z),
|
|
new Vector2(pHost.transform.position.x, pHost.transform.position.z));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
public static bool InitStaticRes()
|
|
{
|
|
// Load action names from file
|
|
if (!m_ActionNames.IsInitialized())
|
|
m_ActionNames.Init("actions_npc.txt", false);
|
|
|
|
return true;
|
|
}
|
|
public void QueueLoadNPCModel()
|
|
{
|
|
/* if (ShouldUseMasterModel())
|
|
{
|
|
if (GetMaster())
|
|
{
|
|
return; // ÄÜ»ñÈ¡½ÇɫģÐÍʱ£¬µ½Ï¸ö Tick ¼ÓÔØÄ£ÐÍ
|
|
} // ÎÞ·¨»ñÈ¡½Çɫʱ¡¢ÔÝʱʹÓà NPC Ä£ÐÍ
|
|
}*/
|
|
int tid = 0;
|
|
string szModelFile = "";
|
|
if (!GetVisibleModel(out tid, out szModelFile))
|
|
{
|
|
return;
|
|
}
|
|
var nameMonster = Path.GetFileNameWithoutExtension(szModelFile);
|
|
BrewMonster.Logger.Log("HoangDev: nameMonster :" + nameMonster);
|
|
var model = NPCBuilder.Instance.GetModelByName(nameMonster);
|
|
|
|
BrewMonster.Logger.Log("HoangDev: model.name :" + model.name);
|
|
var monsterModel = Instantiate(model, transform);
|
|
monsterModel.SetActive(true);
|
|
//QueueECModelForLoad(MTL_ECM_NPC, GetNPCInfo().nid, GetBornStamp(), GetServerPos(), szModelFile, tid);
|
|
}
|
|
public bool GetVisibleModel(out int tid, out string szModelFile)
|
|
{
|
|
tid = 0;
|
|
szModelFile = string.Empty;
|
|
|
|
// nếu vis_tid có model file
|
|
if (GetModelFile(GetNPCInfo().vis_tid, out szModelFile))
|
|
{
|
|
tid = GetNPCInfo().vis_tid;
|
|
}
|
|
// nếu không có thì thử lấy từ tid thường
|
|
else if (GetModelFile(GetNPCInfo().tid, out szModelFile))
|
|
{
|
|
tid = GetNPCInfo().tid;
|
|
}
|
|
|
|
return tid > 0;
|
|
}
|
|
public bool GetModelFile(int tid, out string szModelFile)
|
|
{
|
|
szModelFile = string.Empty;
|
|
|
|
// Lấy database
|
|
var pDB = ElementDataManProvider.GetElementDataMan(); // g_pGame->GetElementDataMan()
|
|
DATA_TYPE dataType = default;
|
|
|
|
// Giả định get_data_ptr trả về object (Essence) và out DataType
|
|
var pDBEssence = pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref dataType);
|
|
if (pDBEssence == null)
|
|
return false;
|
|
|
|
bool ret = true;
|
|
Debug.Log($"HoangDev : DataType : {dataType}");
|
|
|
|
switch (dataType)
|
|
{
|
|
case DATA_TYPE.DT_MONSTER_ESSENCE:
|
|
{
|
|
var ess = (MONSTER_ESSENCE)pDBEssence;
|
|
szModelFile = ByteToStringUtils.ByteArrayToCP936String(ess.file_model);
|
|
Debug.Log($"HoangDev : MONSTER_ESSENCE path : {szModelFile}");
|
|
break;
|
|
}
|
|
case DATA_TYPE.DT_PET_ESSENCE:
|
|
{
|
|
var ess = (PET_ESSENCE)pDBEssence;
|
|
szModelFile = ByteToStringUtils.ByteArrayToCP936String(ess.file_model);
|
|
break;
|
|
}
|
|
case DATA_TYPE.DT_NPC_ESSENCE:
|
|
{
|
|
var ess = (NPC_ESSENCE)pDBEssence;
|
|
szModelFile = ByteToStringUtils.ByteArrayToCP936String(ess.file_model);
|
|
break;
|
|
}
|
|
default:
|
|
ret = false;
|
|
break;
|
|
}
|
|
|
|
Debug.Log($"HoangDev : path 99 : {szModelFile}");
|
|
return ret;
|
|
}
|
|
public static void ReleaseStaticRes()
|
|
{
|
|
m_ActionNames.Release();
|
|
}
|
|
|
|
public void MoveTo(cmd_object_move Cmd)
|
|
{
|
|
if (Cmd.use_time == 0)
|
|
return;
|
|
|
|
m_vServerPos = Cmd.dest;
|
|
m_vMoveDir = Cmd.dest - EC_Utility.ToA3DVECTOR3(transform.position);
|
|
float fDist = m_vMoveDir.Normalize(); // giả sử Normalize() trả về độ dài trước khi chuẩn hóa
|
|
|
|
// If destination position is too far, forcely pull player
|
|
if (IsLag(fDist))
|
|
{
|
|
transform.position = EC_Utility.ToVector3(Cmd.dest);
|
|
return;
|
|
}
|
|
|
|
int iMoveMode = Cmd.move_mode & (int)GPMoveMode.GP_MOVE_MASK;
|
|
|
|
m_bStopMove = false;
|
|
|
|
if (iMoveMode == (int)GPMoveMode.GP_MOVE_PUSH || iMoveMode == (int)GPMoveMode.GP_MOVE_PULL)
|
|
{
|
|
// Push back or pull should occur in stop move command
|
|
UnityEngine.Debug.Assert(false, "Invalid move mode: push/pull inside MoveTo");
|
|
return;
|
|
}
|
|
|
|
m_cdr.bTraceGround = true;
|
|
|
|
if ((Cmd.move_mode & (int)GPMoveMode.GP_MOVE_AIR) != 0)
|
|
{
|
|
m_iMoveEnv = (int)MoveEnvironment.MOVEENV_AIR;
|
|
m_cdr.bTraceGround = false;
|
|
}
|
|
else if ((Cmd.move_mode & (int)GPMoveMode.GP_MOVE_WATER) != 0)
|
|
{
|
|
m_iMoveEnv = (int)MoveEnvironment.MOVEENV_WATER;
|
|
m_cdr.bTraceGround = false;
|
|
}
|
|
else
|
|
{
|
|
m_iMoveEnv = (int)MoveEnvironment.MOVEENV_GROUND;
|
|
|
|
int iTemp = iMoveMode & (int)GPMoveMode.GP_MOVE_MASK;
|
|
if (iTemp == (int)GPMoveMode.GP_MOVE_FALL || iTemp == (int)GPMoveMode.GP_MOVE_FLYFALL)
|
|
m_cdr.bTraceGround = false;
|
|
}
|
|
|
|
m_fMoveSpeed = fDist / (Cmd.use_time * 0.001f);
|
|
// float fSpeed = FIX8TOFLOAT(Cmd.sSpeed);
|
|
// m_fMoveSpeed = Mathf.Clamp(m_fMoveSpeed, 0.0f, fSpeed * 1.2f);
|
|
|
|
// Adjust NPC's direction
|
|
/* if (!IsDirFixed())
|
|
{
|
|
var vDir = m_vMoveDir;
|
|
vDir.y = 0.0f;
|
|
if (!vDir.IsZero())
|
|
{
|
|
vDir.Normalize();
|
|
SetDestDirAndUp(vDir, g_vAxisY, 150);
|
|
}
|
|
}
|
|
*/
|
|
/* if (m_aWorks[(int)WorkType.WT_NORMAL] != (int)WorkID.WORK_MOVE || ShouldPlayNewActionFor(iMoveMode))
|
|
{
|
|
StartWork(WT_NORMAL, WORK_MOVE);
|
|
// Play run or walk action
|
|
PlayMoveAction(iMoveMode);
|
|
}*/
|
|
}
|
|
public void PlayMoveAction(int iMoveMode)
|
|
{
|
|
// Play run or walk aciton
|
|
if (iMoveMode == (int)GPMoveMode.GP_MOVE_RUN || iMoveMode == (int)GPMoveMode.GP_MOVE_RETURN)
|
|
{
|
|
/*if (IsMonsterOrPet())
|
|
PlayModelAction(ACT_RUN, false);
|
|
else
|
|
PlayModelAction(ACT_NPC_RUN, false);*/
|
|
}
|
|
else
|
|
{
|
|
PlayModelAction((int)NPCActionIndex.ACT_WALK, false);
|
|
/* if (IsMonsterOrPet())
|
|
PlayModelAction(ACT_WALK, false);
|
|
else
|
|
PlayModelAction(ACT_NPC_WALK, false);*/
|
|
}
|
|
}
|
|
public void PlayModelAction(int iAction, bool bRestart/* =true */)
|
|
{
|
|
m_iAction = iAction;
|
|
/* if (IsDead())
|
|
{
|
|
// ¼ì²éËÀÍö״̬£¬ÒÔÆÁ±ÎÆäËü¶¯×÷
|
|
// ËÀÍö״̬ÉèÖúó£¬Ö»ÔÊÐí²¥·ÅËÀÍö¶¯×÷
|
|
// Íæ¼Ò¹¥»÷NPCʱ£¬»áÊ×ÏȲ¥·ÅÍæ¼ÒµÄ¹¥»÷¶¯×÷£¬Íê³Éºó²¥·Å¹ÖÎïµÄÊÜÉ˶¯×÷
|
|
// µ«ÔÚÍæ¼Ò×ÔÉíµÄ¹¥»÷¶¯×÷δÍê³Éʱ£¬¿ÉÄܾÍÊÕµ½NPCËÀÍöµÄÏûÏ¢
|
|
// Òò´Ë£¬¿ÉÄÜ»áÏȲ¥·ÅËÀÍö¶¯×÷£¬¶øºóÓÖ²¥·ÅÊÜÉ˶¯×÷£¬µ¼ÖÂËÀÍö¶¯×÷²»ÄÜÕý³£²¥·ÅµÄ±íÏÖ½á¹û
|
|
// ²»·ûºÏÆÚÍû
|
|
if (IsMonsterOrPet())
|
|
{
|
|
if (iAction != CECNPC::ACT_DIE)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iAction != CECNPC::ACT_NPC_DIE)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
bool IsLag(float fDist)
|
|
{
|
|
return m_iPassiveMove == 0 && fDist > MAX_LAGDIST;
|
|
}
|
|
public INFO GetNPCInfo() { return m_NPCInfo; }
|
|
|
|
public struct INFO
|
|
{
|
|
public int nid; // NPC id
|
|
public int tid; // Template id
|
|
public int vis_tid;// template id for shape
|
|
};
|
|
public const float MAX_LAGDIST = 25.0f;
|
|
}
|
|
public enum WorkType
|
|
{
|
|
WT_NOTHING = 0, // Do thing
|
|
WT_NORMAL, // Normal type work
|
|
WT_INTERRUPT, // Interrupt type work
|
|
NUM_WORKTYPE,
|
|
};
|
|
// Work ID
|
|
public enum WorkID
|
|
|
|
{
|
|
WORK_STAND = 1, // Stand and do nothing
|
|
WORK_FIGHT, // Fighting
|
|
WORK_SPELL, // Monster is cast skill
|
|
WORK_DEAD, // Monster is dead
|
|
WORK_MOVE, // Move to a destination terrain position
|
|
WORK_POLICYACTION, // Is playing policy action£¬WT_INTERRUPT
|
|
};
|
|
public enum NPCActionIndex
|
|
|
|
{
|
|
ACT_STAND = 0,
|
|
ACT_IDLE,
|
|
ACT_GUARD,
|
|
ACT_SKILL1,
|
|
ACT_WALK,
|
|
ACT_ATTACK1,
|
|
ACT_ATTACK2,
|
|
ACT_RUN,
|
|
ACT_DIE,
|
|
ACT_JUMP_START,
|
|
ACT_JUMP_LAND,
|
|
ACT_JUMP_LOOP,
|
|
ACT_MAGIC1,
|
|
ACT_WOUNDED,
|
|
ACT_NPC_CHAT1,
|
|
ACT_NPC_CHAT2,
|
|
ACT_NPC_CHATLOOP,
|
|
ACT_NPC_IDLE1,
|
|
ACT_NPC_IDLE2,
|
|
ACT_NPC_STAND,
|
|
ACT_NPC_WALK,
|
|
ACT_NPC_RUN,
|
|
ACT_NPC_ATTACK,
|
|
ACT_NPC_DIE,
|
|
ACT_COMMON_BORN,
|
|
ACT_NPC_DISAPPEAR,
|
|
ACT_WOUNDED2,
|
|
ACT_MAX,
|
|
};
|
|
public ref struct ByteReader
|
|
{
|
|
private ReadOnlySpan<byte> _span;
|
|
private int _offset;
|
|
public ByteReader(ReadOnlySpan<byte> span) { _span = span; _offset = 0; }
|
|
public bool Eof => _offset >= _span.Length;
|
|
public byte ReadByte() => _span[_offset++];
|
|
public int ReadInt32()
|
|
{
|
|
int v = BitConverter.ToInt32(_span.Slice(_offset, 4));
|
|
_offset += 4; return v;
|
|
}
|
|
public void ReadInto(uint[] dst)
|
|
{
|
|
// OBJECT_EXT_STATE_COUNT * sizeof(DWORD)
|
|
int bytes = dst.Length * 4;
|
|
var s = _span.Slice(_offset, bytes);
|
|
for (int i = 0; i < dst.Length; i++)
|
|
dst[i] = BitConverter.ToUInt32(s.Slice(i * 4, 4));
|
|
_offset += bytes;
|
|
}
|
|
public byte[] ReadBytes(int len)
|
|
{
|
|
var s = _span.Slice(_offset, len).ToArray();
|
|
_offset += len; return s;
|
|
}
|
|
} |