Files
test/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs
T
2026-05-08 16:53:47 +07:00

317 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
CECModel m_pNPCModel;
CECNPC m_pNPC;
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_pNPCModel = new CECModel();
m_pNPC = pNPC;
}
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 (m_pNPCModel != null)
{
m_pNPCModel.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 (m_pNPCModel != null)
{
m_pNPCModel.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 m_pNPCModel != null
&& m_pNPCModel.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))
{
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
}
if (_npcVisual.IsAnimationExist(szAct2))
{
m_pNPCModel.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))
{
m_pNPCModel.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))
{
m_pNPCModel.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))
{
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
}
if (_npcVisual.IsAnimationExist(szAct2))
{
m_pNPCModel.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))
{
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
}
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_STAND));
}
}
else
{
result = _npcVisual.TryPlayAction(GetActionName(iAction), cECAttackEvent);
}
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_pNPCModel != null && m_pNPC != null && m_pNPC.gameObject.activeInHierarchy;
}
// [中文] 异步加载并挂载状态效果 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;
_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;
}
}