329 lines
12 KiB
C#
329 lines
12 KiB
C#
using BrewMonster;
|
||
using BrewMonster.Managers;
|
||
using CSNetwork.GPDataType;
|
||
using CSNetwork.Protocols;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using BrewMonster.Scripts;
|
||
using UnityEngine;
|
||
|
||
public class CECNPCModelDefaultPolicy
|
||
: CECNPCModelPolicy
|
||
{
|
||
CECNPC m_pNPC;
|
||
CECModel NPCModel => m_pNPC?.GetModel();
|
||
int m_nBrushes;
|
||
A3DAABB m_CHAABB; // AABB Updated with m_ppBrushes
|
||
// number of brush object used in collision
|
||
|
||
// [中文] 状态效果 GFX 对象缓存,键为 (路径 + 挂点名)
|
||
// [English] Active state-effect GFX objects, keyed by (path + hook name)
|
||
private Dictionary<string, GameObject> _stateGfxObjects = new Dictionary<string, GameObject>();
|
||
|
||
// [中文] 挂点 Transform 缓存,键为挂点名称
|
||
// [English] Hook transform cache, keyed by bone name
|
||
private Dictionary<string, Transform> _hookCache = new Dictionary<string, Transform>();
|
||
|
||
public CECNPCModelDefaultPolicy(CECNPC pNPC)
|
||
{
|
||
m_pNPC = pNPC;
|
||
}
|
||
|
||
static bool IsMoveAction(int iAction)
|
||
{
|
||
return iAction == (int)NPCActionIndex.ACT_WALK
|
||
|| iAction == (int)NPCActionIndex.ACT_RUN
|
||
|| iAction == (int)NPCActionIndex.ACT_NPC_WALK
|
||
|| iAction == (int)NPCActionIndex.ACT_NPC_RUN;
|
||
}
|
||
|
||
public string GetActionName(int iAct, bool bAttackStart = false)
|
||
{
|
||
// Tạo builder thay cho static char[128]
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
// Thêm tên base action
|
||
sb.Append(CECNPC.GetBaseActionName(iAct));
|
||
|
||
// Nếu là attack action thì thêm hậu tố
|
||
if (CECNPC.IsAttackAction(iAct))
|
||
{
|
||
sb.Append(bAttackStart ? EC_Utility.FixGBKString("Æð") : EC_Utility.FixGBKString("Âä"));
|
||
}
|
||
|
||
// Xử lý loại bỏ dấu nháy kép (nếu có)
|
||
string result = sb.ToString().Replace("\"", "");
|
||
|
||
// Trả về kết quả đã clean
|
||
return result;
|
||
}
|
||
public override void ClearComActFlag(bool bSignalCurrent)
|
||
{
|
||
if (NPCModel != null)
|
||
NPCModel.ClearComActFlag(bSignalCurrent);
|
||
}
|
||
public override bool PlayAttackAction(int nAttackSpeed, CECAttackEvent attackEvent)
|
||
{
|
||
int iAction = m_pNPC.IsMonsterOrPet()
|
||
? (int)NPCActionIndex.ACT_ATTACK1 + UnityEngine.Random.Range(0, 2)
|
||
: (int)NPCActionIndex.ACT_NPC_ATTACK;
|
||
if (!PlayModelAction(iAction, true, attackEvent))
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
public override void StopChannelAction()
|
||
{
|
||
if (NPCModel != null)
|
||
NPCModel.StopChannelAction(0, true);
|
||
}
|
||
public override bool GetCHAABB(ref A3DAABB aabb)
|
||
{
|
||
|
||
bool bRet = (false);
|
||
if (HasCHAABB())
|
||
{
|
||
aabb = m_CHAABB;
|
||
// m_CHAABB ¸ù¾Ý͹°üÉϵĶ¥µãλÖÃÀ´¼ÆËã°üΧºÐ£¬¸ü׼ȷ²¢Óë͹°ü¼ì²â±£³ÖÒ»ÖÂ
|
||
// ²»ÄÜʹÓà GetPos() À´µ÷Õû m_CHAABB ºó×÷Ϊ¼ÆËã½á¹û·µ»Ø
|
||
//aabb.Center = GetPos() + A3DVECTOR3(0.0f, m_CHAABB.Extents.y, 0.0f);
|
||
//aabb.CompleteMinsMaxs();
|
||
bRet = true;
|
||
}
|
||
return bRet;
|
||
}
|
||
bool HasCHAABB()
|
||
{
|
||
return NPCModel != null
|
||
&& NPCModel.HasCHAABB()
|
||
&& m_nBrushes > 0;
|
||
}
|
||
public override bool PlayModelAction(int iAction, bool bRestart, CECAttackEvent cECAttackEvent)
|
||
{
|
||
/* if (m_pNPCModel == null)
|
||
{
|
||
return false;
|
||
}
|
||
*/
|
||
bool result = false;
|
||
bool ignoreRef = false;
|
||
if (iAction == (int)NPCActionIndex.ACT_WOUNDED)
|
||
{
|
||
string szAct = GetActionName(iAction);
|
||
if (_npcVisual.IsAnimationExist(szAct))
|
||
{
|
||
szAct = GetActionName((int)NPCActionIndex.ACT_WOUNDED2);
|
||
}
|
||
result = _npcVisual.TryPlayAction(szAct, cECAttackEvent);
|
||
}
|
||
else if (iAction == (int)NPCActionIndex.ACT_ATTACK1 || iAction == (int)NPCActionIndex.ACT_ATTACK2)
|
||
{
|
||
//bool bHideFX = !CECOptimize::Instance().GetGFX().CanShowAttack(m_pNPC->GetNPCID(), m_pNPC->GetClassID());
|
||
result = _npcVisual.TryPlayAction(GetActionName(iAction, true), cECAttackEvent, true);
|
||
if (result)
|
||
{
|
||
string szAct = GetActionName(iAction, false);
|
||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_GUARD);
|
||
|
||
if (_npcVisual.IsAnimationExist(szAct))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||
}
|
||
if (_npcVisual.IsAnimationExist(szAct2))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||
}
|
||
}
|
||
}
|
||
else if (iAction == (int)NPCActionIndex.ACT_IDLE)
|
||
{
|
||
result = _npcVisual.TryPlayAction(GetActionName(iAction), cECAttackEvent);
|
||
if (result)
|
||
{
|
||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_STAND);
|
||
if (_npcVisual.IsAnimationExist(szAct2))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||
}
|
||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_STAND));
|
||
}
|
||
}
|
||
else if (iAction == (int)NPCActionIndex.ACT_NPC_IDLE1 || iAction == (int)NPCActionIndex.ACT_NPC_IDLE2)
|
||
{
|
||
result = _npcVisual.TryPlayAction(GetActionName(iAction), cECAttackEvent);
|
||
if (result)
|
||
{
|
||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_NPC_STAND);
|
||
if (_npcVisual.IsAnimationExist(szAct2))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||
}
|
||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_NPC_STAND));
|
||
}
|
||
}
|
||
else if (iAction == (int)NPCActionIndex.ACT_NPC_ATTACK)
|
||
{
|
||
//bool bHideFX = !CECOptimize::Instance().GetGFX().CanShowAttack(m_pNPC->GetNPCID(), m_pNPC->GetClassID());
|
||
result = _npcVisual.TryPlayAction(GetActionName(iAction, true), cECAttackEvent); ;
|
||
if (result)
|
||
{
|
||
string szAct = GetActionName(iAction, false);
|
||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_NPC_STAND);
|
||
if (_npcVisual.IsAnimationExist(szAct))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||
}
|
||
if (_npcVisual.IsAnimationExist(szAct2))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||
}
|
||
}
|
||
}
|
||
else if (iAction == (int)NPCActionIndex.ACT_COMMON_BORN)
|
||
{
|
||
result = _npcVisual.TryPlayAction(GetActionName(iAction), cECAttackEvent);
|
||
if (result)
|
||
{
|
||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_STAND);
|
||
if (_npcVisual.IsAnimationExist(szAct2))
|
||
{
|
||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||
}
|
||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_STAND));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
result = _npcVisual.TryPlayAction(
|
||
GetActionName(iAction),
|
||
cECAttackEvent,
|
||
isHit: false,
|
||
bRestart: bRestart,
|
||
bLoop: true);
|
||
}
|
||
return result;
|
||
}
|
||
public override bool IsPlayingAction()
|
||
{
|
||
return _npcVisual.IsPlayAnimation();
|
||
}
|
||
public override bool IsPlayingAction(int iAction)
|
||
{
|
||
return _npcVisual.IsPlayAnimation(GetActionName(iAction));
|
||
}
|
||
public override bool HasAction(int iAction)
|
||
{
|
||
return _npcVisual.IsAnimationExist(GetActionName(iAction));
|
||
}
|
||
public override void SetNpcVisual(NPCVisual npcVisual)
|
||
{
|
||
_npcVisual = npcVisual;
|
||
// [中文] 模型重新加载时清除挂点缓存
|
||
// [English] Clear hook cache when the NPC model is reloaded
|
||
_hookCache.Clear();
|
||
}
|
||
|
||
public override void SetDefaultPickAABBExt(A3DVECTOR3 vExt)
|
||
{
|
||
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// IsModelLoaded / PlayGfx / RemoveGfx
|
||
// -----------------------------------------------------------------------
|
||
|
||
// [中文] 判断 NPC 模型是否已加载并激活(作为状态 GFX 的前置守卫)
|
||
// [English] Returns true when the NPC model is present and the game object is active
|
||
public override bool IsModelLoaded()
|
||
{
|
||
return m_pNPC != null
|
||
&& m_pNPC.gameObject.activeInHierarchy
|
||
&& NPCModel != null
|
||
&& NPCModel.transform != null;
|
||
}
|
||
|
||
// [中文] 异步加载并挂载状态效果 GFX 到指定挂点;以 (路径+挂点) 为键去重
|
||
// [English] Async-load and attach a state-effect GFX to the given hook; deduplicated by (path+hook)
|
||
public override async void PlayGfx(string szPath, string szHook)
|
||
{
|
||
if (string.IsNullOrEmpty(szPath)) return;
|
||
|
||
string key = szPath + szHook;
|
||
if (_stateGfxObjects.ContainsKey(key)) return;
|
||
|
||
GameObject prefab = await AddressableManager.Instance.LoadPrefabAsync(szPath);
|
||
if (prefab == null)
|
||
{
|
||
BMLogger.LogWarning($"[NPC GFX] Failed to load prefab: {szPath}");
|
||
return;
|
||
}
|
||
|
||
// [中文] 检查 NPC 是否仍然存在(异步加载期间可能被销毁)
|
||
// [English] Guard against the NPC being destroyed during async load
|
||
if (m_pNPC == null) return;
|
||
|
||
// [中文] 查找挂点,找不到则回退到 NPC 根节点
|
||
// [English] Locate hook bone; fall back to NPC root if not found
|
||
Transform parent = FindHookTransform(szHook) ?? m_pNPC.transform;
|
||
|
||
GameObject vfx = Object.Instantiate(prefab, parent);
|
||
if (vfx == null) return;
|
||
|
||
vfx.transform.localPosition = Vector3.zero;
|
||
vfx.transform.localRotation = Quaternion.Euler(-180f, -90f, 90f);
|
||
_stateGfxObjects[key] = vfx;
|
||
|
||
BMLogger.Log($"[NPC GFX] Playing: {szPath}, hook: {szHook}");
|
||
}
|
||
|
||
// [中文] 销毁并移除指定状态效果 GFX
|
||
// [English] Destroy and untrack a state-effect GFX
|
||
public override void RemoveGfx(string szPath, string szHook)
|
||
{
|
||
string key = szPath + szHook;
|
||
if (_stateGfxObjects.TryGetValue(key, out GameObject vfx) && vfx != null)
|
||
{
|
||
Object.Destroy(vfx);
|
||
_stateGfxObjects.Remove(key);
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Hook lookup — child-name traversal (no SkeletonBuilder on NPC)
|
||
// 挂点查找 —— 遍历子节点名称(NPC 无 SkeletonBuilder)
|
||
// -----------------------------------------------------------------------
|
||
|
||
// [中文] 在 NPC GameObject 的子节点中按名称递归查找挂点,结果缓存以提升性能
|
||
// [English] Recursively find a child transform by name under the NPC; results are cached
|
||
private Transform FindHookTransform(string hookName)
|
||
{
|
||
if (string.IsNullOrEmpty(hookName)) return null;
|
||
|
||
if (_hookCache.TryGetValue(hookName, out Transform cached) && cached != null)
|
||
return cached;
|
||
|
||
Transform root = m_pNPC?.transform;
|
||
if (root == null) return null;
|
||
|
||
Transform found = FindChildByName(root, hookName);
|
||
if (found != null)
|
||
_hookCache[hookName] = found;
|
||
|
||
return found;
|
||
}
|
||
|
||
// [中文] 深度优先递归遍历子节点,按名称查找
|
||
// [English] Depth-first recursive child search by name
|
||
private static Transform FindChildByName(Transform parent, string name)
|
||
{
|
||
foreach (Transform child in parent)
|
||
{
|
||
if (child.name == name) return child;
|
||
Transform found = FindChildByName(child, name);
|
||
if (found != null) return found;
|
||
}
|
||
return null;
|
||
}
|
||
}
|