1096 lines
36 KiB
C#
1096 lines
36 KiB
C#
using BrewMonster;
|
|
using CSNetwork;
|
|
using CSNetwork.GPDataType;
|
|
using ModelRenderer.Scripts.Common;
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using UnityEditor.Rendering;
|
|
using UnityEngine;
|
|
|
|
public class CECNPC : CECObject
|
|
{
|
|
protected INFO m_NPCInfo;
|
|
protected private uint m_dwStates;
|
|
protected private uint m_dwStates2;
|
|
protected private Vector3 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 = 1f;
|
|
protected ROLEBASICPROP m_BasicProps;
|
|
protected Vector3 m_vMoveDir;
|
|
protected int m_iPassiveMove;
|
|
protected bool m_bStopMove;
|
|
protected bool m_bStartFight;
|
|
protected int[] m_aWorks = new int[4];
|
|
protected int m_iAction;
|
|
protected int m_idSelTarget;
|
|
protected int m_iCurWorkType;
|
|
protected int m_nFightTimeLeft;
|
|
protected int m_iCurWork;
|
|
protected int m_idAttackTarget;
|
|
protected int m_DisappearCnt;
|
|
protected bool m_bAboutToDie;
|
|
protected Vector3 m_vStopDir;
|
|
protected ROLEEXTPROP m_ExtProps;
|
|
protected CECNPCModelPolicy m_pNPCModelPolicy;
|
|
[SerializeField] protected float m_fMoveSpeed;
|
|
[SerializeField] protected CharacterController _characterController;
|
|
|
|
protected static CECStringTab m_ActionNames;
|
|
|
|
|
|
|
|
public virtual void SetUpCECNPC(CECNPCMan pNPCMan)
|
|
{
|
|
base.SetUpCECObject();
|
|
m_vServerPos = new Vector3();
|
|
m_iCID = (int)Class_ID.OCID_NPC;
|
|
}
|
|
public virtual bool Init(int tid, in info_npc info, ReadOnlySpan<byte> packet, int infoOffset)
|
|
{
|
|
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 = EC_Utility.ToVector3(info.pos);
|
|
m_iRandomProp = (info.state & 0x0f00) >> 8;
|
|
m_pNPCModelPolicy = new CECNPCModelDefaultPolicy(this);
|
|
m_idSelTarget = 0;
|
|
m_iCurWorkType = -1;
|
|
m_fMoveSpeed = 1.0f;
|
|
m_iCurWork = 0;
|
|
m_bStartFight = false;
|
|
m_bAboutToDie = false;
|
|
m_DisappearCnt = 5000;
|
|
|
|
m_BasicProps = new ROLEBASICPROP(true); // struct mặc định, các trường số = 0, mảng đã tạo
|
|
m_ExtProps = new ROLEEXTPROP(true);
|
|
|
|
_characterController = GetComponent<CharacterController>();
|
|
|
|
m_iMoveEnv = (int)((info.state & PlayerNPCState.GP_STATE_NPC_FLY) != 0 ? Move_environment.MOVEENV_AIR
|
|
: (info.state & PlayerNPCState.GP_STATE_NPC_SWIM) != 0 ? Move_environment.MOVEENV_WATER
|
|
: Move_environment.MOVEENV_GROUND);
|
|
|
|
var npcVisual = GetComponent<NPCVisual>();
|
|
m_pNPCModelPolicy.SetNpcVisual(npcVisual);
|
|
|
|
// 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(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 bool ProcessMessage(ECMSG Msg)
|
|
{
|
|
switch (Msg.dwMsg)
|
|
{
|
|
case long value when value == EC_MsgDef.MSG_NM_NPCATKRESULT: OnMsgNPCAtkResult(Msg); break;
|
|
}
|
|
return true;
|
|
}
|
|
public void Damaged(int iDamage, uint dwModifier = 0)
|
|
{
|
|
// == Trường hợp 1: damage là -1 hoặc -2 (chỉ để hiển thị hiệu ứng)
|
|
if (iDamage == -1 || iDamage == -2)
|
|
{
|
|
// Nếu -1 thì chơi animation bị thương
|
|
if (iDamage == -1)
|
|
PlayModelAction((int)NPCActionIndex. ACT_WOUNDED);
|
|
DamageTextPool.Instance.Show(transform.position, 0);
|
|
// Xét các kiểu modifier
|
|
/* if ((dwModifier & CECAttackEvent.MOD_IMMUNE) != 0 && !IsImmuneDisable())
|
|
BubbleText(BUBBLE_IMMUNE, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_NULLITY) != 0)
|
|
BubbleText(BUBBLE_INVALIDHIT, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_ENCHANT_FAILED) != 0)
|
|
BubbleText(BUBBLE_LOSE, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_SUCCESS) != 0)
|
|
BubbleText(BUBBLE_SUCCESS, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_DODGE_DEBUFF) != 0)
|
|
BubbleText(BUBBLE_DODGE_DEBUFF, 0);*/
|
|
|
|
}
|
|
else
|
|
{
|
|
// == Trường hợp 2: có damage thật
|
|
/* bool bDeadlyStrike = (dwModifier & CECAttackEvent.MOD_CRITICAL_STRIKE) != 0;
|
|
bool bRetort = (dwModifier & CECAttackEvent.MOD_RETORT) != 0;*/
|
|
|
|
if (iDamage > 0)
|
|
{
|
|
PlayModelAction((int)NPCActionIndex.ACT_WOUNDED);
|
|
DamageTextPool.Instance.Show(transform.position, iDamage);
|
|
/* int p1 = 0;
|
|
if (bDeadlyStrike)
|
|
p1 |= 0x0001;
|
|
else if (bRetort)
|
|
p1 |= 0x0002;
|
|
|
|
if ((dwModifier & CECAttackEvent.MOD_REBOUND) != 0)
|
|
BubbleText(BUBBLE_REBOUND, (uint)iDamage);
|
|
else if ((dwModifier & CECAttackEvent.MOD_BEAT_BACK) != 0)
|
|
BubbleText(BUBBLE_BEAT_BACK, (uint)iDamage);
|
|
else
|
|
BubbleText(BUBBLE_DAMAGE, (uint)iDamage, p1);*/
|
|
}
|
|
/* else if ((dwModifier & CECAttackEvent.MOD_IMMUNE) != 0 && !IsImmuneDisable())
|
|
BubbleText(BUBBLE_IMMUNE, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_NULLITY) != 0)
|
|
BubbleText(BUBBLE_INVALIDHIT, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_ENCHANT_FAILED) != 0)
|
|
BubbleText(BUBBLE_LOSE, 0);
|
|
else if ((dwModifier & CECAttackEvent.MOD_SUCCESS) != 0)
|
|
BubbleText(BUBBLE_SUCCESS, 0);
|
|
else
|
|
BubbleText(BUBBLE_HITMISSED, 0);*/
|
|
}
|
|
}
|
|
|
|
private void OnMsgNPCAtkResult(ECMSG msg)
|
|
{
|
|
cmd_object_atk_result pCmd = MemoryMarshal.Read<cmd_object_atk_result>(
|
|
((byte[])msg.dwParam1).AsSpan());
|
|
|
|
if (true)
|
|
{
|
|
// Face to target
|
|
NPCTurnFaceTo(pCmd.target_id, Time.deltaTime);
|
|
m_idAttackTarget = pCmd.target_id;
|
|
|
|
// now start a fight work
|
|
if (IsMonsterNPC())
|
|
{
|
|
CECMonster pMonster = (CECMonster)this;
|
|
MONSTER_ESSENCE pMonsterEssence = pMonster.GetDBEssence();
|
|
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_FIGHT, (uint)(pMonsterEssence.attack_speed * 1000));
|
|
}
|
|
}
|
|
//TODO: Hiệu ứng đánh
|
|
//PlayAttackEffect(pCmd->target_id, 0, 0, iDamage, pCmd->attack_flag, pCmd->speed * 50);
|
|
}
|
|
void NPCTurnFaceTo(int idTarget, float dwTime)
|
|
{
|
|
if (IsDirFixed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// tower in war can not turn face to.
|
|
if (IsMonsterNPC())
|
|
{
|
|
int role_in_war = (int)((CECMonster)this).GetDBEssence().role_in_war;
|
|
if (role_in_war == 2 || role_in_war == 5)
|
|
return;
|
|
}
|
|
|
|
TurnFaceTo(idTarget, dwTime);
|
|
}
|
|
private void Update()
|
|
{
|
|
switch (m_iCurWork)
|
|
{
|
|
case (int)WorkID.WORK_MOVE: TickWork_Move(Time.deltaTime); break;
|
|
}
|
|
}
|
|
public void TickWork_Move(float dwDeltaTime)
|
|
{
|
|
if (m_bAboutToDie)
|
|
{
|
|
WorkFinished((int)WorkID.WORK_MOVE);
|
|
}
|
|
else if (MovingTo(dwDeltaTime))
|
|
{
|
|
/* if (!IsDirFixed())
|
|
{
|
|
SetDestDirAndUp(m_vStopDir, g_vAxisY, 150);
|
|
}*/
|
|
|
|
WorkFinished((int)WorkID.WORK_MOVE);
|
|
|
|
// when stopped, we should rebuild the convex brushes for collision detection.
|
|
//RebuildTraceBrush();
|
|
}
|
|
}
|
|
public bool MovingTo(float deltaTime)
|
|
{
|
|
bool reachedDestination = false;
|
|
Vector3 curPos = transform.position;
|
|
|
|
if (m_bStopMove)
|
|
{
|
|
|
|
// Tính hướng đến serverPos
|
|
Vector3 dir = (m_vServerPos - curPos);
|
|
float dist = dir.magnitude;
|
|
if (dist > 0.001f)
|
|
{
|
|
|
|
dir.Normalize();
|
|
|
|
// Di chuyển một bước
|
|
Vector3 moveDelta = dir * m_fMoveSpeed * deltaTime;
|
|
if (moveDelta.magnitude >= dist)
|
|
{
|
|
// Nếu vượt quá đích thì teleport
|
|
_characterController.enabled = false;
|
|
transform.position = m_vServerPos;
|
|
_characterController.enabled = true;
|
|
reachedDestination = true;
|
|
FaceDirectionImmediate(moveDelta);
|
|
|
|
}
|
|
else
|
|
{
|
|
_characterController.Move(moveDelta);
|
|
FaceDirectionImmediate(moveDelta);
|
|
}
|
|
}
|
|
}
|
|
else // đang move bình thường
|
|
{
|
|
|
|
float dist = (m_vServerPos - curPos).magnitude;
|
|
|
|
if (IsLag(dist))
|
|
{
|
|
// Teleport nếu lag xa
|
|
_characterController.enabled = false;
|
|
transform.position = m_vServerPos;
|
|
_characterController.enabled = true;
|
|
|
|
m_vStopDir = transform.forward;
|
|
return true;
|
|
}
|
|
|
|
Vector3 dir = m_vMoveDir.normalized;
|
|
Vector3 moveDelta = dir * m_fMoveSpeed * deltaTime;
|
|
_characterController.Move(moveDelta);
|
|
// Thêm xoay theo trục Y
|
|
FaceDirectionSmooth(dir, 10f, deltaTime);
|
|
}
|
|
|
|
return reachedDestination;
|
|
}
|
|
/// <summary>
|
|
/// Xoay model NGAY LẬP TỨC theo hướng chỉ định (giữ nguyên trục Y đứng thẳng).
|
|
/// </summary>
|
|
public void FaceDirectionImmediate(Vector3 dir)
|
|
{
|
|
if (dir.sqrMagnitude > 0.0001f)
|
|
{
|
|
Vector3 flatDir = new Vector3(dir.x, 0, dir.z);
|
|
if (flatDir.sqrMagnitude > 0.0001f)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(flatDir, Vector3.up);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Xoay model TỪ TỪ (mượt) theo hướng chỉ định (giữ nguyên trục Y đứng thẳng).
|
|
/// rotateSpeed = tốc độ xoay (độ mượt), ví dụ 10f.
|
|
/// deltaTime = Time.deltaTime trong Update.
|
|
/// </summary>
|
|
public void FaceDirectionSmooth(Vector3 dir, float rotateSpeed, float deltaTime)
|
|
{
|
|
if (dir.sqrMagnitude > 0.0001f)
|
|
{
|
|
Vector3 flatDir = new Vector3(dir.x, 0, dir.z);
|
|
if (flatDir.sqrMagnitude > 0.0001f)
|
|
{
|
|
Quaternion targetRot = Quaternion.LookRotation(flatDir, Vector3.up);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, deltaTime * rotateSpeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WorkFinished(int iWorkID)
|
|
{
|
|
// Note: below judge can prevent many problems when we attempt to
|
|
// finish a work but don't assure we are doing this work
|
|
BrewMonster.BMLogger.Log("HoangDev : WorkFinished :" + iWorkID);
|
|
if (m_iCurWork != iWorkID)
|
|
return;
|
|
|
|
// ASSERT equivalent
|
|
if (m_iCurWork <= 0 || m_iCurWorkType < 0)
|
|
{
|
|
throw new InvalidOperationException("Invalid work state in WorkFinished");
|
|
}
|
|
|
|
// Release current work
|
|
ReleaseWork(m_iCurWorkType);
|
|
|
|
for (int i = m_iCurWorkType - 1; i >= 0; i--)
|
|
{
|
|
if (m_aWorks[i] != 0) // giả định m_aWorks là mảng int workIDs
|
|
{
|
|
m_iCurWorkType = i;
|
|
StartWorkByID(m_aWorks[i], 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// clear passive move flag
|
|
if (iWorkID == (int)WorkID.WORK_MOVE)
|
|
m_iPassiveMove = 0;
|
|
}
|
|
public bool IsDead()
|
|
{
|
|
return (m_dwStates & PlayerNPCState.GP_STATE_CORPSE) != 0;
|
|
}
|
|
public static bool InitStaticRes()
|
|
{
|
|
m_ActionNames = new CECStringTab();
|
|
// Load action names from file
|
|
if (!m_ActionNames.IsInitialized())
|
|
m_ActionNames.Init("actions_npc", false);
|
|
|
|
return true;
|
|
}
|
|
|
|
public void TransformShape(int vis_tid)
|
|
{
|
|
if (m_NPCInfo.vis_tid == vis_tid)
|
|
{
|
|
return;
|
|
}
|
|
m_NPCInfo.vis_tid = vis_tid;
|
|
|
|
QueueLoadNPCModel();
|
|
}
|
|
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);
|
|
var model = NPCBuilder.Instance.GetModelByName(nameMonster);
|
|
if (model == null) return;
|
|
|
|
var monsterModel = Instantiate(model, transform);
|
|
monsterModel.SetActive(true);
|
|
var npcVisual = GetComponent<NPCVisual>();
|
|
npcVisual.InitNPCEventDoneHandler();
|
|
|
|
//QueueECModelForLoad(MTL_ECM_NPC, GetNPCInfo().nid, GetBornStamp(), GetServerPos(), szModelFile, tid);
|
|
}
|
|
public ROLEBASICPROP GetBasicProps() { return m_BasicProps; }
|
|
public ROLEEXTPROP GetExtendProps() { return m_ExtProps; }
|
|
public void SetSelectedTarget(int id) { m_idSelTarget = id; }
|
|
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;
|
|
|
|
switch (dataType)
|
|
{
|
|
case DATA_TYPE.DT_MONSTER_ESSENCE:
|
|
{
|
|
var ess = (MONSTER_ESSENCE)pDBEssence;
|
|
szModelFile = ByteToStringUtils.ByteArrayToCP936String(ess.file_model);
|
|
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;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
public static void ReleaseStaticRes()
|
|
{
|
|
m_ActionNames.Release();
|
|
}
|
|
public static string GetBaseActionName(int iAct)
|
|
{
|
|
return m_ActionNames.GetANSIString(iAct);
|
|
}
|
|
public static bool IsAttackAction(int iAct)
|
|
{
|
|
return iAct == (int)NPCActionIndex.ACT_ATTACK1
|
|
|| iAct == (int)NPCActionIndex.ACT_ATTACK2
|
|
|| iAct == (int)NPCActionIndex.ACT_NPC_ATTACK;
|
|
}
|
|
public void StopMoveTo(cmd_object_stop_move cmd)
|
|
{
|
|
/* if (IsDead())
|
|
return;*/
|
|
|
|
int iMoveMode = cmd.move_mode & (int)GPMoveMode.GP_MOVE_MASK;
|
|
|
|
m_vMoveDir = EC_Utility.ToVector3(cmd.dest) - transform.position;
|
|
m_bStopMove = true;
|
|
m_fMoveSpeed = EC_Utility.FIX8TOFLOAT(cmd.sSpeed);
|
|
m_vServerPos = EC_Utility.ToVector3(cmd.dest);
|
|
m_vStopDir = EC_Utility.glb_DecompressDirH(cmd.dir);
|
|
|
|
// only store the passive move mode
|
|
m_iPassiveMove = (iMoveMode == (int)GPMoveMode.GP_MOVE_PUSH ||
|
|
iMoveMode == (int)GPMoveMode.GP_MOVE_PULL ||
|
|
iMoveMode == (int)GPMoveMode.GP_MOVE_BLINK)
|
|
? iMoveMode : 0;
|
|
|
|
if (IsDirFixed())
|
|
{
|
|
// hướng cố định, set luôn
|
|
transform.forward = m_vStopDir;
|
|
}
|
|
|
|
float fDist = m_vMoveDir.normalized.magnitude; // Normalize() trả về float trong C++, ở đây cần xử lý lại
|
|
m_vMoveDir.Normalize();
|
|
|
|
// Trong các trường hợp dưới thì kéo NPC về đích
|
|
if (iMoveMode != (int)GPMoveMode.GP_MOVE_RETURN &&
|
|
iMoveMode != (int)GPMoveMode.GP_MOVE_PUSH &&
|
|
iMoveMode != (int)GPMoveMode.GP_MOVE_PULL)
|
|
{
|
|
bool bPull = false;
|
|
|
|
if (IsLag(fDist))
|
|
{
|
|
// case 1
|
|
bPull = true;
|
|
}
|
|
else if (fDist < 1.0f)
|
|
{
|
|
// case 2
|
|
Vector3 vDirH = m_vMoveDir;
|
|
vDirH.y = 0.0f;
|
|
vDirH.Normalize();
|
|
if (Vector3.Dot(vDirH, m_vStopDir) < 0.7f)
|
|
bPull = true;
|
|
}
|
|
else if (iMoveMode == (int)GPMoveMode.GP_MOVE_BLINK)
|
|
{
|
|
// case 3
|
|
bPull = true;
|
|
}
|
|
|
|
if (bPull)
|
|
{
|
|
SetPos(EC_Utility.ToVector3(cmd.dest));
|
|
WorkFinished((int)WorkID.WORK_MOVE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!IsDirFixed() && m_iPassiveMove == 0)
|
|
{
|
|
Vector3 vDir = m_vMoveDir;
|
|
vDir.y = 0.0f;
|
|
if (vDir != Vector3.zero)
|
|
{
|
|
vDir.Normalize();
|
|
}
|
|
}
|
|
|
|
if (m_aWorks[(int)WorkType.WT_NORMAL] != (int)WorkID.WORK_MOVE)
|
|
{
|
|
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_MOVE);
|
|
|
|
if (m_iPassiveMove == 0)
|
|
{
|
|
PlayMoveAction(iMoveMode);
|
|
}
|
|
}
|
|
}
|
|
void SetPos(Vector3 pos)
|
|
{
|
|
transform.position = pos;
|
|
}
|
|
public void MoveTo(cmd_object_move Cmd)
|
|
{
|
|
if (Cmd.use_time == 0)
|
|
return;
|
|
var dest = EC_Utility.ToVector3(Cmd.dest);
|
|
m_vServerPos = dest;
|
|
m_vMoveDir = dest - transform.position;
|
|
float fDist = m_vMoveDir.magnitude; // lấy độ dài ban đầu
|
|
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);
|
|
// 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((int)WorkType.WT_NORMAL, (int)WorkID.WORK_MOVE);
|
|
// Play run or walk action
|
|
|
|
PlayMoveAction(iMoveMode);
|
|
}
|
|
}
|
|
public bool IsDirFixed() { return (m_dwStates & PlayerNPCState.GP_STATE_NPC_FIXDIR) != 0 ? true : false; }
|
|
public void ReleaseWork(int iWorkType)
|
|
{
|
|
BrewMonster.BMLogger.LogError("HoangDev : ReleaseWorkl :" + iWorkType);
|
|
Debug.Assert(iWorkType >= 0 && iWorkType < (int)WorkType.NUM_WORKTYPE);
|
|
|
|
switch (m_aWorks[iWorkType])
|
|
{
|
|
case (int)WorkID.WORK_STAND:
|
|
break;
|
|
|
|
case (int)WorkID.WORK_FIGHT:
|
|
break;
|
|
|
|
case (int)WorkID.WORK_SPELL:
|
|
break;
|
|
|
|
case (int)WorkID.WORK_DEAD:
|
|
break;
|
|
|
|
case (int)WorkID.WORK_MOVE:
|
|
{
|
|
// Để tránh trường hợp WORK_MOVE bị ghi đè bởi WORK_SPELL hoặc WORK khác
|
|
// dẫn đến NPC sai vị trí, ta sẽ kiểm tra và kéo NPC về đúng server position
|
|
var pos = m_vServerPos;
|
|
var vDelta = pos - transform.position;
|
|
float fDist = vDelta.magnitude; // Vector3.magnitude trong Unity
|
|
|
|
if (fDist > 0.1f)
|
|
{
|
|
transform.position = (pos);
|
|
/* if (!IsDirFixed())
|
|
{
|
|
SetDestDirAndUp(m_vStopDir, Vector3.up, 150);
|
|
}*/
|
|
//RebuildTraceBrush();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case (int)WorkID.WORK_POLICYACTION:
|
|
{
|
|
/* m_pNPCModelPolicy?.StopChannelAction();
|
|
m_pPolicyAction = null;
|
|
m_nPolicyActionIntervalTimer = 0;*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_aWorks[iWorkType] = 0;
|
|
|
|
if (m_iCurWorkType == iWorkType)
|
|
m_iCurWork = 0;
|
|
}
|
|
|
|
public void StartWork(int iWorkType, int iNewWork, uint dwParam = 0)
|
|
{
|
|
Debug.Assert(iWorkType >= 0 && iWorkType < (int)WorkType.NUM_WORKTYPE);
|
|
|
|
if (iNewWork == (int)WorkID.WORK_DEAD)
|
|
{
|
|
// Dead is a special work
|
|
ReleaseWork((int)WorkType.WT_INTERRUPT);
|
|
ReleaseWork((int)WorkType.WT_NORMAL);
|
|
|
|
m_aWorks[(int)WorkType.WT_NORMAL] = iNewWork;
|
|
m_iCurWorkType = (int)WorkType.WT_NORMAL;
|
|
}
|
|
else if (iWorkType == (int)WorkType.WT_INTERRUPT)
|
|
{
|
|
// Release old work
|
|
ReleaseWork((int)WorkType.WT_INTERRUPT);
|
|
m_aWorks[(int)WorkType.WT_INTERRUPT] = iNewWork;
|
|
|
|
if (m_iCurWorkType == (int)WorkType.WT_NORMAL || m_iCurWorkType == (int)WorkType.WT_NOTHING)
|
|
StopWork(m_iCurWorkType);
|
|
|
|
m_aWorks[(int)WorkType.WT_INTERRUPT] = iNewWork;
|
|
m_iCurWorkType = (int)WorkType.WT_INTERRUPT;
|
|
}
|
|
else if (iWorkType == (int)WorkType.WT_NORMAL)
|
|
{
|
|
// Release old work
|
|
ReleaseWork((int)WorkType.WT_NORMAL);
|
|
m_aWorks[(int)WorkType.WT_NORMAL] = iNewWork;
|
|
|
|
if (m_iCurWorkType < 0 || m_iCurWorkType == (int)WorkType.WT_NORMAL || m_iCurWorkType == (int)WorkType.WT_NOTHING)
|
|
{
|
|
if (m_iCurWorkType == (int)WorkType.WT_NOTHING)
|
|
StopWork((int)WorkType.WT_NOTHING);
|
|
|
|
m_iCurWorkType = (int)WorkType.WT_NORMAL;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
else // iWorkType == WT_NOTHING
|
|
{
|
|
// Release old work
|
|
ReleaseWork((int)WorkType.WT_NOTHING);
|
|
m_aWorks[(int)WorkType.WT_NOTHING] = iNewWork;
|
|
|
|
if (m_iCurWorkType < 0 || m_iCurWorkType == (int)WorkType.WT_NOTHING)
|
|
m_iCurWorkType = (int)WorkType.WT_NOTHING;
|
|
else
|
|
return;
|
|
}
|
|
|
|
StartWorkByID(iNewWork, dwParam);
|
|
}
|
|
|
|
public void StopWork(int iWorkType)
|
|
{
|
|
}
|
|
public void StartWorkByID(int iWorkID, uint dwParam)
|
|
{
|
|
// Ignore all message if this NPC is dead.
|
|
// if (IsDead())
|
|
// return;
|
|
|
|
switch (iWorkID)
|
|
{
|
|
case (int)WorkID.WORK_STAND: StartWork_Stand(dwParam); break;
|
|
case (int)WorkID.WORK_FIGHT: StartWork_Fight(dwParam); break;
|
|
case (int)WorkID.WORK_SPELL: StartWork_Spell(dwParam); break;
|
|
case (int)WorkID.WORK_DEAD: StartWork_Dead(dwParam); break;
|
|
case (int)WorkID.WORK_MOVE: StartWork_Move(dwParam); break;
|
|
case (int)WorkID.WORK_POLICYACTION: StartWork_PolicyAction(dwParam); break;
|
|
}
|
|
|
|
// if (iWorkID != WORK_MOVE) m_iPassiveMove = 0;
|
|
m_iCurWork = iWorkID;
|
|
}
|
|
public void StartWork_Stand(uint dwParam)
|
|
{
|
|
if (!m_bStartFight)
|
|
{
|
|
if (IsMonsterOrPet())
|
|
PlayModelAction((int)NPCActionIndex.ACT_STAND);
|
|
else
|
|
PlayModelAction((int)NPCActionIndex.ACT_NPC_STAND);
|
|
}
|
|
}
|
|
|
|
public void StartWork_Fight(uint dwParam)
|
|
{
|
|
m_nFightTimeLeft = (int)dwParam;
|
|
|
|
BMLogger.LogError("HoangDev: StartWork_Fight");
|
|
// dwParam được dùng như “thời gian chiến đấu còn lại”
|
|
//m_nFightTimeLeft = (int)dwParam;
|
|
// Không play animation ở đây vì animation được điều khiển bởi message tấn công
|
|
|
|
|
|
}
|
|
|
|
public void StartWork_Spell(uint dwParam)
|
|
{
|
|
// Trong C++ không có xử lý gì, giữ nguyên
|
|
}
|
|
|
|
public void StartWork_Dead(uint dwParam)
|
|
{
|
|
if (IsMonsterOrPet())
|
|
PlayModelAction((int)NPCActionIndex.ACT_DIE);
|
|
else
|
|
PlayModelAction((int)NPCActionIndex.ACT_NPC_DIE);
|
|
}
|
|
|
|
public void StartWork_Move(uint dwParam)
|
|
{
|
|
m_bStartFight = false;
|
|
/*
|
|
if (m_pNPCModelPolicy != null && m_pNPCModelPolicy.IsModelLoaded())
|
|
{
|
|
ClearComActFlag(true);
|
|
|
|
// Khi NPC đang di chuyển thì bỏ trace brush (không cần va chạm)
|
|
ReleaseTraceBrush();
|
|
}*/
|
|
}
|
|
|
|
public void StartWork_PolicyAction(uint dwParam)
|
|
{
|
|
/* if (m_pPolicyAction == null)
|
|
m_pPolicyAction = new CECPolicyAction();
|
|
|
|
// Trong C++: m_pPolicyAction->Init((const S2C::cmd_object_start_play_action *)dwParam);
|
|
// Sang C#: dwParam không thể cast trực tiếp. Bạn sẽ cần truyền object phù hợp vào.
|
|
m_pPolicyAction.Init((S2C.cmd_object_start_play_action)dwParam);
|
|
m_pPolicyAction.Tick(0);
|
|
m_nPolicyActionIntervalTimer = 0;
|
|
|
|
CheckStartPolicyAction();*/
|
|
}
|
|
|
|
public bool ShouldPlayNewActionFor(int iMoveMode)
|
|
{
|
|
if (m_pNPCModelPolicy.IsPlayingAction())
|
|
{
|
|
int iAction = GetMoveAction(iMoveMode);
|
|
return !m_pNPCModelPolicy.IsPlayingAction(iAction)
|
|
&& m_pNPCModelPolicy.HasAction(iAction);
|
|
}
|
|
return false;
|
|
}
|
|
public int GetMoveAction(int iMoveMode)
|
|
{
|
|
if (iMoveMode == (int)GPMoveMode.GP_MOVE_RUN || iMoveMode == (int)GPMoveMode.GP_MOVE_RETURN)
|
|
{
|
|
if (IsMonsterOrPet())
|
|
return (int)NPCActionIndex.ACT_RUN;
|
|
else
|
|
return (int)NPCActionIndex.ACT_NPC_RUN;
|
|
}
|
|
else
|
|
{
|
|
if (IsMonsterOrPet())
|
|
return (int)NPCActionIndex.ACT_WALK;
|
|
else
|
|
return (int)NPCActionIndex.ACT_NPC_WALK;
|
|
}
|
|
}
|
|
public bool IsMonsterOrPet() { return IsMonsterNPC() || IsPetNPC(); }
|
|
public bool IsPetNPC() { return (int)Class_ID.OCID_PET == m_iCID; }
|
|
public void PlayMoveAction(int iMoveMode)
|
|
{
|
|
BrewMonster.BMLogger.LogError($"HoangDev: PlayMoveAction {iMoveMode}");
|
|
// Play run or walk aciton
|
|
if (iMoveMode == (int)GPMoveMode.GP_MOVE_RUN || iMoveMode == (int)GPMoveMode.GP_MOVE_RETURN)
|
|
{
|
|
PlayModelAction((int)NPCActionIndex.ACT_WALK, false);
|
|
/*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 = false)
|
|
{
|
|
m_iAction = iAction;
|
|
/* if (IsDead())
|
|
{
|
|
// ¼ì²éËÀÍö״̬£¬ÒÔÆÁ±ÎÆäËü¶¯×÷
|
|
// ËÀÍö״̬ÉèÖúó£¬Ö»ÔÊÐí²¥·ÅËÀÍö¶¯×÷
|
|
// Íæ¼Ò¹¥»÷NPCʱ£¬»áÊ×ÏȲ¥·ÅÍæ¼ÒµÄ¹¥»÷¶¯×÷£¬Íê³Éºó²¥·Å¹ÖÎïµÄÊÜÉ˶¯×÷
|
|
// µ«ÔÚÍæ¼Ò×ÔÉíµÄ¹¥»÷¶¯×÷δÍê³Éʱ£¬¿ÉÄܾÍÊÕµ½NPCËÀÍöµÄÏûÏ¢
|
|
// Òò´Ë£¬¿ÉÄÜ»áÏȲ¥·ÅËÀÍö¶¯×÷£¬¶øºóÓÖ²¥·ÅÊÜÉ˶¯×÷£¬µ¼ÖÂËÀÍö¶¯×÷²»ÄÜÕý³£²¥·ÅµÄ±íÏÖ½á¹û
|
|
// ²»·ûºÏÆÚÍû
|
|
if (IsMonsterOrPet())
|
|
{
|
|
if (iAction != CECNPC::ACT_DIE)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iAction != CECNPC::ACT_NPC_DIE)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}*/
|
|
m_pNPCModelPolicy.PlayModelAction(iAction, bRestart);
|
|
}
|
|
bool IsDisappearing() { return m_DisappearCnt == 0 ? true : false; }
|
|
|
|
public float GetTouchRadius() { return m_fTouchRad; }
|
|
|
|
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;
|
|
|
|
// Get NPC's real position on server
|
|
public A3DVECTOR3 GetServerPos()
|
|
{
|
|
return EC_Utility.ToA3DVECTOR3(m_vServerPos);
|
|
}
|
|
|
|
// Get master id
|
|
public int GetMasterID() { return m_idMaster; }
|
|
// Is monster in invader camp in battle ?
|
|
public virtual bool IsInBattleInvaderCamp() { return false; }
|
|
// Is monster in defender camp in battle ?
|
|
public virtual bool IsInBattleDefenderCamp() { return false; }
|
|
// Get role in battle
|
|
public virtual int GetRoleInBattle() { return 0; }
|
|
public int GetOwnerFaction() { return m_idOwnerFaction; }
|
|
|
|
public bool IsFactionPVPMineCar()
|
|
{
|
|
//if (const MONSTER_ESSENCE* pMonsterEssence = GetMonsterEssence()){
|
|
// return (pMonsterEssence.faction & (1 << 19)) != 0;
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
public bool IsFactionPVPMineBase()
|
|
{
|
|
//if (const MONSTER_ESSENCE *pMonsterEssence = GetMonsterEssence()){
|
|
// return (pMonsterEssence->faction & (1 << 20)) != 0;
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
// Get NPC ID
|
|
public int GetNPCID() { return m_NPCInfo.nid; }
|
|
}
|
|
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;
|
|
}
|
|
} |