using CSNetwork.GPDataType; using System.Text; using System; 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 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 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 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(); 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(); m_pNPCModelPolicy.SetNpcVisual(npcVisual); // 2) Cắt “đuôi” ngay sau phần cố định info_npc int fixedSize = System.Runtime.InteropServices.Marshal.SizeOf(); 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; } 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; } /// /// Xoay model NGAY LẬP TỨC theo hướng chỉ định (giữ nguyên trục Y đứng thẳng). /// 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); } } } /// /// 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. /// 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 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) PlayModelAction((int)NPCActionIndex. ACT_WOUNDED); DamageTextManager.Instance.SpawnDamage(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) { PlayModelAction((int)NPCActionIndex.ACT_WOUNDED); DamageTextManager.Instance.SpawnDamage(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 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 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.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) { // 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; } } 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}"); // 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; } // Get distance to host player public float GetDistToHost() { return m_fDistToHost; } public float GetDistToHostH() { return m_fDistToHostH; } } 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 _span; private int _offset; public ByteReader(ReadOnlySpan 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; } }