From 5581c616c17fd910a6f9b24b1ee328708fe45239 Mon Sep 17 00:00:00 2001 From: Tungdv Date: Thu, 7 May 2026 17:58:42 +0700 Subject: [PATCH 1/5] fix: update move when done load terrain and lit. --- .../Scripts/World/AddressableObject.cs | 6 ++-- .../Scripts/World/LitModelHolder.cs | 31 ++++++++++++++++--- .../Scripts/World/TerrainHolder.cs | 28 +++++++++++++++-- Assets/Scripts/CECHostPlayer.World.cs | 2 ++ Assets/Scripts/CECHostPlayer.cs | 4 ++- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/World/AddressableObject.cs b/Assets/PerfectWorld/Scripts/World/AddressableObject.cs index ae97a06678..f0ad2fabd1 100644 --- a/Assets/PerfectWorld/Scripts/World/AddressableObject.cs +++ b/Assets/PerfectWorld/Scripts/World/AddressableObject.cs @@ -1,6 +1,8 @@ using BrewMonster.Scripts; using Cysharp.Threading.Tasks; using UnityEngine; +using System; + #if UNITY_EDITOR using UnityEditor; @@ -35,7 +37,7 @@ namespace BrewMonster } - public async UniTask LoadAsset() + public async UniTask LoadAsset(Action actDone = null) { if (string.IsNullOrEmpty(assetPath)) { @@ -50,7 +52,7 @@ namespace BrewMonster _isLoading = true; int requestId = ++_loadRequestId; var model = await AddressableManager.Instance.LoadPrefabAsync(assetPath); - + actDone?.Invoke(); // Object might have been destroyed while awaiting. if (this == null || gameObject == null) { diff --git a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs index 8651386e9a..b4e286ca33 100644 --- a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -67,7 +68,13 @@ public class LitModelHolder : MonoSingleton // if load flag is set, process the load and unload objects. if (_needToProcessLoadAndUnload) { - ProcessLoadAndUnloadObjects(); + ProcessLoadAndUnloadObjects(() => + { + if (_hostPlayer != null) + { + _hostPlayer.isLitToReady = true; + } + }); _needToProcessLoadAndUnload = false; } } @@ -284,14 +291,30 @@ public class LitModelHolder : MonoSingleton } _needToProcessLoadAndUnload = true; + //if (_hostPlayer != null) + //{ + // Debug.LogError("ProcessLoadAndUnload Lit _hostPlayer.isLitToReady = false "); + // _hostPlayer.isLitToReady = false; + //} } - private void ProcessLoadAndUnloadObjects() + private void ProcessLoadAndUnloadObjects(Action actDone) { + int begin = 0; + int end = _candidatesForLoading.Count; + Action actLoadingDone = () => + { + begin++; + Debug.LogError("ProcessLoadAndUnload Lit || begin = " + begin + " || end = " + end); + if (begin >= end) + { + actDone?.Invoke(); + } + }; // load the objects that are in the loading range long enough. (_candidateWaitTimeSeconds) foreach (var kvp in _candidatesForLoading) { - kvp.Key.LoadAsset().Forget(); + kvp.Key.LoadAsset(actLoadingDone).Forget(); _loadedObjects.Add(kvp.Key); } _candidatesForLoading.Clear(); @@ -444,4 +467,4 @@ public class LitModelHolder : MonoSingleton $"- anchorsWithMoreThanOneChild={multiChildCount}"); } #endif -} \ No newline at end of file +} diff --git a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs index d7b6142b0c..f7489116cd 100644 --- a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs @@ -48,7 +48,13 @@ namespace BrewMonster UpdateGlobalDataForStreaming(); if (_needToProcessLoadAndUnload) { - ProcessLoadAndUnload(); + ProcessLoadAndUnload(() => + { + if (_hostPlayer != null) + { + _hostPlayer.isTerrainToReady = true; + } + }); _needToProcessLoadAndUnload = false; } } @@ -174,6 +180,11 @@ namespace BrewMonster } _needToProcessLoadAndUnload = true; + //if (_hostPlayer != null) + //{ + // Debug.LogError("ProcessLoadAndUnload Terain _hostPlayer.isTerrainToReady = false"); + // _hostPlayer.isTerrainToReady = false; + //} } } @@ -181,11 +192,22 @@ namespace BrewMonster /// /// process to call Load and Unload on each addressable object that we need to. /// - private void ProcessLoadAndUnload() + private void ProcessLoadAndUnload(Action actDone) { + int begin = 0; + int end = _candidatesForLoading.Count; + Action actLoadingDone = () => + { + begin++; + Debug.LogError("ProcessLoadAndUnload Terain || begin = " + begin + " || end = " + end); + if(begin >= end) + { + actDone?.Invoke(); + } + }; for (int i = 0; i < _candidatesForLoading.Count; i++) { - _candidatesForLoading[i].LoadAsset().Forget(); + _candidatesForLoading[i].LoadAsset(actLoadingDone).Forget(); } _candidatesForLoading.Clear(); diff --git a/Assets/Scripts/CECHostPlayer.World.cs b/Assets/Scripts/CECHostPlayer.World.cs index c7839afe91..ac98b65b39 100644 --- a/Assets/Scripts/CECHostPlayer.World.cs +++ b/Assets/Scripts/CECHostPlayer.World.cs @@ -230,6 +230,8 @@ namespace BrewMonster m_MoveCtrl.SetHostLastPos(EC_Utility.ToA3DVECTOR3(vPos)); m_MoveCtrl.SetLastSevPos(EC_Utility.ToA3DVECTOR3(vPos)); + isLitToReady = false; + isTerrainToReady = false; // Update camera if available // 如果可用则更新相机 // UpdateFollowCamera(false, 10); // Uncomment if UpdateFollowCamera method exists diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index af988a5e55..9f29163251 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -169,6 +169,8 @@ namespace BrewMonster Ray ray; RaycastHit[] hits = new RaycastHit[5]; bool isDataAwaitToReady = false; + public bool isTerrainToReady = false; + public bool isLitToReady = false; private BaseVfxObject m_pSelectedGFX; private BaseVfxObject m_pHoverGFX; @@ -395,7 +397,7 @@ namespace BrewMonster { base.Update(); - if (!isDataAwaitToReady) + if (!isDataAwaitToReady || !isTerrainToReady || !isLitToReady) { return; } From 21269c6cdf407a18fb6c770b826598baf5dfb9d1 Mon Sep 17 00:00:00 2001 From: Tungdv Date: Fri, 8 May 2026 11:04:46 +0700 Subject: [PATCH 2/5] fix: Update callback loading done assset, remove anonymous function. --- .../Scripts/World/LitModelHolder.cs | 40 +++++++++--------- .../Scripts/World/TerrainHolder.cs | 41 ++++++++++--------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs index b4e286ca33..5cfe319733 100644 --- a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs @@ -68,13 +68,7 @@ public class LitModelHolder : MonoSingleton // if load flag is set, process the load and unload objects. if (_needToProcessLoadAndUnload) { - ProcessLoadAndUnloadObjects(() => - { - if (_hostPlayer != null) - { - _hostPlayer.isLitToReady = true; - } - }); + ProcessLoadAndUnloadObjects(); _needToProcessLoadAndUnload = false; } } @@ -297,24 +291,17 @@ public class LitModelHolder : MonoSingleton // _hostPlayer.isLitToReady = false; //} } + int _currentIdxAsset = 0; + int _maxIdxAsset = 0; - private void ProcessLoadAndUnloadObjects(Action actDone) + private void ProcessLoadAndUnloadObjects() { - int begin = 0; - int end = _candidatesForLoading.Count; - Action actLoadingDone = () => - { - begin++; - Debug.LogError("ProcessLoadAndUnload Lit || begin = " + begin + " || end = " + end); - if (begin >= end) - { - actDone?.Invoke(); - } - }; + _currentIdxAsset = 0; + _maxIdxAsset = _candidatesForLoading.Count; // load the objects that are in the loading range long enough. (_candidateWaitTimeSeconds) foreach (var kvp in _candidatesForLoading) { - kvp.Key.LoadAsset(actLoadingDone).Forget(); + kvp.Key.LoadAsset(CallBackAssetLoadingDone).Forget(); _loadedObjects.Add(kvp.Key); } _candidatesForLoading.Clear(); @@ -330,6 +317,19 @@ public class LitModelHolder : MonoSingleton _objectsToUnload.Clear(); } + private void CallBackAssetLoadingDone() + { + _currentIdxAsset++; + if (_currentIdxAsset >= _maxIdxAsset) + { + if (_hostPlayer != null) + { + _hostPlayer.isLitToReady = true; + } + _currentIdxAsset = -1; + _maxIdxAsset = 0; + } + } /// /// Get the Host Player instance. /// diff --git a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs index f7489116cd..edc0a669a7 100644 --- a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs @@ -48,13 +48,7 @@ namespace BrewMonster UpdateGlobalDataForStreaming(); if (_needToProcessLoadAndUnload) { - ProcessLoadAndUnload(() => - { - if (_hostPlayer != null) - { - _hostPlayer.isTerrainToReady = true; - } - }); + ProcessLoadAndUnload(); _needToProcessLoadAndUnload = false; } } @@ -189,25 +183,18 @@ namespace BrewMonster } + int _currentIdxAsset = 0; + int _maxIdxAsset = 0; /// /// process to call Load and Unload on each addressable object that we need to. /// - private void ProcessLoadAndUnload(Action actDone) + private void ProcessLoadAndUnload() { - int begin = 0; - int end = _candidatesForLoading.Count; - Action actLoadingDone = () => - { - begin++; - Debug.LogError("ProcessLoadAndUnload Terain || begin = " + begin + " || end = " + end); - if(begin >= end) - { - actDone?.Invoke(); - } - }; + _currentIdxAsset = 0; + _maxIdxAsset = _candidatesForLoading.Count; for (int i = 0; i < _candidatesForLoading.Count; i++) { - _candidatesForLoading[i].LoadAsset(actLoadingDone).Forget(); + _candidatesForLoading[i].LoadAsset(CallBackAssetLoadingDone).Forget(); } _candidatesForLoading.Clear(); @@ -218,6 +205,20 @@ namespace BrewMonster _objectsToUnload.Clear(); } + private void CallBackAssetLoadingDone() + { + _currentIdxAsset++; + if (_currentIdxAsset >= _maxIdxAsset) + { + if (_hostPlayer != null) + { + _hostPlayer.isTerrainToReady = true; + } + _currentIdxAsset = -1; + _maxIdxAsset = 0; + } + } + /// /// Main-thread anchor for terrain streaming: host position, or navigate clone when force-navigate is active (same idea as LitModelHolder). /// 地形流式锚点:普通用宿主;强制导航用导航克隆(与 LitModelHolder 一致)。 From 9be9c28582e8f44b098da3f55f81980fb54f7581 Mon Sep 17 00:00:00 2001 From: Tungdv Date: Fri, 8 May 2026 11:20:02 +0700 Subject: [PATCH 3/5] fix: update summary for funtion loading asset. --- .../PerfectWorld/Scripts/World/LitModelHolder.cs | 16 +++++++++------- .../PerfectWorld/Scripts/World/TerrainHolder.cs | 10 ++++++++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs index 5cfe319733..70038d877f 100644 --- a/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/LitModelHolder.cs @@ -285,14 +285,7 @@ public class LitModelHolder : MonoSingleton } _needToProcessLoadAndUnload = true; - //if (_hostPlayer != null) - //{ - // Debug.LogError("ProcessLoadAndUnload Lit _hostPlayer.isLitToReady = false "); - // _hostPlayer.isLitToReady = false; - //} } - int _currentIdxAsset = 0; - int _maxIdxAsset = 0; private void ProcessLoadAndUnloadObjects() { @@ -317,6 +310,14 @@ public class LitModelHolder : MonoSingleton _objectsToUnload.Clear(); } + int _currentIdxAsset = 0; // The current counter for loaded assets. + int _maxIdxAsset = 0; // Limit the number of assets that have finished loading. + + /// + /// isLitToReady is a condition used by _hostPlayer to wait until the Lit assets have finished loading. + /// This function counts the number of assets successfully loaded from the Addressable system, + /// and once the required number is reached, it sets _hostPlayer.isLitToReady = true. + /// private void CallBackAssetLoadingDone() { _currentIdxAsset++; @@ -330,6 +331,7 @@ public class LitModelHolder : MonoSingleton _maxIdxAsset = 0; } } + /// /// Get the Host Player instance. /// diff --git a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs index edc0a669a7..8cfe0ed009 100644 --- a/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs +++ b/Assets/PerfectWorld/Scripts/World/TerrainHolder.cs @@ -183,8 +183,6 @@ namespace BrewMonster } - int _currentIdxAsset = 0; - int _maxIdxAsset = 0; /// /// process to call Load and Unload on each addressable object that we need to. /// @@ -205,6 +203,14 @@ namespace BrewMonster _objectsToUnload.Clear(); } + int _currentIdxAsset = 0; // The current counter for loaded assets. + int _maxIdxAsset = 0; // Limit the number of assets that have finished loading. + + /// + /// isLitToReady is a condition used by _hostPlayer to wait until the Terrain assets have finished loading. + /// This function counts the number of assets successfully loaded from the Addressable system, + /// and once the required number is reached, it sets _hostPlayer.isLitToReady = true. + /// private void CallBackAssetLoadingDone() { _currentIdxAsset++; From c58c7be1bdbcc14d062d1175fb7175bba9a419cc Mon Sep 17 00:00:00 2001 From: vuong dinh hoang Date: Fri, 8 May 2026 16:53:47 +0700 Subject: [PATCH 4/5] done gfx monster and fix bug movable when get stuned --- Assets/PerfectWorld/Scripts/NPC/CECNPC.cs | 116 ++++++++++++++++-- .../Scripts/NPC/CECNPCModelDefaultPolicy.cs | 112 ++++++++++++++++- .../Scripts/NPC/CECNPCModelPolicy.cs | 12 ++ .../Scripts/Network/CSNetwork/GameSession.cs | 5 + Assets/Resources/DebugCmdHistory.json | 14 +-- Assets/Scripts/CECHostPlayer.cs | 4 +- 6 files changed, 245 insertions(+), 18 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs index 39aca84030..45fc25dd54 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs @@ -7,6 +7,7 @@ using ModelRenderer.Scripts.Common; using System; using System.Threading.Tasks; using BrewMonster.Scripts.Chat; +using BrewMonster.Scripts.Skills; using UnityEngine; public class CECNPC : CECObject { @@ -43,6 +44,14 @@ public class CECNPC : CECObject 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; protected UINPC m_npcUI; private CECModel m_pNPCCECModel; // CECModel instance for hook system / 用于挂点系统的CECModel实例 @@ -134,7 +143,7 @@ public class CECNPC : CECObject var ext = new uint[ojexitStateCount]; if ((info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0) r.ReadInto(ext); - //SetNewExtendStates(0, ext, ojexitStateCount ); + SetNewExtendStates(0, ext, (int)ojexitStateCount); // PET m_idMaster = 0; @@ -199,7 +208,7 @@ public class CECNPC : CECObject { 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_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; @@ -217,6 +226,99 @@ public class CECNPC : CECObject //m_pNPCModelPolicy.PlayGfx(res_GFXFile(RES_GFX_LEVELUP), NULL); } + // [中文] 处理服务器发来的扩展状态更新消息,驱动状态 GFX 的添加与移除 + // [English] Handle server ext-state update message — drives state GFX add/remove + private void OnMsgNPCExtState(ECMSG Msg) + { + if (Convert.ToInt32(Msg.dwParam2) == CommandID.UPDATE_EXT_STATE) + { + cmd_update_ext_state pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + if (pCmd.id == m_NPCInfo.nid) + SetNewExtendStates(0, pCmd.states, (int)OBJECT_EXT_STATE.OBJECT_EXT_STATE_COUNT); + } + } + + // [中文] 更新扩展状态并刷新 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, pvs.GetHH()); + } + else + { + // [中文] 添加新状态效果 GFX + // [English] Add new state GFX + BMLogger.Log($"[HoangDev NPC StateGFX] Playing: {strGFXFile}, hook: {pvs.GetHH()}"); + m_pNPCModelPolicy.PlayGfx(strGFXFile, pvs.GetHH()); + } + } + } + + Array.Copy(pData, 0, m_aExtStatesShown, start, count); + } + private void OnMsgNPCInvisible(ECMSG Msg) { cmd_object_invisible pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); @@ -652,10 +754,10 @@ public class CECNPC : CECObject }*/ // Clear extend states before model is released - /* ClearShowExtendStates(); - - ::memset(m_aExtStates, 0, sizeof(m_aExtStates)); - 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); @@ -998,7 +1100,7 @@ public class CECNPC : CECObject { return; } - + try { szModelFile = AFile.NormalizePath(szModelFile.ToLower(), true); diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs index e46ab78a6c..4db37a3d61 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs @@ -1,6 +1,10 @@ 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 @@ -12,6 +16,14 @@ public class CECNPCModelDefaultPolicy 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 _stateGfxObjects = new Dictionary(); + + // [中文] 挂点 Transform 缓存,键为挂点名称 + // [English] Hook transform cache, keyed by bone name + private Dictionary _hookCache = new Dictionary(); + public CECNPCModelDefaultPolicy(CECNPC pNPC) { m_pNPCModel = new CECModel(); @@ -73,7 +85,7 @@ public class CECNPCModelDefaultPolicy // m_CHAABB ¸ù¾Ý͹°üÉϵĶ¥µãλÖÃÀ´¼ÆËã°üΧºÐ£¬¸ü׼ȷ²¢Óë͹°ü¼ì²â±£³ÖÒ»Ö // ²»ÄÜʹÓà GetPos() À´µ÷Õû m_CHAABB ºó×÷Ϊ¼ÆËã½á¹û·µ»Ø //aabb.Center = GetPos() + A3DVECTOR3(0.0f, m_CHAABB.Extents.y, 0.0f); - //aabb.CompleteMinsMaxs(); + //aabb.CompleteMinsMaxs(); bRet = true; } return bRet; @@ -199,10 +211,106 @@ public class CECNPCModelDefaultPolicy 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; } } diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelPolicy.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelPolicy.cs index 490dcb8114..aa9a634bd8 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelPolicy.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelPolicy.cs @@ -16,5 +16,17 @@ namespace BrewMonster public abstract bool GetCHAABB(ref A3DAABB ab); public abstract void StopChannelAction(); public abstract void SetDefaultPickAABBExt(CSNetwork.GPDataType.A3DVECTOR3 vExt); + + // [中文] 模型是否已加载完毕(用于状态效果 GFX 的显示守卫) + // [English] Whether the NPC model is loaded (guard for state-effect GFX display) + public abstract bool IsModelLoaded(); + + // [中文] 在 NPC 模型挂点上播放状态效果 GFX + // [English] Play a state-effect GFX on the NPC model at the given hook + public abstract void PlayGfx(string szPath, string szHook); + + // [中文] 移除 NPC 模型上的状态效果 GFX + // [English] Remove a state-effect GFX from the NPC model + public abstract void RemoveGfx(string szPath, string szHook); } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 2f91fcde21..5d2449ed24 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -1546,6 +1546,11 @@ namespace CSNetwork break; } + case CommandID.HOST_NOTIFY_ROOT: + case CommandID.HOST_DISPEL_ROOT: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ROOTNOTIFY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; default: #if UNITY_EDITOR if (isDebug) diff --git a/Assets/Resources/DebugCmdHistory.json b/Assets/Resources/DebugCmdHistory.json index 49637133fb..2e156f9aca 100644 --- a/Assets/Resources/DebugCmdHistory.json +++ b/Assets/Resources/DebugCmdHistory.json @@ -1,5 +1,12 @@ { "items": [ + { + "header": 1992, + "param": 0, + "hasParam": false, + "describe": "Buff rage", + "lastUsedUtcTicks": 639138106383814060 + }, { "header": 8903, "param": 73125, @@ -14,13 +21,6 @@ "describe": "Up Level", "lastUsedUtcTicks": 639136623463073402 }, - { - "header": 1992, - "param": 0, - "hasParam": false, - "describe": "Buff rage", - "lastUsedUtcTicks": 639136573213427110 - }, { "header": 1988, "param": 0, diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 9f29163251..653898cb8f 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -609,7 +609,7 @@ namespace BrewMonster private void OnMsgHstRootNotify(in ECMSG Msg) { - if ((int)Msg.dwParam2 == CommandID.HOST_NOTIFY_ROOT) + if (Convert.ToInt32(Msg.dwParam2) == CommandID.HOST_NOTIFY_ROOT) { cmd_host_notify_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_dwLIES |=(uint) (1 << pCmd.type); @@ -633,7 +633,7 @@ namespace BrewMonster } } } - else if ((int)Msg.dwParam2 == CommandID.HOST_DISPEL_ROOT) + else if (Convert.ToInt32(Msg.dwParam2) == CommandID.HOST_DISPEL_ROOT) { cmd_host_dispel_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_dwLIES &=(uint) ~(1 << pCmd.type); From 8f7d7e017bc43240e95939818a216ddc05ec9c0d Mon Sep 17 00:00:00 2001 From: vuong dinh hoang Date: Fri, 8 May 2026 17:17:08 +0700 Subject: [PATCH 5/5] done gfx for monster --- Assets/PerfectWorld/Scripts/NPC/CECNPC.cs | 4 ++-- Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs | 1 + Assets/Resources/DebugCmdHistory.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs index 45fc25dd54..8d13a393e6 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs @@ -304,14 +304,14 @@ public class CECNPC : CECObject { // [中文] 移除旧状态效果 GFX // [English] Remove old state GFX - m_pNPCModelPolicy.RemoveGfx(strGFXFile, pvs.GetHH()); + 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, pvs.GetHH()); + m_pNPCModelPolicy.PlayGfx(strGFXFile,"HH_头顶" /*pvs.GetHH()*/); } } } diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs index 4db37a3d61..0afdf771a1 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPCModelDefaultPolicy.cs @@ -260,6 +260,7 @@ public class CECNPCModelDefaultPolicy 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}"); diff --git a/Assets/Resources/DebugCmdHistory.json b/Assets/Resources/DebugCmdHistory.json index 2e156f9aca..5da8d2d05a 100644 --- a/Assets/Resources/DebugCmdHistory.json +++ b/Assets/Resources/DebugCmdHistory.json @@ -5,14 +5,14 @@ "param": 0, "hasParam": false, "describe": "Buff rage", - "lastUsedUtcTicks": 639138106383814060 + "lastUsedUtcTicks": 639138321717437357 }, { "header": 8903, "param": 73125, "hasParam": true, "describe": "NoCooldown", - "lastUsedUtcTicks": 639137253458330904 + "lastUsedUtcTicks": 639138321659072960 }, { "header": 2000,