From 24213729f243ddb582ae3ac8bbe420e4f98775bb Mon Sep 17 00:00:00 2001 From: vuong dinh hoang Date: Mon, 4 May 2026 14:55:50 +0700 Subject: [PATCH] stun logic --- Assets/PerfectWorld/Scripts/Move/CECPlayer.cs | 252 +++++++++++++----- Assets/PerfectWorld/Scripts/NPC/CECModel.cs | 3 +- .../Scripts/Skills/ElementSkill.cs | 8 +- Assets/Resources/DebugCmdHistory.json | 14 +- Assets/Scripts/CECHostPlayer.cs | 34 +++ 5 files changed, 236 insertions(+), 75 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index c71f88878e..ef3b988eba 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -324,6 +324,9 @@ namespace BrewMonster public const int EFF_FACEPILL = 1; } private BaseVfxObject _levelUpVfx; + // [中文] 当前激活的状态效果 GFX,以 (路径+挂点) 为键 + // [English] Currently active state-effect GFX objects, keyed by (path + hook) + private Dictionary _stateGfxObjects = new Dictionary(); protected GameObject[] m_pModels = new GameObject[(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAX]; protected int[] m_aShapeID = new int[(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAX]; @@ -895,91 +898,103 @@ namespace BrewMonster // !CECOptimize::Instance().GetGFX().CanShowState(GetCharacterID(), GetClassID())) // return; - //static const char* szBasePath = "�߻�����\\״̬Ч��\\"; + // [中文] 策划联入\状态效果\ (状态效果 GFX 的基础路径) + // [English] Designer-integrated state effect GFX base path + const string szBasePath = "策划联入/状态效果/"; const int bitSize = sizeof(uint) * 8; - BMLogger.LogError($"[CECPlayer] ShowExtendStates: start: {start}, count: {count}, bitSize: {bitSize}"); - for (int index = 0; indexGetEffect(); - // if (!strGFXFile.GetLength()) - // continue; + string strEffect = pvs.GetEffect(); + if (string.IsNullOrEmpty(strEffect)) + continue; - // strGFXFile = szBasePath + strGFXFile; + string strGFXFile = szBasePath + strEffect; - // if (TestProcessPetCureGFX(strGFXFile, dwFlag2 != 0, i + idState*bitSize)) - // continue; + // [中文] TestProcessPetCureGFX — 宠愈 GFX 系统尚未移植,跳过(等价于始终返回 false) + // [English] TestProcessPetCureGFX — pet-cure GFX system not ported yet; skip (equivalent to always false) + // if (TestProcessPetCureGFX(strGFXFile, dwFlag2 != 0, i + idState * bitSize)) continue; if (dwFlag1 != 0) { + // [中文] 移除旧状态效果 + // [English] Remove old state GFX if (SkillGfxMan.InstanceSub != null) - { SkillGfxMan.InstanceSub.RemoveAllTraceTargetGfx(); - } - // //TODO: Implement remove old state - // CECModel *pWeapon = NULL; - // bool bLeft (false); - // if (IsWeaponHookPos(pvs->GetHH(), &bLeft, &pWeapon)) - // { - // // ��������Ч - // if (pWeapon) - // { - // const char * gfxHook = GetWeaponGFXHookPos(pWeapon, bLeft); - // pWeapon->RemoveGfx(strGFXFile, gfxHook); - // } - // } - // else + /*bool bLeft = false; + CECModel pWeapon = null; + if (IsWeaponHookPos(pvs.GetHH(), out bLeft, out pWeapon)) { - // ģ������Ч - // RemoveGfx(strGFXFile, pvs->GetHH(), PLAYERMODEL_TYPEALL); + // [中文] 武器上的效果 + // [English] GFX on weapon model + if (pWeapon != null) + { + string gfxHook = GetWeaponGFXHookPos(pWeapon, bLeft); + RemoveStateGfxFromModel(pWeapon, strGFXFile, gfxHook); + } + } + else + { + // [中文] 模型上的效果 + // [English] GFX on player model + RemoveGfx(strGFXFile, pvs.GetHH(), (uint)PLAYERMODEL_TYPE.PLAYERMODEL_TYPEALL); + }*/ + } + else + { + // [中文] 添加新状态效果 + // [English] Add new state GFX + string szHH = pvs.GetHH(); + float fScale; + + // [中文] 根据挂点类型获取模型缩放数据 + // [English] Determine GFX scale from model outer data based on hook type + CECModel majorModel = GetMajorModel()?.GetComponent(); + if (majorModel != null && szHH.Equals("HH_Head", StringComparison.OrdinalIgnoreCase)) + fScale = majorModel.GetOuterData()[0]; + else if (majorModel != null && szHH.Equals("HH_Spine", StringComparison.OrdinalIgnoreCase)) + fScale = majorModel.GetOuterData()[1]; + else + fScale = 1.0f; + + bool bLeft = false; + CECModel pWeapon = null; + if (IsWeaponHookPos(szHH, out bLeft, out pWeapon)) + { + // [中文] 武器上的效果 + // [English] GFX on weapon model + if (pWeapon != null) + { + string gfxHook = GetWeaponGFXHookPos(pWeapon, bLeft); + PlayStateGfxOnModel(pWeapon, strGFXFile, gfxHook, fScale); + } + } + else + { + // [中文] 玩家模型上的效果 + // [English] GFX on player model + PlayGfx(strGFXFile, pvs.GetHH(), fScale, (uint)PLAYERMODEL_TYPE.PLAYERMODEL_TYPEALL, true); } } - // else - // { - // // Add new state - - // const char* szHH = pvs->GetHH(); - // float fScale; - - // if (stricmp("HH_Head", szHH) == 0) - // fScale = GetMajorModel()->GetOuterData()[0]; - // else if (stricmp("HH_Spine", szHH) == 0) - // fScale = GetMajorModel()->GetOuterData()[1]; - // else - // fScale = 1.0f; - - // CECModel *pWeapon = NULL; - // bool bLeft (false); - // if (IsWeaponHookPos(pvs->GetHH(), &bLeft, &pWeapon)) - // { - // if (pWeapon) - // { - // const char *gfxHook = GetWeaponGFXHookPos(pWeapon, bLeft); - // pWeapon->PlayGfx(strGFXFile, gfxHook, fScale); - // } - // } - // else - // { - // PlayGfx(strGFXFile, pvs->GetHH(), fScale, PLAYERMODEL_TYPEALL, true); - // } - // } } } @@ -996,6 +1011,68 @@ namespace BrewMonster int bitOffset = n % bitSize; return (m_aExtStates[index] & (1 << bitOffset)) != 0; } + + // [中文] 判断挂点是否为武器挂点,并返回对应武器模型和左右手标志 + // [English] Check if hook pos is a weapon hook; output the corresponding weapon model and left-hand flag + protected bool IsWeaponHookPos(string szHH, out bool bLeft, out CECModel pWeapon) + { + bLeft = false; + pWeapon = null; + if (string.IsNullOrEmpty(szHH)) return false; + + // [中文] 左手武器挂点(手持或肩部) + // [English] Left-hand weapon hook positions (held or shoulder) + if (szHH.Equals("HH_lefthandweapon", StringComparison.OrdinalIgnoreCase) || + szHH.Equals("HH_leftsword", StringComparison.OrdinalIgnoreCase)) + { + bLeft = true; + pWeapon = GetLeftHandWeapon(); + return true; + } + + // [中文] 右手武器挂点(手持、肩部或镰刀) + // [English] Right-hand weapon hook positions (held, shoulder, or sickle/nickle) + if (szHH.Equals("HH_righthandweapon", StringComparison.OrdinalIgnoreCase) || + szHH.Equals("HH_rightsword", StringComparison.OrdinalIgnoreCase) || + szHH.Equals("HH_weapon", StringComparison.OrdinalIgnoreCase)) + { + pWeapon = GetRightHandWeapon(); + return true; + } + + return false; + } + + // [中文] 获取左手武器模型(武器 CECModel 未接入时返回 null) + // [English] Get left-hand weapon model (returns null until weapon CECModel tracking is wired) + protected virtual CECModel GetLeftHandWeapon() => null; + + // [中文] 获取右手武器模型(武器 CECModel 未接入时返回 null) + // [English] Get right-hand weapon model (returns null until weapon CECModel tracking is wired) + protected virtual CECModel GetRightHandWeapon() => null; + + // [中文] 获取武器上用于挂载 GFX 的挂点名称 + // [English] Get the GFX hook position name on the weapon model + protected string GetWeaponGFXHookPos(CECModel pModel, bool bLeft) + { + // [中文] 武器 CECModel 挂点逻辑未接入,暂时返回 null + // [English] Weapon CECModel hook logic not wired yet; return null for now + return null; + } + + // [中文] 从玩家模型上移除状态效果 GFX + // [English] Remove a state-effect GFX from the player model + protected void RemoveGfx(string szPath, string szHook, uint iShapeTypeMask) + { + string key = szPath + szHook; + if (_stateGfxObjects.TryGetValue(key, out BaseVfxObject vfx) && vfx != null) + { + vfx.Stop(); + Destroy(vfx.gameObject); + _stateGfxObjects.Remove(key); + } + } + public virtual void SetUpPlayer() { m_dwResFlags = 0; @@ -1708,15 +1785,15 @@ namespace BrewMonster //swing sfx //workaround for sound effect delay, it need to trigger via weapon combine action SFXManager.Instance.PlaySkillSfxAtPointAsync(soundPath, Vector3.zero,iTransTime/1000f).Forget(); - + szAct = EC_Utility.BuildActionName(action, weapon_type, "落"); m_pActionController.QueueNonSkillActionWithName(iAction, szAct, 0, false, bHideFX); - + //hit sfx //workaround for sound effect delay, it need to trigger via weapon combine action //.1f is a magic number to make sure the sound effect is triggered after the action is finished SFXManager.Instance.PlaySkillSfxAtPointAsync(hitSoundPath, Vector3.zero,iTransTime/1000f+.1f).Forget(); - + //PlayNonSkillActionWithName(iAction, szAct, true, 200, true, ref pActFlag, COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX);gagága /* if (pRightHandWeapon != null && IsUsingMagicWeapon()) @@ -2832,10 +2909,53 @@ namespace BrewMonster // } // } // } - PlayLevelUpGfx(szPath); + // [中文] 路由到状态 GFX 播放(支持多 GFX 同时激活) + // [English] Route to state GFX playback (supports multiple concurrent GFX) + _ = PlayStateGfxAsync(szPath, szHook, fScale); return false; } + // [中文] 异步加载并播放状态效果 GFX,以 (路径+挂点) 为键去重 + // [English] Asynchronously load and play a state-effect GFX; keyed by (path+hook) to deduplicate + private async Task PlayStateGfxAsync(string path, string hook, float fScale) + { + if (string.IsNullOrEmpty(path)) return; + string key = path + hook; + if (_stateGfxObjects.ContainsKey(key)) return; // [中文] 已激活,跳过 / [English] Already active, skip + + GameObject prefab = await AddressableManager.Instance.LoadPrefabAsync(path); + if (prefab == null) + { + BMLogger.LogWarning($"[StateGFX] Failed to load prefab: {path}"); + return; + } + + // [中文] 实例化挂载在玩家 transform 下,初始位置归零 + // [English] Instantiate parented to player transform with zeroed local position + BaseVfxObject vfx = Instantiate(prefab, transform).GetComponent(); + if (vfx == null) return; + + vfx.transform.localPosition = Vector3.zero; + vfx.SetScale(fScale); + vfx.Play(); + _stateGfxObjects[key] = vfx; + BMLogger.Log($"[StateGFX] Playing: {path}, hook: {hook}, scale: {fScale}"); + } + + // [中文] 在武器 CECModel 上移除状态效果 GFX(武器挂点逻辑未接入,暂存桩) + // [English] Remove state-effect GFX from weapon CECModel (weapon hook not wired yet; stub) + private void RemoveStateGfxFromModel(CECModel model, string path, string hook) + { + // TODO: implement when weapon CECModel GFX tracking is available + } + + // [中文] 在武器 CECModel 上播放状态效果 GFX(武器挂点逻辑未接入,暂存桩) + // [English] Play state-effect GFX on weapon CECModel (weapon hook not wired yet; stub) + private void PlayStateGfxOnModel(CECModel model, string path, string hook, float fScale) + { + // TODO: implement when weapon CECModel GFX tracking is available + } + private async void PlayLevelUpGfx(string path) { // Usage: Load the prefab asynchronously using AddressableManager @@ -3967,7 +4087,7 @@ namespace BrewMonster public CECAttackEvent AttackEvent; public bool IsHitAnim; public bool IsForceStopPrevious; - public bool IsLoop; + public bool IsLoop; public QueueActionEvent(string animationName, Action setFlag, bool isHitAnim, CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false, bool isLoop = false) { diff --git a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs index a776135f1f..cce383018f 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs @@ -127,7 +127,7 @@ public class CECModelStaticData SrcBlend = A3DBLEND.A3DBLEND_SRCALPHA, DestBlend = A3DBLEND.A3DBLEND_INVSRCALPHA }; - private readonly float[] m_OuterData = new float[CECModelConstants.OUTER_DATA_COUNT]; + public float[] m_OuterData = new float[CECModelConstants.OUTER_DATA_COUNT]; private bool m_bCanCastShadow = true; private bool m_bRenderSkinModel = true; private bool m_bRenderEdge = true; @@ -524,6 +524,7 @@ public class CECModel } return hook; } + public float[] GetOuterData() { return m_pMapModel.m_OuterData; } /// /// Invalidate hook cache (call when model reloads) diff --git a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs index 62b44c3de8..75c6b42e6b 100644 --- a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs +++ b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs @@ -612,7 +612,13 @@ namespace BrewMonster.Scripts.Skills public virtual string GetName() { return null; } public virtual string GetHH() { return null; } public virtual string GetEffect() { return null; } - public static VisibleState Query(int profession, int id) { return null; } + public static VisibleState Query(int profession, int id) + { + // [中文] C++ 实现同样忽略 profession,只按 id 查找 + // [English] C++ impl also ignores profession; keyed by id only + GNET.VisibleState.TryGetValue(id, out var result); + return result; + } } public class TeamState { diff --git a/Assets/Resources/DebugCmdHistory.json b/Assets/Resources/DebugCmdHistory.json index 7efd87d400..838d62a442 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": 639134653927186702 + }, { "header": 1988, "param": 0, @@ -20,13 +27,6 @@ "hasParam": true, "describe": "NoCooldown", "lastUsedUtcTicks": 639123542141980400 - }, - { - "header": 1992, - "param": 0, - "hasParam": false, - "describe": "Buff rage", - "lastUsedUtcTicks": 639119244650372830 } ] } \ No newline at end of file diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 818e0af5c5..99129348fd 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -578,6 +578,7 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_SETMOVESTAMP: OnMsgHstSetMoveStamp(Msg); break; case EC_MsgDef.MSG_HST_SANCTUARY: OnMsgHstSanctuary(Msg); break; case EC_MsgDef.MSG_HST_RECEIVEEXP: OnMsgHstReceiveExp(Msg); break; + case EC_MsgDef.MSG_HST_ROOTNOTIFY: OnMsgHstRootNotify(Msg) ; break; default: // Uncomment to debug unhandled messages // Debug.LogWarning($"[CECHostPlayer] ProcessMessage: Unhandled message {msg}"); @@ -604,6 +605,39 @@ namespace BrewMonster }*/ } + private void OnMsgHstRootNotify(in ECMSG Msg) + { + if ((int)Msg.dwParam2 == CommandID.HOST_NOTIFY_ROOT) + { + cmd_host_notify_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + m_dwLIES |=(uint) (1 << pCmd.type); + + if (pCmd.type != 3) + { + // Force pull host to specified position + SetPos(EC_Utility.ToVector3(pCmd.pos)); + } + + if (IsRooting()) + { + if (m_pWorkMan.IsFollowing()){ + m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_FOLLOW); + } + if (m_pWorkMan.IsMovingToPosition()){ + m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_MOVETOPOS); + } + if (m_pWorkMan.IsTracing()){ + m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_TRACEOBJECT); + } + } + } + else if ((int)Msg.dwParam2 == CommandID.HOST_DISPEL_ROOT) + { + cmd_host_dispel_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + m_dwLIES &=(uint) ~(1 << pCmd.type); + } + } + private void OnMsgHstPickupMoney(ECMSG msg) { var data = msg.dwParam1 as byte[];