diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 282c6f9c5e..64cb67ac78 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -2,6 +2,7 @@ using Animancer; using BrewMonster; using BrewMonster.Managers; using BrewMonster.Scripts.Managers; +using BrewMonster.Scripts.Skills; using CSNetwork.GPDataType; using ModelRenderer.Scripts.GameData; using PerfectWorld.Scripts.Managers; @@ -13,6 +14,8 @@ using TMPro; using Unity.VisualScripting; using UnityEngine; using UnityEngine.SceneManagement; +using UnityEngine.UIElements; +using static CECPlayer; public abstract class CECPlayer : CECObject { @@ -51,9 +54,13 @@ public abstract class CECPlayer : CECObject public bool m_bShowWeapon; private QueueActionEvent queueActionEvent; protected static PLAYER_LEVELEXP_CONFIG _player_levelup_exp; + private CECPlayerActionController m_pActionController; + private enumWingType m_wingType; protected int NUM_WEAPON_TYPE = 15; public static readonly int[] m_sciStateIDForStateAction = { 117 }; + private static Dictionary _default_skill_actions + = new Dictionary(); public MOVECONST m_MoveConst; // Const used when moving control public Move_Mode m_MoveMode; @@ -150,7 +157,7 @@ public abstract class CECPlayer : CECObject m_dwResFlags = 0; m_iFashionWeaponType = -1; m_uAttackType = DEFAULT_ACTION_TYPE; - + AttachWeapon(); } @@ -250,6 +257,63 @@ public abstract class CECPlayer : CECObject } } } + if (_turning_actions != null) _turning_actions = new PLAYER_ACTION[(int)PLAYER_ACTION_TYPE.ACT_MAX]; + _turning_actions = new PLAYER_ACTION[(int)PLAYER_ACTION_TYPE.ACT_MAX]; + if (actionMap.TryGetValue("自身旋转", out PLAYER_ACTION_INFO_CONFIG turningData)) + { + for (int i = 0; i < (int)PLAYER_ACTION_TYPE.ACT_MAX; i++) + { + if (i < (int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE || + i > (int)PLAYER_ACTION_TYPE.ACT_REVIVE) + { + // Dùng hành động xoay thay thế hành động thường + _turning_actions[i].type = (PLAYER_ACTION_TYPE)i; + _turning_actions[i].data = turningData; + } + else + { + // Dùng hành động mặc định + _turning_actions[i] = _default_actions[i]; + } + } + } + + uint idSkill = 0; + + while (true) + { + idSkill = ElementSkill.NextSkill(idSkill); + if (idSkill == 0) + break; + + string skillName = ElementSkill.GetName(idSkill); + + if (!string.IsNullOrEmpty(skillName)) + { + if (skillActionMap.TryGetValue(skillName, out PLAYER_ACTION_INFO_CONFIG data1)) + { + // ✅ Gán dữ liệu hành động cho skill tương ứng + _default_skill_actions[idSkill] = data1; + +#if DEBUG_OUTPUT_ACTIONS + for (int n = 0; n < PlayerSkillAction.NUM_WEAPON_TYPE; n++) + { + var suffix = data.action_weapon_suffix[n].suffix; + if (!string.IsNullOrEmpty(suffix)) + { + Debug.Log($"{data.action_prefix}_Ò÷³ª_{suffix}"); + Debug.Log($"{data.action_prefix}_Ê©·ÅÆð_{suffix}"); + Debug.Log($"{data.action_prefix}_Ê©·ÅÂä_{suffix}"); + } + } +#endif + continue; + } + } + + // ❌ not found + // Debug.LogWarning($"CECPlayer::BuildActionList() Failed to find skill action {idSkill}!"); + } } } @@ -606,7 +670,7 @@ public abstract class CECPlayer : CECObject // ============================== if (GetMoveEnv() == (int)MoveEnvironment.MOVEENV_GROUND) { - + // “起” 动作(挥起) szAct = EC_Utility.BuildActionName(action, weapon_type, "Æð"); EventBus.PublishChannel(m_PlayerInfo.cid, new PlayActionEvent(szAct)); @@ -719,7 +783,7 @@ public abstract class CECPlayer : CECObject { //todo: mr Hoang should double check it return 10; - + int weapon_type = 0; if (CanShowFashionWeapon((int)m_uAttackType, m_iFashionWeaponType) && m_aEquips[(int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_WEAPON] != 0) @@ -740,9 +804,10 @@ public abstract class CECPlayer : CECObject { return m_bWeaponAttached; } - bool AttachWeapon(){ + bool AttachWeapon() + { bool result = (false); - + /*while (GetPlayerModel() && (GetLeftHandWeapon() || GetRightHandWeapon())){ A3DSkinModel *pSkinModel = GetPlayerModel()->GetA3DSkinModel(); if (!pSkinModel || @@ -804,7 +869,7 @@ public abstract class CECPlayer : CECObject BMLogger.LogError("CECPlayer::GetFashionConfig, Failed to load fashion weapon config"); return false; } - + int fashion_weapon_mask = (int)pConfig.Value.action_mask[fashion_weapon_type]; return (fashion_weapon_mask & (1 << GetWeaponType(weapon_type))) != 0; } @@ -1064,7 +1129,50 @@ public abstract class CECPlayer : CECObject BubbleText(BUBBLE_HITMISSED, 0);*/ } } + public bool PlaySkillCastAction(int idSkill) + { + string szAct = ""; + int weapon_type = GetShowingWeaponType(); + + PLAYER_ACTION_INFO_CONFIG data = _default_skill_actions[(uint)idSkill]; + + if (data.action_prefix == null || data.action_prefix.Length == 0 || data.action_prefix[0] == 0) + { + // Check if it's a target item skill + if (ElementSkill.GetCommonCoolDown((uint)idSkill) > 1 << 4) + { + data = m_PlayerActions[(int)PLAYER_ACTION_TYPE.ACT_USING_TARGET_ITEM].data; + + if (data.action_prefix == null || data.action_prefix.Length == 0 || data.action_prefix[0] == 0) + return false; + } + else + return false; + } + + if (GetMoveEnv() == (int)MoveEnvironment.MOVEENV_GROUND) + { + szAct = EC_Utility.BuildActionName(data, weapon_type, "_Ò÷³ª_"); + } + else + { + /* if ((*//*UsingWing()*//*m_wingType == enumWingType.WINGTYPE_WING && IsFlying()) || (GetProfession() == PROF_ANGEL) || (GetProfession() == PROF_ARCHOR) || (GetProfession() == PROF_MONK) || (GetProfession() == PROF_GHOST)) + sprintf(szAct, "%s_¿ÕÖгá°ò_Ò÷³ª_%s", data->action_prefix, data->action_weapon_suffix[weapon_type].suffix); + else + sprintf(szAct, "%s_¿ÕÖзɽ£_Ò÷³ª_%s", data->action_prefix, data->action_weapon_suffix[weapon_type].suffix);*/ + } + + bool bHideFX = false; /*!CECOptimize::Instance().GetGFX().CanShowCast(GetCharacterID(), GetClassID());*/ + if (!PlaySkillCastActionWithName(idSkill, szAct, bHideFX)) + { + return false; + } + + ShowWeaponByConfig(data); + //UpdateWeaponHangerPosBySkillAction(idSkill);// ¸ù¾Ý¼¼Äܲ¥·ÅµÄ¶¯×÷£¬¸ü¸ÄÎäÆ÷Ðü¹ÒλÖã¨Ö»Äܼì²é²»ÅŶӶ¯×÷£© + return true; + } public bool OnDamaged(int skill) { var atkMan = CECAttacksMan.Instance; @@ -1084,7 +1192,11 @@ public abstract class CECPlayer : CECObject return false; } - + public bool PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX/* =false */) + { + return m_pActionController != null + && m_pActionController.PlaySkillCastActionWithName(idSkill, szActName, bNoFX); + } bool GetSkillStateActionName(int skill, int state, string name1, string name2) { /* for (int i = 0; i < (int)m_SkillStateActionVec.size(); i++) @@ -1179,6 +1291,13 @@ public struct INFO this.crc_e = crc_; } } +public +enum enumWingType +{ + WINGTYPE_WING, // ·ÉÐÐÆ÷ÀàÐÍ£º³á°ò + WINGTYPE_FLYSWORD, // ·ÉÐÐÆ÷ÀàÐÍ£º·É½£ + WINGTYPE_DOUBLEWHEEL, // ·ÉÐÐÆ÷ÀàÐÍ£ºË«½Å·ÉÐÐÆ÷ +}; public enum PlayerResourcesReadyFlag diff --git a/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs new file mode 100644 index 0000000000..20a3c0c347 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs @@ -0,0 +1,190 @@ +using System; + +// CECPlayerActionController +// 玩家动作控制器 / Player action controller +public class CECPlayerActionController +{ + // Action channels for split-body animation (kept for parity with C++) + // 动作通道(保留与C++一致的接口语义) / Channels kept to mirror C++ API + public const int ACT_CHANNEL_UPPERBODY = 0; + public const int ACT_CHANNEL_LOWERBODY = 1; + public const int ACT_CHANNEL_WOUND = 2; + + private CECPlayer m_pPlayer; + private CECModel m_pPlayerModel; + private bool m_bSupportCastSkillWhenMove; + private CECPlayerActionPlayPolicy m_actionPlayPolicy; + private bool m_bSkillAttackActionPlayed; + + public CECPlayerActionController() + { + m_pPlayer = null; + m_pPlayerModel = null; + m_bSupportCastSkillWhenMove = false; + m_actionPlayPolicy = null; + m_bSkillAttackActionPlayed = false; + } + + ~CECPlayerActionController() + { + ReleaseActionPlayPolicy(); + } + + public void Bind(CECPlayer player, CECModel playerModel) + { + if (player == null) + { + return; + } + if (player == m_pPlayer && playerModel == m_pPlayerModel) + { + return; + } + + ReleaseActionPlayPolicy(); + m_pPlayer = player; + m_pPlayerModel = playerModel; + + // 是否支持移动中施法(当前C#端未实现,统一设为false) + // Support cast-while-move (not implemented in C# port -> false) + m_bSupportCastSkillWhenMove = false; + + if (!m_bSupportCastSkillWhenMove) + { + // 如果不支持,仍保留“受击”通道逻辑钩子(在C#里留空) + // If not supported, keep the hook for wound channel (no-op in C#) + } + + InitializeActionPlayPolicy(); + } + + public bool SupportCastSkillWhenMove() + { + return m_bSupportCastSkillWhenMove; + } + + public bool CanCombineWithMoveForSkill(int idSkill) + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.CanCombineWithMoveForSkill(idSkill); + } + + private void InitializeActionPlayPolicy() + { + // 当前C#只提供默认策略(不拆分上下半身) + // Use default policy in this C# port + m_actionPlayPolicy = new CECPlayerActionPlayPolicy(m_pPlayer, m_pPlayerModel); + } + + private void ReleaseActionPlayPolicy() + { + m_actionPlayPolicy = null; + } + + // Build channels (C# no-op, return false to indicate not supported here) + // 构建动作通道(C#未实现,返回false) + private bool BuildChannelForCastSkillWhenMove() + { + return false; + } + + public bool PlayNonSkillActionWithName(int iAction, string szActName, bool bRestart = true, int nTransTime = 200, bool bNoFx = false, bool[] pActFlag = null, uint dwFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.PlayNonSkillActionWithName(iAction, szActName, bRestart, nTransTime, bNoFx, pActFlag, dwFlagMode); + } + + public bool QueueNonSkillActionWithName(int iAction, string szActName, int nTransTime = 200, bool bForceStopPrevAct = false, bool bNoFx = false, bool bResetSpeed = false, bool bResetActFlag = false, bool[] pNewActFlag = null, uint dwNewFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.QueueNonSkillActionWithName(iAction, szActName, nTransTime, bForceStopPrevAct, bNoFx, bResetSpeed, bResetActFlag, pNewActFlag, dwNewFlagMode); + } + + public bool PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX = false) + { + if (m_actionPlayPolicy != null + && m_actionPlayPolicy.PlaySkillCastActionWithName(idSkill, szActName, bNoFX)) + { + m_bSkillAttackActionPlayed = false; + return true; + } + return false; + } + + public bool PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX = false, bool[] pActFlag = null, uint dwFlagMode = 0) + { + if (m_actionPlayPolicy != null + && m_actionPlayPolicy.PlaySkillAttackActionWithName(idSkill, szActName, bNoFX, pActFlag, dwFlagMode)) + { + m_bSkillAttackActionPlayed = true; + return true; + } + return false; + } + + public bool QueueSkillAttackActionWithName(int idSkill, string szActName, int nTransTime = 200, bool bNoFX = false, bool bResetSpeed = false, bool bResetActFlag = false, bool[] pNewActFlag = null, uint dwNewFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.QueueSkillAttackActionWithName(idSkill, szActName, nTransTime, bNoFX, bResetSpeed, bResetActFlag, pNewActFlag, dwNewFlagMode); + } + + public bool PlayWoundActionWithName(string szActName) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.PlayWoundActionWithName(szActName); + } + + public void ClearComActFlagAllRankNodes(bool bSignalCurrent) + { + if (m_actionPlayPolicy != null) + { + m_actionPlayPolicy.ClearComActFlagAllRankNodes(bSignalCurrent); + } + } + + public void StopChannelAction() + { + if (m_actionPlayPolicy != null) + { + m_actionPlayPolicy.StopChannelAction(); + } + } + + public void StopSkillCastAction() + { + if (m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction() && !m_bSkillAttackActionPlayed) + { + m_actionPlayPolicy.StopSkillAction(); + // LOG kept minimal in C# + } + } + + public void StopSkillAttackAction() + { + if (m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction() && m_bSkillAttackActionPlayed) + { + m_actionPlayPolicy.StopSkillAction(); + // LOG kept minimal in C# + } + } + + public bool IsPlayingAction(int iAction) + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingAction(iAction); + } + + public bool IsPlayingCastingSkillAction() + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction(); + } + + public bool IsPlayingMoveAction() + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingMoveAction(); + } + + public int GetLowerBodyAction() + { + return m_actionPlayPolicy != null ? m_actionPlayPolicy.GetLowerBodyAction() : -1; + } +} + diff --git a/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs.meta b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs.meta new file mode 100644 index 0000000000..d0534021b1 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a0afbacc947643442a7bb827ce5b8000 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs new file mode 100644 index 0000000000..47f51706c8 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs @@ -0,0 +1,340 @@ +using System; + +/*public class CECPlayerActionController +{ + // Action channels for split-body animation (kept for parity with C++) + // 动作通道(保留与C++一致的接口语义) / Channels kept to mirror C++ API + public const int ACT_CHANNEL_UPPERBODY = 0; + public const int ACT_CHANNEL_LOWERBODY = 1; + public const int ACT_CHANNEL_WOUND = 2; + + private CECPlayer m_pPlayer; + private CECModel m_pPlayerModel; + private bool m_bSupportCastSkillWhenMove; + private CECPlayerActionPlayPolicy m_actionPlayPolicy; + private bool m_bSkillAttackActionPlayed; + + public CECPlayerActionController() + { + m_pPlayer = null; + m_pPlayerModel = null; + m_bSupportCastSkillWhenMove = false; + m_actionPlayPolicy = null; + m_bSkillAttackActionPlayed = false; + } + + ~CECPlayerActionController() + { + ReleaseActionPlayPolicy(); + } + + public void Bind(CECPlayer player, CECModel playerModel) + { + if (player == null) + { + return; + } + if (player == m_pPlayer && playerModel == m_pPlayerModel) + { + return; + } + + ReleaseActionPlayPolicy(); + m_pPlayer = player; + m_pPlayerModel = playerModel; + + // 是否支持移动中施法(当前C#端未实现,统一设为false) + // Support cast-while-move (not implemented in C# port -> false) + m_bSupportCastSkillWhenMove = false; + + if (!m_bSupportCastSkillWhenMove) + { + // 如果不支持,仍保留“受击”通道逻辑钩子(在C#里留空) + // If not supported, keep the hook for wound channel (no-op in C#) + } + + InitializeActionPlayPolicy(); + } + + public bool SupportCastSkillWhenMove() + { + return m_bSupportCastSkillWhenMove; + } + + public bool CanCombineWithMoveForSkill(int idSkill) + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.CanCombineWithMoveForSkill(idSkill); + } + + private void InitializeActionPlayPolicy() + { + // 当前C#只提供默认策略(不拆分上下半身) + // Use default policy in this C# port + m_actionPlayPolicy = new CECPlayerActionPlayPolicy(m_pPlayer, m_pPlayerModel); + } + + private void ReleaseActionPlayPolicy() + { + m_actionPlayPolicy = null; + } + + // Build channels (C# no-op, return false to indicate not supported here) + // 构建动作通道(C#未实现,返回false) + private bool BuildChannelForCastSkillWhenMove() + { + return false; + } + + public bool PlayNonSkillActionWithName(int iAction, string szActName, bool bRestart = true, int nTransTime = 200, bool bNoFx = false, bool[] pActFlag = null, uint dwFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.PlayNonSkillActionWithName(iAction, szActName, bRestart, nTransTime, bNoFx, pActFlag, dwFlagMode); + } + + public bool QueueNonSkillActionWithName(int iAction, string szActName, int nTransTime = 200, bool bForceStopPrevAct = false, bool bNoFx = false, bool bResetSpeed = false, bool bResetActFlag = false, bool[] pNewActFlag = null, uint dwNewFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.QueueNonSkillActionWithName(iAction, szActName, nTransTime, bForceStopPrevAct, bNoFx, bResetSpeed, bResetActFlag, pNewActFlag, dwNewFlagMode); + } + + public bool PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX = false) + { + if (m_actionPlayPolicy != null + && m_actionPlayPolicy.PlaySkillCastActionWithName(idSkill, szActName, bNoFX)) + { + m_bSkillAttackActionPlayed = false; + return true; + } + return false; + } + + public bool PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX = false, bool[] pActFlag = null, uint dwFlagMode = 0) + { + if (m_actionPlayPolicy != null + && m_actionPlayPolicy.PlaySkillAttackActionWithName(idSkill, szActName, bNoFX, pActFlag, dwFlagMode)) + { + m_bSkillAttackActionPlayed = true; + return true; + } + return false; + } + + public bool QueueSkillAttackActionWithName(int idSkill, string szActName, int nTransTime = 200, bool bNoFX = false, bool bResetSpeed = false, bool bResetActFlag = false, bool[] pNewActFlag = null, uint dwNewFlagMode = 0) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.QueueSkillAttackActionWithName(idSkill, szActName, nTransTime, bNoFX, bResetSpeed, bResetActFlag, pNewActFlag, dwNewFlagMode); + } + + public bool PlayWoundActionWithName(string szActName) + { + return m_actionPlayPolicy != null + && m_actionPlayPolicy.PlayWoundActionWithName(szActName); + } + + public void ClearComActFlagAllRankNodes(bool bSignalCurrent) + { + if (m_actionPlayPolicy != null) + { + m_actionPlayPolicy.ClearComActFlagAllRankNodes(bSignalCurrent); + } + } + + public void StopChannelAction() + { + if (m_actionPlayPolicy != null) + { + m_actionPlayPolicy.StopChannelAction(); + } + } + + public void StopSkillCastAction() + { + if (m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction() && !m_bSkillAttackActionPlayed) + { + m_actionPlayPolicy.StopSkillAction(); + // LOG kept minimal in C# + } + } + + public void StopSkillAttackAction() + { + if (m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction() && m_bSkillAttackActionPlayed) + { + m_actionPlayPolicy.StopSkillAction(); + // LOG kept minimal in C# + } + } + + public bool IsPlayingAction(int iAction) + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingAction(iAction); + } + + public bool IsPlayingCastingSkillAction() + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingCastingSkillAction(); + } + + public bool IsPlayingMoveAction() + { + return m_actionPlayPolicy != null && m_actionPlayPolicy.IsPlayingMoveAction(); + } + + public int GetLowerBodyAction() + { + return m_actionPlayPolicy != null ? m_actionPlayPolicy.GetLowerBodyAction() : -1; + } +}*/ +public class CECPlayerActionPlayPolicy +{ + protected readonly CECPlayer m_pPlayer; + protected readonly CECModel m_pPlayerModel; + + // 简单状态记录以支持“是否在施法中”的判断 + // Simple state to support IsPlayingCastingSkillAction + private bool m_isCastingSkill; + + public CECPlayerActionPlayPolicy(CECPlayer pPlayer, CECModel pPlayerModel) + { + m_pPlayer = pPlayer; + m_pPlayerModel = pPlayerModel; + m_isCastingSkill = false; + } + + public virtual bool CanCombineWithMoveForSkill(int idSkill) + { + return false; + } + + public CECPlayer GetPlayer() + { + return m_pPlayer; + } + + public CECModel GetModel() + { + return m_pPlayerModel; + } + + public bool IsMoving() + { + return IsPlayingMoveAction() && (m_pPlayer != null && m_pPlayer.IsWorkMoveRunning()); + } + + public static bool IsMoveAction(int action) + { + return action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_RUN + || action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_WALK + || action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_FLY + || action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_FLY_SWORD + || action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_SWIM + || action == (int)CECPlayer.PLAYER_ACTION_TYPE.ACT_SWIM_FOR_MOVESKILL; + } + + public bool IsPlayingCastingSkillAction() + { + return m_isCastingSkill; + } + + // Non-skill action + public virtual bool PlayNonSkillActionWithName(int iAction, string szActName, bool bRestart, int nTransTime, bool bNoFx, bool[] pActFlag, uint dwFlagMode) + { + if (m_pPlayer == null || string.IsNullOrEmpty(szActName)) + { + return false; + } + EventBus.PublishChannel(m_pPlayer.GetPlayerInfo().cid, new PlayActionEvent(szActName)); + return true; + } + + public virtual bool QueueNonSkillActionWithName(int iAction, string szActName, int nTransTime, bool bForceStopPrevAct, bool bNoFx, bool bResetSpeed, bool bResetActFlag, bool[] pNewActFlag, uint dwNewFlagMode) + { + // 当前实现:直接播放(队列行为由上层系统处理) + // Current: just play, queue behavior handled by upper systems + return PlayNonSkillActionWithName(iAction, szActName, false, nTransTime, bNoFx, pNewActFlag, dwNewFlagMode); + } + + // Skill actions + public virtual bool PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX) + { + if (m_pPlayer == null || string.IsNullOrEmpty(szActName)) + { + return false; + } + if (IsMoving()) + { + return false; + } + m_isCastingSkill = true; + EventBus.PublishChannel(m_pPlayer.GetPlayerInfo().cid, new PlayActionEvent(szActName)); + return true; + } + + public virtual bool PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX, bool[] pActFlag, uint dwFlagMode) + { + if (m_pPlayer == null || string.IsNullOrEmpty(szActName)) + { + return false; + } + if (IsMoving()) + { + return false; + } + m_isCastingSkill = true; + EventBus.PublishChannel(m_pPlayer.GetPlayerInfo().cid, new PlayActionEvent(szActName)); + return true; + } + + public virtual bool QueueSkillAttackActionWithName(int idSkill, string szActName, int nTransTime, bool bNoFX, bool bResetSpeed, bool bResetActFlag, bool[] pNewActFlag, uint dwNewFlagMode) + { + return PlaySkillAttackActionWithName(idSkill, szActName, bNoFX, pNewActFlag, dwNewFlagMode); + } + + public virtual bool PlayWoundActionWithName(string szActName) + { + if (m_pPlayer == null || string.IsNullOrEmpty(szActName)) + { + return false; + } + EventBus.PublishChannel(m_pPlayer.GetPlayerInfo().cid, new PlayActionEvent(szActName)); + return true; + } + + public virtual void ClearComActFlagAllRankNodes(bool bSignalCurrent) + { + if (m_pPlayer != null) + { + EventBus.PublishChannel(m_pPlayer.GetPlayerInfo().cid, new CECPlayer.CleearComActFlagAllRankNodesEvent(bSignalCurrent)); + } + } + + public virtual void StopChannelAction() + { + // 在C#端没有底层通道概念,做成清旗标即可 + // No channel concept here; clear act flags as a substitute + ClearComActFlagAllRankNodes(true); + } + + public virtual void StopSkillAction() + { + m_isCastingSkill = false; + StopChannelAction(); + } + + public virtual bool IsPlayingAction(int iAction) + { + // C#动画系统由上层驱动,这里不跟踪具体动作,统一返回false + // Not tracked here; return false by default + return false; + } + + public virtual bool IsPlayingMoveAction() + { + return false; + } + + public virtual int GetLowerBodyAction() + { + return -1; + } +} diff --git a/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs.meta b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs.meta new file mode 100644 index 0000000000..58ba192819 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f3e916f6bf57b24db022c97379fffca \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs index 04e213d5e4..b47d2d58c4 100644 --- a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs +++ b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs @@ -151,6 +151,13 @@ namespace BrewMonster.Scripts.Skills return new Dictionary(); } public virtual string GetName() { return null; } + public static string GetName(uint id) + { + SkillStub s = SkillStub.GetStub(id); + if (s != null) + return s.GetName(); + return ""; + } public virtual byte[] GetNativeName() { return null; } // �������?,��skill_type public virtual byte GetType() { return 1; } @@ -225,6 +232,13 @@ namespace BrewMonster.Scripts.Skills // ��֧���� public virtual int GetArrowCost() { return 0; } + public static int GetCommonCoolDown(uint id) + { + SkillStub s = SkillStub.GetStub(id); + if (s != null) + return s.commoncooldown; + return 0; + } // ���������ж� public virtual bool ValidWeapon(int w) { return true; } // 0, �ɹ���1��������ƥ�䣻2, mp���㣻3��λ�����������㣻4���������������?5������ID, 6��δѡ��Ŀ�� diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 47172203d1..94ccd29f8f 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -61,6 +61,7 @@ public partial class CECHostPlayer : CECPlayer private int m_iRoleCreateTime; private int m_iRoleLastLoginTime; // Role last login time private int m_iAccountTotalCash; + private int m_idCurSkillTarget; private List m_aTabSels = new List(); private List m_aPtSkills = new List(); @@ -68,8 +69,11 @@ public partial class CECHostPlayer : CECPlayer private List m_aEquipSkills = new List(); private List m_aGoblinSkills = new List(); + private bool m_bSpellDSkill; private bool m_bEnterGame; public CECSkill m_pPrepSkill; + private CECSkill m_pCurSkill; + private CECCounter m_IncantCnt; private float playerSpeed = 5.0f; private float jumpHeight = 1.5f; private float gravityValue = -9.81f; @@ -404,9 +408,398 @@ public partial class CECHostPlayer : CECPlayer case int value when value == EC_MsgDef.MSG_HST_SKILLDATA: OnMsgHstSkillData(Msg); break; case int value when value == EC_MsgDef.MSG_HST_DIED: OnMsgHstDied(Msg); break; case int value when value == EC_MsgDef.MSG_HST_GOTO: OnMsgHstGoto(Msg); break; + case int value when value == EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break; + } } + private void OnMsgPlayerCastSkill(ECMSG Msg) + { + // using namespace S2C; + bool bDoOtherThing = false; + int idTarget = 0; + + bool bActionStartSkill = false; + int iActionTime = 1000; + //CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper(); + BMLogger.LogError("HoangDev OnMsgPlayerCastSkill Msg.dwParam2 " + (int)Msg.dwParam2); + switch ((int)Msg.dwParam2) + { + case CommandID.OBJECT_CAST_SKILL: + { + var pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + + if (m_pCurSkill != null) + { + m_pCurSkill.EndCharging(); + } + + m_pCurSkill = GetPositiveSkillByID(pCmd.skill); + if (m_pCurSkill == null) m_pCurSkill = GetEquipSkillByID(pCmd.skill); + if (m_pCurSkill == null) m_pCurSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)pCmd.skill); + if (m_pCurSkill == null) + { + System.Diagnostics.Debug.Assert(m_pCurSkill != null); + return; + } + + if (m_pCurSkill.IsChargeable()) + m_pCurSkill.StartCharging(pCmd.time); + + int iWaitTime = -1; + if (m_pCurSkill.GetExecuteTime() >= 0) + iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); + + var pWork = (CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT); + pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); + m_pWorkMan.StartWork_p1(pWork); + + if (!m_pCurSkill.IsChargeable()) + { + int iTime = pCmd.time; + iTime = Math.Max(iTime, 10); + m_IncantCnt.SetPeriod(iTime); + m_IncantCnt.Reset(); + } + else + { + m_IncantCnt.Reset(true); + } + + m_bSpellDSkill = false; + + TurnFaceTo(pCmd.target); + + m_idCurSkillTarget = pCmd.target; + PlaySkillCastAction(m_pCurSkill.GetSkillID()); + + bActionStartSkill = true; + iActionTime = iWaitTime; + break; + } + + /* case CommandID.SKILL_PERFORM: + { + m_pPrepSkill = null; + + if (m_pCurSkill != null && m_pCurSkill.IsDurative()) + m_bSpellDSkill = true; + + break; + } + + case CommandID.HOST_STOP_SKILL: + { + m_pPrepSkill = null; + + CECSkill pSkillToMatch = m_pCurSkill; + if (m_pCurSkill != null) + { + ClearComActFlagAllRankNodes(true); + + if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || + m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) + { + bDoOtherThing = true; + idTarget = m_idCurSkillTarget; + } + + m_pCurSkill.EndCharging(); + m_pCurSkill = null; + } + + AP_ActionEvent(AP_EVENT_STOPSKILL); + + if (pSkillToMatch != null) + { + m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); + } + StopSkillAttackAction(); + + m_idCurSkillTarget = 0; + break; + } + + case CommandID.SELF_SKILL_INTERRUPTED: + { + int skill_id = 0; + m_pPrepSkill = null; + + CECSkill pSkillToMatch = m_pCurSkill; + if (m_pCurSkill != null) + { + skill_id = m_pCurSkill.GetSkillID(); + + ClearComActFlagAllRankNodes(false); + + if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || + m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) + { + bDoOtherThing = true; + idTarget = m_idCurSkillTarget; + } + + m_pCurSkill.EndCharging(); + m_pCurSkill = null; + } + + m_idCurSkillTarget = 0; + + if (pSkillToMatch != null) + { + m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); + } + StopSkillCastAction(); + + g_pGame.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT); + + AP_ActionEvent(AP_EVENT_STOPSKILL); + + CECAutoPolicy.GetInstance().SendEvent_SkillInterrupt(skill_id); + break; + } + + case CommandID.OBJECT_CAST_INSTANT_SKILL: + { + var pCmd = (S2C.cmd_object_cast_instant_skill)Msg.Param1; + System.Diagnostics.Debug.Assert(pCmd.caster == m_PlayerInfo.cid); + + CECSkill pSkill = GetPositiveSkillByID(pCmd.skill); + if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); + if (pSkill == null) + { + System.Diagnostics.Debug.Assert(pSkill != null); + return; + } + + if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) + TurnFaceTo(pCmd.target); + + PlaySkillCastAction(pSkill.GetSkillID()); + bActionStartSkill = true; + break; + } + + case CommandID.OBJECT_CAST_POS_SKILL: + { + var pCmd = (S2C.cmd_object_cast_pos_skill)Msg.Param1; + System.Diagnostics.Debug.Assert(pCmd.caster == m_PlayerInfo.cid); + + CECSkill pSkill = GetNormalSkill(pCmd.skill); + if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); + if (pSkill == null) + { + System.Diagnostics.Debug.Assert(pSkill != null); + break; + } + + TurnFaceTo(pCmd.target); + + if (pSkill.GetRangeType() != CECSkill.RangeType.RANGE_SLEF && + pSkill.GetRangeType() != CECSkill.RangeType.RANGE_SELFSPHERE && + (pSkill.GetSkillID() == 1095 || + pSkill.GetSkillID() == 1278 || + pSkill.GetSkillID() == 1279 || + pSkill.GetSkillID() == 2313)) + { + // Teleport-style placement + A3DVECTOR3 vPos = pCmd.pos; + if (!IsPosCollideFree(vPos)) + { + vPos += g_vAxisY * 0.1f; + } + + A3DVECTOR3 vNormal; + float vTerrainHeight = g_pGame.GetGameRun().GetWorld().GetTerrainHeight(vPos, out vNormal); + if (vPos.y < vTerrainHeight) + vPos.y = vTerrainHeight; + + SetPos(vPos); + + m_CDRInfo.vTPNormal = (vPos.y <= vTerrainHeight + 0.1f) ? vNormal : g_vOrigin; + m_CDRInfo.fYVel = 0.0f; + m_CDRInfo.vAbsVelocity.Clear(); + ResetJump(); + + m_MoveCtrl.SetHostLastPos(vPos); + m_MoveCtrl.SetLastSevPos(vPos); + + UpdateFollowCamera(false, 10); + } + else + { + var pWork = (CECHPWorkFMove)m_pWorkMan.CreateWork(CECHPWork.WORK_FLASHMOVE); + + int nExecuteTime = pSkill.GetExecuteTime(); + nExecuteTime = Math.Max(nExecuteTime, 50); + + if (pSkill.GetSkillID() == 1145 || + pSkill.GetSkillID() == 1314 || + pSkill.GetSkillID() == 1315 || + pSkill.GetSkillID() == 1362 || + pSkill.GetSkillID() == 1690 || + pSkill.GetSkillID() == 1691 || + pSkill.GetSkillID() == 1845 || + pSkill.GetSkillID() == 1844 || + pSkill.GetSkillID() == 1815 || + pSkill.GetSkillID() == 2272 || + pSkill.GetSkillID() == 2315 || + pSkill.GetSkillID() == 2285 || + pSkill.GetSkillID() == 2340 || + pSkill.GetSkillID() == 2341 || + pSkill.GetSkillID() == 2342 || + pSkill.GetSkillID() == 2553 || + pSkill.GetSkillID() == 2740 || + pSkill.GetSkillID() == 2741 || + pSkill.GetSkillID() == 2559 || + pSkill.GetSkillID() == 2752 || + pSkill.GetSkillID() == 2753) + { + A3DVECTOR3 vPos = pCmd.pos; + if (!IsPosCollideFree(vPos)) + { + vPos += g_vAxisY * 0.1f; + } + + A3DVECTOR3 vNormal; + float vTerrainHeight = g_pGame.GetGameRun().GetWorld().GetTerrainHeight(vPos, out vNormal); + if (vPos.y < vTerrainHeight) + vPos.y = vTerrainHeight; + + m_CDRInfo.vTPNormal = (vPos.y <= vTerrainHeight + 0.1f) ? vNormal : g_vOrigin; + m_CDRInfo.fYVel = 0.0f; + m_CDRInfo.vAbsVelocity.Clear(); + ResetJump(); + + pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, pSkill.GetSkillID()); + } + else + { + if (pSkill.GetRangeType() == CECSkill.RangeType.RANGE_SLEF || + pSkill.GetRangeType() == CECSkill.RangeType.RANGE_SELFSPHERE) + { + m_CDRInfo.vTPNormal = m_MoveCtrl.m_vFlashTPNormal; + } + pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, 0); + } + + m_pWorkMan.StartWork_p2(pWork); + iActionTime = nExecuteTime; + } + + bActionStartSkill = true; + break; + } + + case CommandID.PLAYER_CAST_RUNE_SKILL: + { + var pCmd = (S2C.cmd_player_cast_rune_skill)Msg.Param1; + System.Diagnostics.Debug.Assert(pCmd.caster == m_PlayerInfo.cid); + + if (m_pTargetItemSkill != null) + { + m_pTargetItemSkill = null; + } + + m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); + if (m_pTargetItemSkill == null) + { + System.Diagnostics.Debug.Assert(m_pTargetItemSkill != null); + return; + } + + m_pCurSkill = m_pTargetItemSkill; + + if (m_pCurSkill.IsChargeable()) + m_pCurSkill.StartCharging(pCmd.time); + + int iWaitTime = -1; + if (m_pCurSkill.GetExecuteTime() >= 0) + iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); + + var pWork = (CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.WORK_SPELLOBJECT); + pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); + m_pWorkMan.StartWork_p1(pWork); + + if (!m_pCurSkill.IsChargeable()) + { + int iTime = pCmd.time; + iTime = Math.Max(iTime, 10); + m_IncantCnt.SetPeriod(iTime); + m_IncantCnt.Reset(); + } + else + { + m_IncantCnt.Reset(true); + } + + m_bSpellDSkill = false; + + TurnFaceTo(pCmd.target); + + m_idCurSkillTarget = pCmd.target; + PlaySkillCastAction(m_pCurSkill.GetSkillID()); + + bActionStartSkill = true; + iActionTime = iWaitTime; + g_pGame.RuntimeDebugInfo(0xffffffff, $"Cast skill({m_pCurSkill.GetSkillID()}): {g_pGame.GetSkillDesc().GetWideString(m_pCurSkill.GetSkillID() * 10)}"); + break; + } + + case CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: + { + var pCmd = (S2C.cmd_player_cast_rune_instant_skill)Msg.Param1; + System.Diagnostics.Debug.Assert(pCmd.caster == m_PlayerInfo.cid); + + if (m_pTargetItemSkill != null) + { + m_pTargetItemSkill = null; + } + + m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); + if (m_pTargetItemSkill == null) + { + System.Diagnostics.Debug.Assert(m_pTargetItemSkill != null); + return; + } + + if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) + TurnFaceTo(pCmd.target); + + PlaySkillCastAction(m_pTargetItemSkill.GetSkillID()); + bActionStartSkill = true; + break; + } + + case CommandID.ERROR_MESSAGE: + bDoOtherThing = true; + // m_pPrepSkill = null; + break; + + default: + System.Diagnostics.Debug.Assert(false); + break;*/ + } + + if (bActionStartSkill) + AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime); + + if (bDoOtherThing) + { + if (m_pComboSkill != null && !m_pComboSkill.IsStop()) + { + if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled()) + g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex()); + else + m_pComboSkill.Continue(false); + } + else + { + if (idTarget != 0 && idTarget != m_PlayerInfo.cid) + NormalAttackObject(idTarget, true); + } + } + } private void OnMsgHstSkillData(ECMSG Msg) { cmd_skill_data pCmd = default; diff --git a/Assets/Scripts/EC_Utility.cs b/Assets/Scripts/EC_Utility.cs index 0a2041baa4..97239ed2bf 100644 --- a/Assets/Scripts/EC_Utility.cs +++ b/Assets/Scripts/EC_Utility.cs @@ -116,6 +116,20 @@ public static class EC_Utility var tailFixed = FixGBKString(tail); return $"{prefix}_{suffix}{tailFixed}"; } + public static string BuildActionName(PLAYER_ACTION_INFO_CONFIG data, int weaponType, string midBody = "") + { + string prefix = data.ActionPrefix ?? string.Empty; + string suffix = string.Empty; + + if (data.action_weapon_suffix != null + && weaponType >= 0 + && weaponType < data.action_weapon_suffix.Length) + { + suffix = data.action_weapon_suffix[weaponType].Suffix ?? string.Empty; + } + var midBodyFixed = FixGBKString(midBody); + return $"{prefix}{midBodyFixed}{suffix}"; + } // Build pvp mask public static byte glb_BuildPVPMask(bool bForceAttack)