2002 lines
68 KiB
C#
2002 lines
68 KiB
C#
using BrewMonster;
|
||
using BrewMonster.Managers;
|
||
using BrewMonster.Scripts;
|
||
using CSNetwork;
|
||
using CSNetwork.GPDataType;
|
||
using ModelRenderer.Scripts.Common;
|
||
using System;
|
||
using System.Threading.Tasks;
|
||
using BrewMonster.Scripts.Chat;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using BrewMonster.Scripts.Skills;
|
||
public class CECNPC : CECObject
|
||
{
|
||
[SerializeField] 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 = 1f;
|
||
protected A3DVECTOR3 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 A3DVECTOR3 m_vStopDir;
|
||
protected ROLEEXTPROP m_ExtProps;
|
||
protected ROLEBASICPROP m_BasicProps;
|
||
protected CECNPCModelPolicy m_pNPCModelPolicy;
|
||
protected CECPolicyAction m_pPolicyAction;
|
||
public int m_iMMIndex;
|
||
|
||
// [中文] 上次从服务器收到的完整扩展状态位图(用于 SetNewExtendStates 中的 Array.Copy)
|
||
// [English] Last full extended-state bitmask received from server (copied in SetNewExtendStates)
|
||
protected uint[] m_aExtStates = new uint[(int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT];
|
||
|
||
// [中文] 当前已显示的扩展状态位图(与新数据做差分,控制 GFX 增删)
|
||
// [English] Currently displayed extended-state bitmask (diffed against new data to add/remove GFX)
|
||
protected uint[] m_aExtStatesShown = new uint[(int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT];
|
||
public int m_idAttackTarget;
|
||
/// <summary>Buff/debuff icon states from server (ICON_STATE_NOTIFY). Same role as CECPlayer.m_aIconStates.</summary>
|
||
public List<IconState> m_aIconStates = new List<IconState>();
|
||
protected UINPC m_npcUI;
|
||
private CECModel m_pNPCCECModel; // CECModel instance for hook system / 用于挂点系统的CECModel实例
|
||
CECCounter m_IdleCnt = new CECCounter();
|
||
protected float m_nFightTimeLeft = 0; // Work Fight time left
|
||
|
||
[SerializeField] protected float m_fMoveSpeed;
|
||
[SerializeField] protected CharacterController _characterController;
|
||
[SerializeField] protected bool isDebug;
|
||
[SerializeField] protected NPCVisual npcVisual;
|
||
GameObject m_modelVisual = null;
|
||
|
||
protected static CECStringTab m_ActionNames;
|
||
/* public string NameNPC => m_strName;
|
||
public string ROLEBASICPROP => m_strName;*/
|
||
|
||
|
||
protected virtual void Awake()
|
||
{
|
||
m_DisappearCnt = new CECCounter();
|
||
m_IdleCnt = new CECCounter();
|
||
m_TransCnt = new CECCounter();
|
||
m_aWorks = new int[4];
|
||
}
|
||
|
||
public virtual void SetUpCECNPC(CECNPCMan pNPCMan)
|
||
{
|
||
base.SetUpCECObject();
|
||
m_vServerPos = new A3DVECTOR3();
|
||
m_iCID = (int)Class_ID.OCID_NPC;
|
||
m_DisappearCnt.SetPeriod(5000);
|
||
m_IdleCnt.SetPeriod(25000);
|
||
m_IdleCnt.IncCounter(UnityEngine.Random.Range(0, 6000));
|
||
m_pNPCModelPolicy = new CECNPCModelDefaultPolicy(this);
|
||
}
|
||
|
||
|
||
public string GetName()
|
||
{
|
||
return m_strName;
|
||
}
|
||
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 = 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_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);
|
||
|
||
npcVisual = GetComponent<NPCVisual>();
|
||
if (npcVisual == null)
|
||
{
|
||
BMLogger.LogError("HoangDev 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, (int)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 = CECGameRun.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 void ProcessMessage(ECMSG Msg)
|
||
{
|
||
switch (Msg.dwMsg)
|
||
{
|
||
case long value when value == EC_MsgDef.MSG_NM_NPCATKRESULT: OnMsgNPCAtkResult(Msg); break;
|
||
case long value when value == EC_MsgDef.MSG_NM_NPCSTARTPLAYACTION: OnMsgNPCStartPlayAction(Msg); break;
|
||
case long value when value == EC_MsgDef.MSG_NM_NPCEXTSTATE: OnMsgNPCExtState(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_NPCCASTSKILL: OnMsgNPCCastSkill(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_ENCHANTRESULT: OnMsgNPCEnchantResult(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_NPCROOT: OnMsgNPCRoot(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_NPCSKILLRESULT: OnMsgNPCSkillResult(Msg); break;
|
||
case long value when value == EC_MsgDef.MSG_NM_NPCLEVELUP: OnMsgNPCLevel(Msg); break;
|
||
case long value when value == EC_MsgDef.MSG_NM_NPCINVISIBLE: OnMsgNPCInvisible(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_NPCSTOPPLAYACTION: OnMsgNPCStopPlayAction(Msg); break;
|
||
//case long value when value == EC_MsgDef.MSG_NM_MULTIOBJECT_EFFECT: OnMsgNPCMultiObjectEffect(Msg); break;
|
||
}
|
||
}
|
||
|
||
private void OnMsgNPCExtState(ECMSG Msg)
|
||
{
|
||
if (Convert.ToInt32(Msg.dwParam2) == CommandID.ICON_STATE_NOTIFY)
|
||
{
|
||
var cmd = new cmd_icon_state_notify();
|
||
if (!cmd.Initialize((byte[])Msg.dwParam1))
|
||
return;
|
||
if (cmd.id != GetNPCID())
|
||
return;
|
||
m_aIconStates = cmd.states ?? new List<IconState>();
|
||
if (m_aIconStates.Count > 1)
|
||
{
|
||
m_aIconStates.Sort((a, b) =>
|
||
{
|
||
if (a.id < b.id) return -1;
|
||
if (a.id > b.id) return 1;
|
||
return 0;
|
||
});
|
||
}
|
||
}
|
||
if (Convert.ToInt32(Msg.dwParam2) == CommandID.UPDATE_EXT_STATE)
|
||
{
|
||
cmd_update_ext_state pCmd = GPDataTypeHelper.FromBytes<cmd_update_ext_state>((byte[])Msg.dwParam1);
|
||
if (pCmd.id == m_NPCInfo.nid)
|
||
SetNewExtendStates(0, pCmd.states, (int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT);
|
||
}
|
||
// UPDATE_EXT_STATE for NPC is handled separately from icon strip; extend if NPC state GFX is ported.
|
||
}
|
||
|
||
private void OnMsgNPCLevel(ECMSG Msg)
|
||
{
|
||
cmd_level_up pCmd = GPDataTypeHelper.FromBytes<cmd_level_up>((byte[])Msg.dwParam1);
|
||
//m_pNPCModelPolicy.PlayGfx(res_GFXFile(RES_GFX_LEVELUP), NULL);
|
||
}
|
||
|
||
// [中文] 处理服务器发来的扩展状态更新消息,驱动状态 GFX 的添加与移除
|
||
// [English] Handle server ext-state update message — drives state GFX add/remove
|
||
|
||
|
||
// [中文] 更新扩展状态并刷新 GFX 显示
|
||
// [English] Update the ext-state arrays and refresh GFX display
|
||
public void SetNewExtendStates(int start, uint[] pData, int count)
|
||
{
|
||
if (pData == null || start + count > (int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT)
|
||
return;
|
||
|
||
ShowExtendStates(start, pData, count);
|
||
Array.Copy(pData, 0, m_aExtStates, start, count);
|
||
}
|
||
|
||
// [中文] 清除所有已显示的状态效果 GFX(传入全零位图触发全量移除)
|
||
// [English] Clear all currently displayed state-effect GFX (pass all-zero bitmap to remove everything)
|
||
private void ClearShowExtendStates()
|
||
{
|
||
ShowExtendStates(0, new uint[(int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT],
|
||
(int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT, true);
|
||
}
|
||
|
||
// [中文] 对比旧位图与新位图,逐位差分,新增或移除对应状态效果 GFX
|
||
// [English] Diff old vs new bitmask bit-by-bit and add/remove state-effect GFX accordingly
|
||
private void ShowExtendStates(int start, uint[] pData, int count, bool bIgnoreOptimize = false)
|
||
{
|
||
if (pData == null || start + count > (int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT)
|
||
return;
|
||
|
||
// [中文] 模型必须已加载才能挂载 GFX
|
||
// [English] Model must be loaded before GFX can be attached
|
||
if (!m_pNPCModelPolicy.IsModelLoaded())
|
||
return;
|
||
|
||
// [中文] 策划联入\状态效果\ —— 状态效果 GFX 的基础路径(与 C++ 保持一致)
|
||
// [English] Designer-linked state-effect GFX base path (matches C++)
|
||
const string szBasePath = "gfx/策划联入/状态效果/";
|
||
|
||
const int bitSize = sizeof(uint) * 8;
|
||
for (int index = 0; index < count; index++)
|
||
{
|
||
int idState = index + start;
|
||
for (int i = 0; i < bitSize; i++)
|
||
{
|
||
uint dwMask = 1u << i;
|
||
uint dwFlag1 = m_aExtStatesShown[idState] & dwMask; // currently shown
|
||
uint dwFlag2 = pData[index] & dwMask; // incoming
|
||
|
||
// [中文] 两者相同(都激活或都未激活),无需处理
|
||
// [English] Both unchanged — nothing to do
|
||
if ((dwFlag1 == 0 && dwFlag2 == 0) || (dwFlag1 != 0 && dwFlag2 != 0))
|
||
continue;
|
||
|
||
// [中文] 查询可见状态定义(NPC 固定使用 profession 127)
|
||
// [English] Query visible state definition (NPCs always use profession 127)
|
||
VisibleState pvs = GNET.QueryVisibleState(127, i + idState * bitSize);
|
||
if (pvs == null)
|
||
continue;
|
||
|
||
string strEffect = pvs.GetEffect();
|
||
if (string.IsNullOrEmpty(strEffect))
|
||
continue;
|
||
|
||
string strGFXFile = szBasePath + strEffect;
|
||
|
||
if (dwFlag1 != 0)
|
||
{
|
||
// [中文] 移除旧状态效果 GFX
|
||
// [English] Remove old state GFX
|
||
m_pNPCModelPolicy.RemoveGfx(strGFXFile, "HH_头顶" /*pvs.GetHH()*/);
|
||
}
|
||
else
|
||
{
|
||
// [中文] 添加新状态效果 GFX
|
||
// [English] Add new state GFX
|
||
BMLogger.Log($"[HoangDev NPC StateGFX] Playing: {strGFXFile}, hook: {pvs.GetHH()}");
|
||
m_pNPCModelPolicy.PlayGfx(strGFXFile,"HH_头顶" /*pvs.GetHH()*/);
|
||
}
|
||
}
|
||
}
|
||
|
||
Array.Copy(pData, 0, m_aExtStatesShown, start, count);
|
||
}
|
||
|
||
private void OnMsgNPCInvisible(ECMSG Msg)
|
||
{
|
||
cmd_object_invisible pCmd = GPDataTypeHelper.FromBytes<cmd_object_invisible>((byte[])Msg.dwParam1);
|
||
|
||
if (pCmd.invisible_degree > 0)
|
||
{
|
||
m_dwStates |= PlayerNPCState.GP_STATE_INVISIBLE;
|
||
}
|
||
else
|
||
{
|
||
m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_INVISIBLE;
|
||
}
|
||
}
|
||
|
||
private void OnMsgNPCStartPlayAction(ECMSG Msg)
|
||
{
|
||
if (IsInPolicyAction())
|
||
CheckStopPolicyAction();
|
||
cmd_object_start_play_action cmd = GPDataTypeHelper.FromBytes<cmd_object_start_play_action>((byte[])Msg.dwParam1);
|
||
StartWork((int)WorkType.WT_INTERRUPT, (int)WorkID.WORK_POLICYACTION, 0, cmd);
|
||
}
|
||
|
||
private void CheckStopPolicyAction()
|
||
{
|
||
if (!IsInPolicyAction()) return;
|
||
|
||
WorkFinished((int)WorkID.WORK_POLICYACTION);
|
||
|
||
if (m_pPolicyAction != null)
|
||
{
|
||
m_pNPCModelPolicy.StopChannelAction();
|
||
m_pPolicyAction = null;
|
||
m_nPolicyActionIntervalTimer = 0;
|
||
}
|
||
}
|
||
|
||
private bool IsInPolicyAction()
|
||
{
|
||
return m_pPolicyAction != null;
|
||
}
|
||
|
||
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 = CECGameRun.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)
|
||
{
|
||
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)
|
||
{
|
||
return m_pNPCModelPolicy.PlayAttackAction(nAttackSpeed, attackevent);
|
||
}
|
||
public void NPCTurnFaceTo(int idTarget, float dwTime = 0.3f)
|
||
{
|
||
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);
|
||
}
|
||
|
||
protected override void Update()
|
||
{
|
||
base.Update();
|
||
switch (m_iCurWork)
|
||
{
|
||
case (int)WorkID.WORK_MOVE: TickWork_Move(Time.deltaTime); break;
|
||
case (int)WorkID.WORK_STAND: TickWork_Stand(Time.deltaTime); break;
|
||
case (int)WorkID.WORK_FIGHT: TickWork_Fight(Time.deltaTime); break;
|
||
case (int)WorkID.WORK_SPELL: TickWork_Spell(Time.deltaTime); break;
|
||
case (int)WorkID.WORK_DEAD: TickWork_Dead(Time.deltaTime); break;
|
||
//case (int)WorkID.WORK_POLICYACTION: TickWork_PolicyAction(Time.deltaTime); break;
|
||
}
|
||
|
||
// Calculate distance to host player
|
||
CECHostPlayer pHost = EC_ManMessageMono.Instance.EC_ManPlayer.GetHostPlayer();
|
||
if (pHost /*&& pHost.IsSkeletonReady()*/)
|
||
{
|
||
m_fDistToHost = CalcDist(pHost.GetPos(), true);
|
||
m_fDistToHostH = CalcDist(pHost.GetPos(), false);
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
private void TickWork_Dead(float deltaTime)
|
||
{
|
||
}
|
||
|
||
private void TickWork_Spell(float deltaTime)
|
||
{
|
||
|
||
}
|
||
|
||
private void TickWork_Fight(float deltaTime)
|
||
{
|
||
m_nFightTimeLeft -= deltaTime;
|
||
|
||
if (m_nFightTimeLeft < 0)
|
||
{
|
||
m_nFightTimeLeft = 0;
|
||
WorkFinished((int)WorkID.WORK_FIGHT);
|
||
return;
|
||
}
|
||
|
||
NPCTurnFaceTo(m_idAttackTarget, 100);
|
||
|
||
A3DVECTOR3 vDir = m_vServerPos - GetPos();
|
||
float fDist = vDir.Normalize();
|
||
if (fDist > 0.0001f)
|
||
{
|
||
float fMoveDist = 10.0f * deltaTime * 0.001f;
|
||
if (fMoveDist > fDist)
|
||
fMoveDist = fDist;
|
||
|
||
SetPos(EC_Utility.ToVector3(GetPos() + vDir * fMoveDist));
|
||
}
|
||
}
|
||
|
||
private void TickWork_Stand(float deltaTime)
|
||
{
|
||
if (m_IdleCnt.IncCounter(deltaTime * 1000))
|
||
{
|
||
m_IdleCnt.Reset();
|
||
|
||
if (IsMonsterOrPet())
|
||
{
|
||
PlayModelAction((int)NPCActionIndex.ACT_IDLE);
|
||
if (IsPetNPC() && !IsDead() && !IsDisappearing())
|
||
{
|
||
//OnPetSays(CECPetWords::TW_REST);
|
||
}
|
||
}
|
||
else
|
||
PlayModelAction((int)NPCActionIndex.ACT_NPC_IDLE1 + UnityEngine.Random.Range(0, 2));
|
||
}
|
||
}
|
||
|
||
public void DestroySelf()
|
||
{
|
||
PrefabPoolManager.Instance.Despawn(gameObject);
|
||
//Destroy(gameObject);
|
||
}
|
||
public 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_aIconStates?.Clear();
|
||
// [中文] 模型释放前先移除所有状态效果 GFX,并重置位图
|
||
// [English] Remove all state-effect GFX and reset bitmasks before the model is released
|
||
ClearShowExtendStates();
|
||
Array.Clear(m_aExtStates, 0, m_aExtStates.Length);
|
||
|
||
m_pNPCModelPolicy = null;
|
||
PoolManager.Instance.Despawn(m_modelVisual);
|
||
/*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)
|
||
{
|
||
if (IsDisappearing())
|
||
return true;
|
||
|
||
bool bRet = false;
|
||
A3DVECTOR3 vCurPos = EC_Utility.ToA3DVECTOR3(transform.position);
|
||
|
||
if (m_bStopMove)
|
||
{
|
||
// Tính hướng đến serverPos
|
||
A3DVECTOR3 vDir = m_vServerPos - vCurPos;
|
||
float fDist = vDir.Normalize();
|
||
A3DVECTOR3 vPos = vDir * m_fMoveSpeed * deltaTime;
|
||
A3DVECTOR3 exPPos = vPos + vCurPos;
|
||
float fMoveDelta = Vector3.Magnitude(EC_Utility.ToVector3(exPPos) - EC_Utility.ToVector3(vCurPos));
|
||
BMLogger.LogMono(this, $"HoangDev: m_vServerPos:{m_vServerPos},vCurPos:{vCurPos},vDir:{vDir},fDist:{fDist},vPos:{vPos},fMoveDelta:{fMoveDelta}");
|
||
|
||
if (fMoveDelta >= fDist)
|
||
{
|
||
// Already at destination
|
||
_characterController.enabled = false;
|
||
SetPos(EC_Utility.ToVector3(m_vServerPos));
|
||
_characterController.enabled = true;
|
||
bRet = true;
|
||
}
|
||
else
|
||
{
|
||
_characterController.enabled = false;
|
||
SetPos(EC_Utility.ToVector3(exPPos));
|
||
_characterController.enabled = true;
|
||
FaceDirectionImmediate(EC_Utility.ToVector3(vPos));
|
||
}
|
||
}
|
||
else // đang move bình thường
|
||
{
|
||
float fDist = Vector3.Magnitude(EC_Utility.ToVector3(m_vServerPos) - EC_Utility.ToVector3(vCurPos));
|
||
|
||
if (IsLag(fDist))
|
||
{
|
||
// Teleport nếu lag xa
|
||
_characterController.enabled = false;
|
||
SetPos(EC_Utility.ToVector3(m_vServerPos));
|
||
_characterController.enabled = true;
|
||
|
||
m_vStopDir = EC_Utility.ToA3DVECTOR3(transform.forward);
|
||
return true;
|
||
}
|
||
|
||
A3DVECTOR3 dir = m_vMoveDir;
|
||
dir.Normalize();
|
||
var vDir = EC_Utility.ToVector3(dir);
|
||
Vector3 moveDelta = vDir * m_fMoveSpeed * deltaTime;
|
||
if (_characterController == null)
|
||
{
|
||
BMLogger.LogError("CECNPC.MovingTo _characterController == null " + gameObject.name);
|
||
}
|
||
_characterController.Move(moveDelta);
|
||
// Thêm xoay theo trục Y
|
||
FaceDirectionImmediate(vDir);
|
||
}
|
||
return bRet;
|
||
}
|
||
/// <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()
|
||
{
|
||
FadeOut();
|
||
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);
|
||
FLoatingTextManager.Instance.ShowText(transform.position, iDamage, Color.red, 1.0f, ImageResType.NUM_IMAGE, this);
|
||
|
||
if ((dwModifier & (uint)MOD.MOD_IMMUNE) != 0 /* && !IsImmuneDisable()*/)
|
||
BubbleText((int)MOD.MOD_IMMUNE, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_NULLITY) != 0)
|
||
BubbleText((int)MOD.MOD_NULLITY, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_ENCHANT_FAILED) != 0)
|
||
BubbleText((int)MOD.MOD_ENCHANT_FAILED, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_SUCCESS) != 0)
|
||
BubbleText((int)MOD.MOD_SUCCESS, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_DODGE_DEBUFF) != 0)
|
||
BubbleText((int)MOD.MOD_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 & (uint)MOD.MOD_CRITICAL_STRIKE) != 0;
|
||
bool bRetort = (dwModifier & (uint)MOD.MOD_RETORT) != 0;
|
||
|
||
if (iDamage > 0)
|
||
{
|
||
if (!m_bStartFight)
|
||
PlayModelAction((int)NPCActionIndex.ACT_WOUNDED);
|
||
|
||
// Damage number + tint/icons: BubbleText → ShowText only (avoid duplicate red plain text + orange BubbleText).
|
||
// 伤害数字与图标只走 BubbleText,避免先红字再橙字飘两次。
|
||
int p1 = 0;
|
||
if (bDeadlyStrike)
|
||
p1 |= 0x0001;
|
||
else if (bRetort)
|
||
p1 |= 0x0002;
|
||
|
||
if ((dwModifier & (uint)MOD.MOD_REBOUND) != 0)
|
||
BubbleText((int)BubbleTextType.BUBBLE_REBOUND, iDamage);
|
||
else if ((dwModifier & (uint)MOD.MOD_BEAT_BACK) != 0)
|
||
BubbleText((int)BubbleTextType.BUBBLE_BEAT_BACK, iDamage);
|
||
else
|
||
BubbleText((int)BubbleTextType.BUBBLE_DAMAGE, iDamage, p1);
|
||
}
|
||
else if ((dwModifier & (uint)MOD.MOD_IMMUNE) != 0 /*&& !IsImmuneDisable()*/)
|
||
BubbleText((int)BubbleTextType.BUBBLE_IMMUNE, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_NULLITY) != 0)
|
||
BubbleText((int)BubbleTextType.BUBBLE_INVALIDHIT, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_ENCHANT_FAILED) != 0)
|
||
BubbleText((int)BubbleTextType.BUBBLE_LOSE, 0);
|
||
else if ((dwModifier & (uint)MOD.MOD_SUCCESS) != 0)
|
||
BubbleText((int)BubbleTextType.BUBBLE_SUCCESS, 0);
|
||
else
|
||
BubbleText((int)BubbleTextType.BUBBLE_HITMISSED, 0);
|
||
}
|
||
}
|
||
public void BubbleText(int iIndex, int dwNum, int p1 = 0/* 0 */)
|
||
{
|
||
//FLoatingTextManager.Instance.ShowText(transform.position, dwNum, Color.red, 1.0f, (uint)iIndex);
|
||
Color displayColor = new Color(237, 56, 0);
|
||
ImageResType imageResType = ImageResType.NUM_IMAGE;
|
||
switch (iIndex)
|
||
{
|
||
case (int)BubbleTextType.BUBBLE_DAMAGE:
|
||
if ((p1 & 0x0001) != 0)
|
||
imageResType = ImageResType.IMG_DEADLYSTRIKE;
|
||
else if ((p1 & 0x0002) != 0)
|
||
imageResType = ImageResType.IMG_RETORT;
|
||
break;
|
||
|
||
case (int)BubbleTextType.BUBBLE_HITMISSED:
|
||
imageResType = ImageResType.IMG_HITMISSED;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_INVALIDHIT:
|
||
imageResType = ImageResType.IMG_INVALIDHIT;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_IMMUNE:
|
||
imageResType = ImageResType.IMG_IMMUNE;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_HPWARN:
|
||
imageResType = ImageResType.IMG_HPWARN;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_LOSE:
|
||
imageResType = ImageResType.IMG_ATTACKLOSE;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_SUCCESS:
|
||
imageResType = ImageResType.IMG_SUCCESS;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_REBOUND:
|
||
imageResType = ImageResType.IMG_REBOUND;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_BEAT_BACK:
|
||
imageResType = ImageResType.IMG_BEAT_BACK;
|
||
break;
|
||
case (int)BubbleTextType.BUBBLE_DODGE_DEBUFF:
|
||
imageResType = ImageResType.IMG_DODGE_DEBUFF;
|
||
break;
|
||
default:
|
||
imageResType = ImageResType.NUM_IMAGE;
|
||
break;
|
||
}
|
||
FLoatingTextManager.Instance.ShowText(transform.position, dwNum, displayColor, 1.0f, imageResType, this);
|
||
}
|
||
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);
|
||
|
||
bool foundNextWork = false;
|
||
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);
|
||
foundNextWork = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// If no lower priority work found, default to WORK_STAND (idle state)
|
||
// This matches C++ behavior where NPC should stand when movement finishes
|
||
if (!foundNextWork && !IsDead())
|
||
{
|
||
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_STAND, 0);
|
||
}
|
||
|
||
// 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 async Task QueueLoadNPCModel()
|
||
{
|
||
/* if (ShouldUseMasterModel())
|
||
{
|
||
if (GetMaster())
|
||
{
|
||
return; // ÄÜ»ñÈ¡½ÇɫģÐÍʱ£¬µ½Ï¸ö Tick ¼ÓÔØÄ£ÐÍ
|
||
} // ÎÞ·¨»ñÈ¡½Çɫʱ¡¢ÔÝʱʹÓà NPC Ä£ÐÍ
|
||
}*/
|
||
int tid = 0;
|
||
string szModelFile = "";
|
||
if (!GetVisibleModel(out tid, out szModelFile))
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
szModelFile = AFile.NormalizePath(szModelFile.ToLower(), true);
|
||
m_modelVisual = await NPCBuilder.Instance.GetModelByPath(szModelFile);
|
||
if (m_modelVisual == null)
|
||
{
|
||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||
m_modelVisual.name = szModelFile;
|
||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||
}
|
||
|
||
//var monsterModel = Instantiate(model, transform);
|
||
m_modelVisual.transform.SetParent(transform, false);
|
||
m_modelVisual.SetActive(true);
|
||
var npcVisual = GetComponent<NPCVisual>();
|
||
npcVisual?.InitNPCEventDoneHandler(m_NPCInfo);
|
||
|
||
// UINPC.Start can run before async model instantiate; refresh anchor once SMR hierarchy exists.
|
||
// UINPC.Start可能在异步模型实例化之前执行;SMR层次就绪后刷新名牌锚点。
|
||
m_npcUI?.RefreshWorldNameplatePosition();
|
||
|
||
//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)
|
||
{
|
||
BMLogger.LogMono(this, "CECNPC::StopMoveTo");
|
||
if (IsDead())
|
||
return;
|
||
BMLogger.LogMono(this, "CECNPC::StopMoveTo not dead");
|
||
|
||
int iMoveMode = cmd.move_mode & (int)GPMoveMode.GP_MOVE_MASK;
|
||
|
||
cmd.dest = new A3DVECTOR3(
|
||
(float)Math.Round(cmd.dest.x, 4),
|
||
(float)Math.Round(cmd.dest.y, 4),
|
||
(float)Math.Round(cmd.dest.z, 4)
|
||
);
|
||
m_vMoveDir = (cmd.dest) - EC_Utility.ToA3DVECTOR3(transform.position);
|
||
m_bStopMove = true;
|
||
m_fMoveSpeed = EC_Utility.FIX8TOFLOAT(cmd.sSpeed);
|
||
m_vServerPos = 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())
|
||
{
|
||
transform.forward = EC_Utility.ToVector3(m_vStopDir);
|
||
}
|
||
|
||
float fDist = 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
|
||
A3DVECTOR3 vDirH = (m_vMoveDir);
|
||
vDirH.y = 0.0f;
|
||
vDirH.Normalize();
|
||
if (DotProduct(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));
|
||
if (!IsDirFixed())
|
||
{
|
||
SetDestDirAndUp(m_vStopDir, g_vAxisY, 150);
|
||
}
|
||
WorkFinished((int)WorkID.WORK_MOVE);
|
||
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;
|
||
|
||
if (iMoveMode == (int)GPMoveMode.GP_MOVE_FALL || iMoveMode == (int)GPMoveMode.GP_MOVE_FLYFALL)
|
||
m_cdr.bTraceGround = false;
|
||
}
|
||
|
||
if (!IsDirFixed() && m_iPassiveMove == 0)
|
||
{
|
||
A3DVECTOR3 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)
|
||
{
|
||
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_MOVE);
|
||
|
||
if (m_iPassiveMove == 0)
|
||
{
|
||
PlayMoveAction(iMoveMode);
|
||
}
|
||
}
|
||
}
|
||
public static float DotProduct(A3DVECTOR3 v1, A3DVECTOR3 v2)
|
||
{
|
||
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
|
||
}
|
||
public bool GetCHAABB(ref A3DAABB aabb)
|
||
{
|
||
return m_pNPCModelPolicy.GetCHAABB(ref aabb);
|
||
}
|
||
public void SetPos(Vector3 pos)
|
||
{
|
||
transform.position = pos;
|
||
}
|
||
public void MoveTo(cmd_object_move Cmd)
|
||
{
|
||
if (Cmd.use_time == 0)
|
||
return;
|
||
var dest = Cmd.dest;
|
||
m_vServerPos = (dest);
|
||
m_vMoveDir = dest - EC_Utility.ToA3DVECTOR3(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:
|
||
{
|
||
var pos = EC_Utility.ToVector3(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, cmd_object_start_play_action cmd_Object_Start_Play_Action = default)
|
||
{
|
||
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, cmd_Object_Start_Play_Action);
|
||
}
|
||
public bool ShouldDisappear() { return m_DisappearCnt.IsFull(); }
|
||
public void StopWork(int iWorkType)
|
||
{
|
||
}
|
||
public void StartWorkByID(int iWorkID, uint dwParam, cmd_object_start_play_action cmd_Object_Start_Play_Action = default)
|
||
{
|
||
// 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(cmd_Object_Start_Play_Action); break;
|
||
}
|
||
|
||
// if (iWorkID != WORK_MOVE) m_iPassiveMove = 0;
|
||
m_iCurWork = iWorkID;
|
||
}
|
||
public void StartWork_Stand(uint dwParam)
|
||
{
|
||
if (isDebug)
|
||
{
|
||
BMLogger.LogError("StartWork_Stand ");
|
||
}
|
||
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(cmd_object_start_play_action cmd_Object_Start_Play_Action)
|
||
{
|
||
/* if (m_pPolicyAction == null)
|
||
m_pPolicyAction = new CECPolicyAction();
|
||
|
||
|
||
m_pPolicyAction.Init((cmd_object_start_play_action)cmd_Object_Start_Play_Action);
|
||
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 IsMonsterNPC() { return (int)Class_ID.OCID_MONSTER == m_iCID; }
|
||
public 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 SetWorldHealthImage(float currenthealth, float maxHealth)
|
||
{
|
||
m_npcUI.gameObject.SetActive(true);
|
||
m_npcUI.SetHealthImage(currenthealth / maxHealth);
|
||
m_npcUI.SetHealthText($"{currenthealth}/{maxHealth}");
|
||
}
|
||
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_npcUI.gameObject.SetActive(false);
|
||
}
|
||
if (isDebug)
|
||
{
|
||
BMLogger.LogError("PlayModelAction iAction :" + iAction);
|
||
}
|
||
m_pNPCModelPolicy.PlayModelAction(iAction, bRestart, null);
|
||
}
|
||
|
||
bool IsDisappearing()
|
||
{
|
||
if(m_DisappearCnt != null)
|
||
{
|
||
return m_DisappearCnt.GetCounter() != 0 ? true : false;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public int GetTemplateID() { return m_NPCInfo.tid; }
|
||
|
||
public float GetTouchRadius() { return m_fTouchRad; }
|
||
|
||
bool IsLag(float fDist)
|
||
{
|
||
return m_iPassiveMove == 0 && fDist > MAX_LAGDIST;
|
||
}
|
||
public INFO GetNPCInfo() { return m_NPCInfo; }
|
||
|
||
[Serializable]
|
||
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 (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; }
|
||
|
||
// Get NPC name color
|
||
public virtual uint GetNameColor() { return 0xffffff00; }
|
||
|
||
CECPateText m_pPateLastWords1;
|
||
CECPateText m_pPateLastWords2;
|
||
public void SetLastSaidWords(string words, int timeShow = -1)
|
||
{
|
||
if (m_pPateLastWords1 == null)
|
||
{
|
||
m_pPateLastWords1 = new CECPateText();
|
||
}
|
||
|
||
if (m_pPateLastWords2 == null)
|
||
{
|
||
m_pPateLastWords2 = new CECPateText();
|
||
}
|
||
|
||
if (words == null)
|
||
return;
|
||
|
||
int len1 = m_pPateLastWords1.SetText(words, true, out string strWords, false, null);
|
||
|
||
/*
|
||
if (len1 < strWords.Length)
|
||
m_pPateLastWords2.SetText(strWords.Substring(len1), true, true);
|
||
else
|
||
m_pPateLastWords2.Clear();
|
||
|
||
m_iLastSayCnt = timeShow > 0 ? timeShow : 20000;*/
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get NPC's CECModel instance
|
||
/// 获取NPC的CECModel实例
|
||
/// </summary>
|
||
/// <returns>CECModel instance or null / CECModel实例,未找到返回null</returns>
|
||
public CECModel GetModel()
|
||
{
|
||
// Initialize CECModel if not already created
|
||
// 如果尚未创建,则初始化CECModel
|
||
if (m_pNPCCECModel == null)
|
||
{
|
||
// Find the model GameObject (typically a child of this NPC)
|
||
// 查找模型GameObject(通常是此NPC的子对象)
|
||
GameObject modelObject = null;
|
||
|
||
// Try to find model in children (where NPC models are typically placed)
|
||
// 尝试在子对象中查找模型(NPC模型通常放置在那里)
|
||
NPCVisual npcVisual = GetComponent<NPCVisual>();
|
||
if (npcVisual != null)
|
||
{
|
||
// Find SkeletonBuilder which is typically on the model GameObject
|
||
// 查找通常在模型GameObject上的SkeletonBuilder
|
||
SkeletonBuilder skeleton = GetComponentInChildren<SkeletonBuilder>(true);
|
||
if (skeleton != null)
|
||
{
|
||
modelObject = skeleton.gameObject;
|
||
}
|
||
}
|
||
|
||
// If no model found, try to find any child with SkeletonBuilder
|
||
// 如果未找到模型,尝试查找任何带有SkeletonBuilder的子对象
|
||
if (modelObject == null)
|
||
{
|
||
SkeletonBuilder skeleton = GetComponentInChildren<SkeletonBuilder>(true);
|
||
if (skeleton != null)
|
||
{
|
||
modelObject = skeleton.gameObject;
|
||
}
|
||
}
|
||
|
||
if (modelObject != null)
|
||
{
|
||
// Create and initialize CECModel instance
|
||
// 创建并初始化CECModel实例
|
||
m_pNPCCECModel = new CECModel();
|
||
m_pNPCCECModel.SetTransform(modelObject.transform);
|
||
|
||
// Find and set SkeletonBuilder
|
||
// 查找并设置SkeletonBuilder
|
||
SkeletonBuilder skeletonBuilder = modelObject.GetComponent<SkeletonBuilder>();
|
||
if (skeletonBuilder == null)
|
||
{
|
||
skeletonBuilder = modelObject.GetComponentInChildren<SkeletonBuilder>(true);
|
||
}
|
||
|
||
if (skeletonBuilder != null)
|
||
{
|
||
m_pNPCCECModel.SetSkeletonBuilder(skeletonBuilder);
|
||
m_pNPCCECModel.InitializeSkeletonBuilder();
|
||
}
|
||
}
|
||
}
|
||
|
||
return m_pNPCCECModel;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get hook Transform by name (convenience method)
|
||
/// 根据名称获取挂点变换(便捷方法)
|
||
/// </summary>
|
||
/// <param name="hookName">Hook name / 挂点名称</param>
|
||
/// <param name="recursive">Search recursively / 递归搜索</param>
|
||
/// <returns>Hook Transform or null / 挂点变换,未找到返回null</returns>
|
||
public Transform GetHook(string hookName, bool recursive = true)
|
||
{
|
||
CECModel model = GetModel();
|
||
return model?.GetHook(hookName, recursive);
|
||
}
|
||
public enum BubbleTextType
|
||
{
|
||
BUBBLE_DAMAGE = 0,
|
||
BUBBLE_HITMISSED,
|
||
BUBBLE_INVALIDHIT,
|
||
BUBBLE_IMMUNE,
|
||
BUBBLE_HPWARN,
|
||
BUBBLE_LOSE,
|
||
BUBBLE_SUCCESS,
|
||
BUBBLE_REBOUND, // ����
|
||
BUBBLE_BEAT_BACK, // ����
|
||
BUBBLE_DODGE_DEBUFF,
|
||
};
|
||
}
|
||
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,
|
||
};
|
||
//BUBBLE TEXT TYPE FOR NPC. HOST IS DIFFERENT FROM NPC.
|
||
|
||
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;
|
||
}
|
||
}
|