Files
test/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs
T
2025-10-25 17:28:46 +07:00

1456 lines
47 KiB
C#

using Animancer;
using BrewMonster;
using BrewMonster.Managers;
using BrewMonster.Scripts;
using CSNetwork;
using CSNetwork.GPDataType;
using ModelRenderer.Scripts.Common;
using System;
using System.IO;
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_iCurWork;
protected uint m_nPolicyActionIntervalTimer;
protected CECCounter m_DisappearCnt = new CECCounter();
protected CECCounter m_TransCnt = new CECCounter();
protected int m_StartDisappearCnt;
protected bool m_bAboutToDie;
protected Vector3 m_vStopDir;
protected ROLEEXTPROP m_ExtProps;
protected CECNPCModelPolicy m_pNPCModelPolicy;
protected CECPolicyAction m_pPolicyAction;
public int m_iMMIndex;
public int m_idAttackTarget;
public UINPC m_npcUI;
[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.SetPeriod(5000);
m_StartDisappearCnt = 0;
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);
m_npcUI = GetComponentInChildren<UINPC>();
// 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)
{
BMLogger.LogError($" PlayerNPCState.GP_STATE_NPC_NAME ");
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);
BMLogger.LogError($"HoangDev:m_npcUI = {m_npcUI}, NPC Name = " + m_strName);
if (m_npcUI != null)
{
m_npcUI.SetName(m_strName);
}
}
}
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 void ProcessMessage(ECMSG Msg)
{
switch (Msg.dwMsg)
{
case long value when value == EC_MsgDef.MSG_NM_NPCATKRESULT: OnMsgNPCAtkResult(Msg); break;
}
}
public void OnMsgAttackHostResult(int idHost, int nDamage, int nFlag, int nSpeed)
{
BMLogger.LogError("HoangDev: OnMsgAttackHostResultNPC");
if (!IsDead())
{
// 🔹 Quay mặt về mục tiêu
NPCTurnFaceTo(idHost);
m_idAttackTarget = idHost;
// 🔹 Nếu là quái vật thì bắt đầu “work” đánh nhau
if (IsMonsterNPC())
{
var pMonster = this as CECMonster;
var pMonsterEssence = (MONSTER_ESSENCE?)pMonster?.GetDBEssence();
if (pMonsterEssence != null)
{
StartWork(
(int)WorkType.WT_NORMAL,
(int)WorkID.WORK_FIGHT,
(uint)(pMonsterEssence.Value.attack_speed * 1000)
);
}
}
}
// 🔹 Phát hiệu ứng tấn công
PlayAttackEffect(idHost, 0, 0, nDamage, (uint)nFlag, nSpeed);
// 🔹 Nếu là pet thì phát câu thoại chiến đấu
/* if (!IsDead() && IsPetNPC())
{
OnPetSays(CECPetWords.TW_FIGHT);
}*/
}
public void OnMsgNPCAtkResult(ECMSG msg)
{
var pCmd = GPDataTypeHelper.FromBytes<cmd_object_atk_result>(msg.dwParam1 as byte[]);
if (!IsDead())
{
// Face to target
NPCTurnFaceTo(pCmd.target_id);
m_idAttackTarget = pCmd.target_id;
// Start a fight work
if (IsMonsterNPC())
{
var pMonster = (CECMonster)this;
var pMonsterEssence = pMonster.GetDBEssence();
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_FIGHT, (uint)(pMonsterEssence.attack_speed * 1000));
}
}
var pHost = GameController.Instance.GetHostPlayer();
int iDamage = -1;
// Attacker is host's pet
if (IsPetNPC() && GetMasterID() == pHost.GetCharacterID())
{
iDamage = pCmd.damage;
}
// Attack target is host's pet
else if (GPDataTypeHelper.ISNPCID(pCmd.target_id))
{
var pTarget = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(pCmd.target_id);
if (pTarget != null && pTarget.GetMasterID() == pHost.GetCharacterID())
iDamage = pCmd.damage;
}
// Common melee attack result, idSkill = 0
PlayAttackEffect(
pCmd.target_id,
0, // idSkill
0, // dwUserData
iDamage,
(uint)pCmd.attack_flag,
pCmd.speed * 50
);
/* if (!IsDead() && IsPetNPC())
{
OnPetSays(CECPetWords.TW_FIGHT);
}*/
}
public void PlayAttackEffect(
int idTarget,
int idSkill,
int skillLevel,
int nDamage,
uint dwModifier,
int nAttackSpeed,
int nSection = 0)
{
BMLogger.LogError("HoangDev: PlayAttackEffectNPC");
if (m_pNPCModelPolicy == null)
return;
m_bStartFight = true;
var pAttacksMan = CECAttacksMan.Instance;
// --- Melee Attack ---
if (idSkill == 0)
{
int nTimeFly = 10;
if (IsMonsterNPC())
{
var pData = ((CECMonster)this).GetDBEssence();
if (pData.short_range_mode == 0) // cận chiến
nTimeFly = 700;
}
/* if (IsPetNPC())
{
var pData = ((CECPet)this).GetDBEssence() as PET_ESSENCE;
if (pData != null && !string.IsNullOrEmpty(pData.file_gfx_short))
nTimeFly = 700;
}*/
if (pAttacksMan.FindAttackByAttacker(m_NPCInfo.nid) != null)
{
// signal early attack event
ClearComActFlag(true);
}
// Melee attack
var pAttack = pAttacksMan.AddMeleeAttack(
m_NPCInfo.nid,
idTarget,
0,
dwModifier,
nDamage,
nTimeFly
);
if (!IsDead()
&& (dwModifier & (uint)MOD.MOD_RETORT) == 0
&& (dwModifier & (uint)MOD.MOD_ATTACK_AURA) == 0
&& PlayAttackAction(nAttackSpeed, pAttack)
&& (dwModifier & (uint)MOD.MOD_BEAT_BACK) == 0)
{
// normal flow
}
else
{
pAttack.m_bSignaled = true;
}
}
/* // --- Skill Attack ---
else
{
if (skillLevel == 0)
{
if (m_pCurSkill != null)
skillLevel = m_pCurSkill.GetSkillLevel();
else
skillLevel = 1;
}
// Try to find if there is already a skill attack event in attackman
var attackerEvents = pAttacksMan.FindAttackByAttacker(m_NPCInfo.nid);
if (attackerEvents != null)
{
var pAttack = attackerEvents.Find(idSkill, nSection);
if (pAttack != null)
{
// thêm target vào skill attack hiện có
pAttack.AddTarget(idTarget, dwModifier, nDamage);
return;
}
else
{
attackerEvents.Signal();
}
}
// Begin a new skill attack
var pNewAttack = pAttacksMan.AddSkillAttack(
m_NPCInfo.nid,
m_idCurSkillTarget,
idTarget,
0,
idSkill,
skillLevel,
dwModifier,
nDamage
);
if (pNewAttack != null)
{
pNewAttack.SetSkillSection(nSection);
if (!IsDead()
&& (dwModifier & CECAttackEvent.MOD_RETORT) == 0
&& (dwModifier & CECAttackEvent.MOD_ATTACK_AURA) == 0
&& PlaySkillAttackAction(idSkill, nAttackSpeed, nSection, ref pNewAttack.m_bSignaled)
&& (dwModifier & CECAttackEvent.MOD_BEAT_BACK) == 0)
{
// normal case
}
else
{
pNewAttack.m_bSignaled = true;
}
}
}*/
}
private bool PlayAttackAction(int nAttackSpeed, CECAttackEvent attackevent)
{
BMLogger.LogError("HoangDev: PlayAttackActionNPC");
return m_pNPCModelPolicy.PlayAttackAction(nAttackSpeed, attackevent);
}
void NPCTurnFaceTo(int idTarget, float dwTime = 0)
{
if (IsDirFixed())
{
return;
}
// tower in war can not turn face to.
/* if (IsMonsterNPC())
{
int role_in_war = ((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;
}
if (IsDisappearing())
{
// When m_DisappearCnt passed half length, start changing model's transparence
float dwOldCnt = m_DisappearCnt.GetCounter();
m_DisappearCnt.IncCounter(Time.deltaTime * 1000);
float dwHalf = m_DisappearCnt.GetPeriod() / 2;
if (dwOldCnt < dwHalf && m_DisappearCnt.GetCounter() >= dwHalf)
StartAdjustTransparency(-1.0f, 1.0f, dwHalf);
}
else
{
StartAdjustTransparency(-1.0f, GetTransparentLimit(), 500);
}
}
public void DestroySelf()
{
Destroy(gameObject);
}
float GetTransparentLimit()
{
if ((m_dwStates & (uint)PlayerNPCState.GP_STATE_INVISIBLE) != 0)
{
return 0.7f;//ÒþÉí
}
else if (!IsSelectable())
{
return 0.5f;//ÎÞ·¨Ñ¡ÖÐ
}
return -1.0f;
}
bool StartAdjustTransparency(float fCur, float fDest, float dwTime)
{
// use current value for starting
/*if (fCur < 0.0f)
{
if (!m_pNPCModelPolicy->GetTransparent(fCur))
{
fCur = m_fCurTrans;
}
}
if (fDest < 0.f) fDest = 0.f;
// ignore the invalid params
if (dwTime == 0 || fabs(fDest - m_fDstTrans) < 0.0001f || fabs(fDest - fCur) < 0.0001f)
return false;
m_fCurTrans = fCur;
m_fDstTrans = fDest;
m_fTransDelta = (fDest - m_fCurTrans) / dwTime;
m_TransCnt.SetPeriod(dwTime);
m_TransCnt.Reset();*/
return true;
}
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 void Release()
{
// Release current skill if it exists
/* if (m_pCurSkill)
{
delete m_pCurSkill;
m_pCurSkill = NULL;
}*/
// Clear extend states before model is released
/* ClearShowExtendStates();
::memset(m_aExtStates, 0, sizeof(m_aExtStates));
m_aIconStates.clear();*/
m_pNPCModelPolicy = null;
/*if (m_pPateName)
{
delete m_pPateName;
m_pPateName = NULL;
}
if (m_pPateLastWords1)
{
delete m_pPateLastWords1;
m_pPateLastWords1 = NULL;
}
if (m_pPateLastWords2)
{
delete m_pPateLastWords2;
m_pPateLastWords2 = NULL;
}
if (m_pBubbleTexts)
{
delete m_pBubbleTexts;
m_pBubbleTexts = NULL;
}*/
m_pPolicyAction = null;
m_nPolicyActionIntervalTimer = 0;
/* for (MOEffectMAP::iterator it = m_mapMOEffect.begin(); it != m_mapMOEffect.end(); ++it)
{
A3DGFXExMan* pGFXExMan = g_pGame->GetA3DGFXExMan();
pGFXExMan->CacheReleasedGfx(it->second);
}
m_mapMOEffect.clear();*/
}
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 bool IsAboutToDie() { return m_bAboutToDie; }
public void Killed(bool bDelay)
{
ClearComActFlag(true);
m_dwStates |= PlayerNPCState.GP_STATE_CORPSE;
// No delay die, enter disappear process immediately
if (!bDelay)
Disappear();
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_DEAD, m_dwStates);
SetUseGroundNormal(true);
}
public void Disappear()
{
BMLogger.Log("CECNPC::Disappear");
m_DisappearCnt.SetCounter(1);
PlayModelAction((int)NPCActionIndex.ACT_NPC_DISAPPEAR);
}
public void ClearComActFlag(bool bSignalCurrent)
{
EventBus.PublishChannel(m_NPCInfo.nid, new ClearComActFlagEvent(true));
}
public void Damaged(int iDamage, uint dwModifier/* 0 */)
{
if (iDamage == -1 || iDamage == -2)
{
// when else player hit this npc iDamage is -1,
// so if iDamage is -1 we will shoud the wounded animation
if (iDamage == -1 && !m_bStartFight)
PlayModelAction((int)NPCActionIndex.ACT_WOUNDED);
DamageTextManager.Instance.ShowDamageText(transform.position, iDamage, Color.red, 1.0f);
/*if ((dwModifier & (uint)MOD.MOD_IMMUNE) != 0 *//* && !IsImmuneDisable()*//*)
textma
else if (dwModifier & CECAttackEvent::MOD_NULLITY)
BubbleText(BUBBLE_INVALIDHIT, 0);
else if (dwModifier & CECAttackEvent::MOD_ENCHANT_FAILED)
BubbleText(BUBBLE_LOSE, 0);
else if (dwModifier & CECAttackEvent::MOD_SUCCESS)
BubbleText(BUBBLE_SUCCESS, 0);
else if (dwModifier & CECAttackEvent::MOD_DODGE_DEBUFF)
BubbleText(BUBBLE_DODGE_DEBUFF, 0);*/
}
else
{
// this message is related to the host, so we should show a pop up message
// Popup a damage decal
/* bool bDeadlyStrike = (dwModifier & CECAttackEvent::MOD_CRITICAL_STRIKE) ? true : false;
bool bRetort = (dwModifier & CECAttackEvent::MOD_RETORT) ? true : false;*/
if (iDamage > 0)
{
if (!m_bStartFight)
PlayModelAction((int)NPCActionIndex.ACT_WOUNDED);
DamageTextManager.Instance.ShowDamageText(transform.position, iDamage, Color.red, 1.0f);
/* int p1 = 0;
if (bDeadlyStrike)
p1 |= 0x0001;
else if (bRetort)
p1 |= 0x0002;*/
/* if (dwModifier & CECAttackEvent::MOD_REBOUND)
BubbleText(BUBBLE_REBOUND, (DWORD)iDamage);
else if (dwModifier & CECAttackEvent::MOD_BEAT_BACK)
BubbleText(BUBBLE_BEAT_BACK, (DWORD)iDamage);
else
BubbleText(BUBBLE_DAMAGE, (DWORD)iDamage, p1);*/
}
/* else if ((dwModifier & CECAttackEvent::MOD_IMMUNE) && !IsImmuneDisable())
BubbleText(BUBBLE_IMMUNE, 0);
else if (dwModifier & CECAttackEvent::MOD_NULLITY)
BubbleText(BUBBLE_INVALIDHIT, 0);
else if (dwModifier & CECAttackEvent::MOD_ENCHANT_FAILED)
BubbleText(BUBBLE_LOSE, 0);
else if (dwModifier & CECAttackEvent::MOD_SUCCESS)
BubbleText(BUBBLE_SUCCESS, 0);
else
BubbleText(BUBBLE_HITMISSED, 0);*/
}
}
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
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 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;
}
BMLogger.LogError($" CECNPC.QueueLoadNPCModel szModelFile= {szModelFile.ToLower()} ");
var model = NPCBuilder.Instance.GetModelByPath(szModelFile.ToLower());
if (model == null) {
BMLogger.LogError($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
return; }
var monsterModel = Instantiate(model, transform);
monsterModel.SetActive(true);
var npcVisual = GetComponent<NPCVisual>();
npcVisual.InitNPCEventDoneHandler(m_NPCInfo);
//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 bool ShouldDisappear() { return m_DisappearCnt.IsFull(); }
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)
{
// 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(); }
bool IsMonsterNPC() { return (int)Class_ID.OCID_MONSTER == m_iCID; }
bool IsPetNPC() { return (int)Class_ID.OCID_PET == m_iCID; }
public bool IsDead() { return (m_dwStates & PlayerNPCState.GP_STATE_CORPSE) != 0; }
public void PlayMoveAction(int iMoveMode)
{
//BrewMonster.BMLogger.LogError($"HoangDev: PlayMoveAction {iMoveMode}");
if (iMoveMode == (int)GPMoveMode.GP_MOVE_RUN || iMoveMode == (int)GPMoveMode.GP_MOVE_RETURN)
{
if (IsMonsterOrPet())
PlayModelAction((int)NPCActionIndex.ACT_RUN, false);
else
PlayModelAction((int)NPCActionIndex.ACT_NPC_RUN, false);
}
else
{
if (IsMonsterOrPet())
PlayModelAction((int)NPCActionIndex.ACT_WALK, false);
else
PlayModelAction((int)NPCActionIndex.ACT_NPC_WALK, false);
}
}
public void PlayModelAction(int iAction, bool bRestart = false)
{
m_iAction = iAction;
if (IsDead())
{
// ¼ì²éËÀÍö״̬£¬ÒÔÆÁ±ÎÆäËü¶¯×÷
// ËÀÍö״̬ÉèÖúó£¬Ö»ÔÊÐí²¥·ÅËÀÍö¶¯×÷
// Íæ¼Ò¹¥»÷NPCʱ£¬»áÊ×ÏȲ¥·ÅÍæ¼ÒµÄ¹¥»÷¶¯×÷£¬Íê³Éºó²¥·Å¹ÖÎïµÄÊÜÉ˶¯×÷
// µ«ÔÚÍæ¼Ò×ÔÉíµÄ¹¥»÷¶¯×÷δÍê³Éʱ£¬¿ÉÄܾÍÊÕµ½NPCËÀÍöµÄÏûÏ¢
// Òò´Ë£¬¿ÉÄÜ»áÏȲ¥·ÅËÀÍö¶¯×÷£¬¶øºóÓÖ²¥·ÅÊÜÉ˶¯×÷£¬µ¼ÖÂËÀÍö¶¯×÷²»ÄÜÕý³£²¥·ÅµÄ±íÏÖ½á¹û
// ²»·ûºÏÆÚÍû
if (IsMonsterOrPet())
{
if (iAction != (int)NPCActionIndex.ACT_DIE)
{
return;
}
}
else
{
if (iAction != (int)NPCActionIndex.ACT_NPC_DIE)
{
return;
}
}
}
m_pNPCModelPolicy.PlayModelAction(iAction, bRestart, null);
}
bool IsDisappearing() { return m_DisappearCnt.GetCounter() != 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; }
// Get distance to host player
public float GetDistToHost() { return m_fDistToHost; }
public float GetDistToHostH() { return m_fDistToHostH; }
}
public struct ClearComActFlagEvent
{
public bool BSignalCurrent;
public ClearComActFlagEvent(bool bSignalCurrentnid)
{
BSignalCurrent = bSignalCurrentnid;
}
}
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 struct NameNPCGotEvent
{
public string NpcName;
public NameNPCGotEvent(string npcName)
{
NpcName = npcName;
}
}
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;
}
}