diff --git a/Assets/PerfectWorld/Editor.meta b/Assets/PerfectWorld/Editor.meta new file mode 100644 index 0000000000..866bb78448 --- /dev/null +++ b/Assets/PerfectWorld/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75a0cc0fd8902794a883916febf9855b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs b/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs new file mode 100644 index 0000000000..ac25be83be --- /dev/null +++ b/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs @@ -0,0 +1,33 @@ +using UnityEditor; +using UnityEngine; + +namespace BrewMonster +{ + public static class ResetStaticUtilityEditor + { + [MenuItem("Tools/Reset Static Fields/Run Now", priority = 10)] + public static void RunNow() + { + int count = ResetStaticUtility.ResetAll(logEach: true); + EditorUtility.DisplayDialog("Reset Static Fields", $"Reset completed.\nFields processed: {count}", "OK"); + } + + private const string AutoRunPath = "Tools/Reset Static Fields/Auto Reset On Play"; + + [MenuItem(AutoRunPath, priority = 11)] + public static void ToggleAutoRunOnPlay() + { + bool enabled = ResetStaticUtility.GetAutoRunOnPlay(); + ResetStaticUtility.SetAutoRunOnPlay(!enabled); + } + + [MenuItem(AutoRunPath, validate = true)] + public static bool ToggleAutoRunOnPlayValidate() + { + Menu.SetChecked(AutoRunPath, ResetStaticUtility.GetAutoRunOnPlay()); + return true; + } + } +} + + diff --git a/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs.meta b/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs.meta new file mode 100644 index 0000000000..5abc899e7c --- /dev/null +++ b/Assets/PerfectWorld/Editor/ResetStaticUtilityEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4f472ae43a592b64cb9876ad7a36eb3a \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs index 2c736597e3..50593c3610 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs @@ -1,25 +1,26 @@ using BrewMonster; +using BrewMonster.Assets.PerfectWorld.Scripts.Common; using BrewMonster.Managers; using CSNetwork.GPDataType; using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using Unity.VisualScripting; using UnityEngine; -using static CECAttacksMan; -public class CECAttacksMan : MonoSingleton +public class CECAttacksMan : Singleton { private readonly LinkedList m_AttackLinkedList = new LinkedList(); + private CECMultiSectionSkillMan m_pMultiSkillGfxComposerMan; + #if UNITY_EDITOR - public List m_AttackList = new List(); + public List m_AttackList = new List(); #endif - + private void Update() { #if UNITY_EDITOR - m_AttackList = m_AttackLinkedList.ToList(); + m_AttackList = m_AttackLinkedList.ToList(); #endif var node = m_AttackLinkedList.First; while (node != null) @@ -68,7 +69,22 @@ public class CECAttacksMan : MonoSingleton newEvent.UpdateTargetFlag(); return m_AttackLinkedList.Last.Value; } - + public bool GetSkillSectionActionSuffix(int skill, int section, out string suffix) + { + // TODO: Implement multi-section skill logic + // 待实现:多段技能逻辑 + if (m_pMultiSkillGfxComposerMan != null) + { + CECMultiSectionSkillMan::SectionInfo info = m_pMultiSkillGfxComposerMan.GetSecionInfo(skill, section); + if (info && info->action_suffix != "0") // 0 表示技能没有后缀 + { + suffix = info->action_suffix; + return true; + } + } + suffix = null; + return false; + } public CECAttackEvent AddSkillAttack(int idHost, int idCastTarget, int idTarget, int idWeapon, int idSkill, int nSkillLevel, uint dwModifier, int nDamage) { var newEvent = new CECAttackEvent( @@ -104,6 +120,207 @@ public class CECAttacksMan : MonoSingleton } + +/// +/// Manager for multi-section skills +/// 多段技能管理器 +/// +public class CECMultiSectionSkillMan +{ + /// + /// Section information for multi-section skills + /// 多段技能的段信息 + /// + [Serializable] + public class SectionInfo + { + public int skill_id; // 技能ID / Skill ID + public byte section; // 段号 / Section number + public string action_suffix; // 动作后缀 / Action suffix + public A3DSkillGfxComposer pComposer; // 技能特效组合器 / Skill GFX composer + + public SectionInfo() + { + skill_id = 0; + section = 1; + action_suffix = string.Empty; + pComposer = null; + } + } + + // 用于多段技能的sgc映射 / Map for multi-section skill SGC files + private readonly Dictionary m_SgcName2ComposerMap = new Dictionary(); + + // 多段技能组合器列表 / Multi-section skill composer list + private readonly List m_MultiSectionSkillComposerVec = new List(); + + public CECMultiSectionSkillMan() + { + } + + ~CECMultiSectionSkillMan() + { + Release(); + } + + /// + /// Release resources + /// 释放资源 + /// + public void Release() + { + m_MultiSectionSkillComposerVec.Clear(); + + foreach (var kvp in m_SgcName2ComposerMap) + { + if (kvp.Value != null) + { + // TODO: Implement proper disposal if A3DSkillGfxComposer has cleanup + // kvp.Value.Release(); + } + } + + m_SgcName2ComposerMap.Clear(); + } + + /// + /// Load configuration from file + /// 从文件加载配置 + /// + /// Configuration file path / 配置文件路径 + /// Success / 是否成功 + public bool LoadConfig(string szFile) + { + // TODO: Implement file loading using Unity's file system + // This would require porting AScriptFile functionality or using Unity's TextAsset + BMLogger.LogWarning($"CECMultiSectionSkillMan.LoadConfig: Not yet implemented for {szFile}"); + + /* + // Original C++ logic: + // 1. Open script file + // 2. Parse skill ID followed by { } + // 3. Inside braces, parse comma-separated values: section,suffix,sgc + // 4. Load or reuse A3DSkillGfxComposer for each SGC file + // 5. Store SectionInfo in vector + + // Example file format: + // skill_id + // { + // section,suffix,sgc_file + // section,suffix,sgc_file + // } + */ + + return false; + } + + /// + /// Play multi-section skill effect + /// 播放多段技能特效 + /// + /// Skill ID / 技能ID + /// Skill section / 技能段数 + /// Host character ID / 施法者ID + /// Cast target ID / 施法目标ID + /// Target data list / 目标数据列表 + /// Is goblin skill / 是否为精灵技能 + public void Play(int nSkillID, int section, int nHostID, int nCastTargetID, + List Targets, bool bIsGoblinSkill = false) + { + foreach (var info in m_MultiSectionSkillComposerVec) + { + if (nSkillID == info.skill_id && section == info.section && info.pComposer != null) + { + // TODO: Implement A3DSkillGfxComposer.Play method + // info.pComposer.Play(nHostID, nCastTargetID, Targets, bIsGoblinSkill); + BMLogger.Log($"CECMultiSectionSkillMan.Play: Skill {nSkillID}, Section {section}"); + return; + } + } + } + + /// + /// Get skill GFX composer for specific skill and section + /// 获取指定技能和段数的特效组合器 + /// + /// Skill ID / 技能ID + /// Section number / 段号 + /// Skill GFX composer or null / 技能特效组合器或null + public A3DSkillGfxComposer GetSkillGfxComposer(int skill, int section) + { + foreach (var info in m_MultiSectionSkillComposerVec) + { + if (skill == info.skill_id && section == info.section && info.pComposer != null) + { + return info.pComposer; + } + } + return null; + } + + /// + /// Get section information for specific skill and section + /// 获取指定技能和段数的段信息 + /// + /// Skill ID / 技能ID + /// Section number / 段号 + /// Section info or null / 段信息或null + public SectionInfo GetSecionInfo(int skill, int section) + { + foreach (var info in m_MultiSectionSkillComposerVec) + { + if (skill == info.skill_id && section == info.section) + { + return info; + } + } + return null; + } +} + +/// +/// Placeholder class for A3DSkillGfxComposer +/// A3DSkillGfxComposer的占位类 +/// +public class A3DSkillGfxComposer +{ + public uint m_dwFlyTime; // 飞行时间 / Fly time in milliseconds + + public A3DSkillGfxComposer() + { + m_dwFlyTime = 0; + } + + /// + /// Load composer from file + /// 从文件加载组合器 + /// + public bool Load(string sgcFile) + { + // TODO: Implement SGC file loading + BMLogger.LogWarning($"A3DSkillGfxComposer.Load: Not yet implemented for {sgcFile}"); + return false; + } + + /// + /// Initialize composer + /// 初始化组合器 + /// + public void Init(object pSkillGfxMan) + { + // TODO: Implement initialization + } + + /// + /// Play skill effect + /// 播放技能特效 + /// + /* public void Play(int nHostID, int nCastTargetID, List targets, bool bIsGoblinSkill = false) + { + // TODO: Implement skill effect playback + BMLogger.Log($"A3DSkillGfxComposer.Play: Host {nHostID}, Target {nCastTargetID}"); + }*/ +} [Serializable] public class CECAttackEvent { @@ -194,241 +411,242 @@ public class CECAttackEvent return true; } -bool DoFire() -{ - float vFlyScale = 1.0f; - float vHitScale = 1.0f; + public void SetSkillSection(int nSection) { m_nSkillSection = nSection; } + bool DoFire() + { + float vFlyScale = 1.0f; + float vHitScale = 1.0f; - m_bDoFired = true; + m_bDoFired = true; - if( GPDataTypeHelper.ISPLAYERID(m_idHost) ) - { - if( m_idSkill != 0 ) - { - // const A3DSkillGfxComposer* pComposer = NULL; - // - // // we use skill composed gfx to present the skill effect - // if (m_nSkillSection>0) // ¶à¶Î¼¼ÄÜ - // { - // CECMultiSectionSkillMan pMan = m_pManager.GetMultiSkillGfxComposerMan(); - // if(pMan) - // { - // pMan->Play(m_idSkill, m_nSkillSection,m_idHost, m_idCastTarget, m_targets,GNET::ElementSkill::IsGoblinSkill(m_idSkill)); - // pComposer = pMan->GetSkillGfxComposer(m_idSkill, m_nSkillSection); - // } - // } - // else - // { - // if(GNET::ElementSkill::IsGoblinSkill(m_idSkill)) - // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets, true); - // else - // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); - // pComposer = m_pManager->GetSkillGfxComposerMan()->GetSkillGfxComposer(m_idSkill); - // } - // - // if (pComposer && pComposer->m_dwFlyTime == 0) // ¼¼ÄÜgfxûÓзÉÐÐʵ¼Ê£¬ÔòÂíÉÏÍ·¶¥Ã°×Ö - // { - // m_timeToDoDamage = 1; - // } - // else - // { - // A3DVECTOR3 vecHost, vecTarget; - // // now we estimated a time to do damage - // if( m_targets.size() && _get_pos_by_id(m_idHost, vecHost) && _get_pos_by_id(m_targets[0].idTarget, vecTarget) ) - // { - // m_timeToDoDamage = int(Magnitude(vecHost - vecTarget) / 20.0f * 1000.0f); - // a_ClampFloor(m_timeToDoDamage, 10ul); - // } - // } - } - else if( m_idWeapon != 0 ) - { - // first determine gfx used - // const char * szFlyGFX = NULL; - // const char * szHitGFX = NULL; - // - // // using weapon gfx - // DATA_TYPE dt; - // const void * pData = g_pGame->GetElementDataMan()->get_data_ptr( - // m_idWeapon, ID_SPACE_ESSENCE, dt); - // ASSERT(dt == DT_WEAPON_ESSENCE || dt == DT_PROJECTILE_ESSENCE); - // - // if( dt == DT_PROJECTILE_ESSENCE ) - // { - // // Ô¶³ÌÎäÆ÷£¬Ê¹Óõ¯Ò©µÄЧ¹û - // PROJECTILE_ESSENCE * pProjectile = (PROJECTILE_ESSENCE *) pData; - // - // szFlyGFX = pProjectile->file_firegfx + 4; // skip gfx/ - // szHitGFX = pProjectile->file_hitgfx + 4; // skip gfx/ - // } - // else if (dt == DT_WEAPON_ESSENCE) - // { - // // ½ü³ÌÎïÀí¹¥»÷£¬Ê¹ÓÃÎäÆ÷µÄЧ¹û - // WEAPON_ESSENCE * pWeapon = (WEAPON_ESSENCE *) pData; - // WEAPON_SUB_TYPE * pWeaponType = (WEAPON_SUB_TYPE*) g_pGame->GetElementDataMan()->get_data_ptr(pWeapon->id_sub_type, ID_SPACE_ESSENCE, dt); - // ASSERT(dt == DT_WEAPON_SUB_TYPE); - // szFlyGFX = NULL; - // szHitGFX = pWeaponType->file_hitgfx + 4; // skip gfx/ - // } - // - // bool bHideFlyGfx = !CECOptimize::Instance().GetGFX().CanShowFly(m_idHost); - // bool bHideHitGfx = !CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); - // int nNumTargets = m_targets.size(); - // for(int i=0; iGetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, - // pszFlyGFX, pszHitGFX, m_timeToDoDamage, false, enumLinearMove, 1, 0, NULL, vFlyScale, vHitScale, data.dwModifier); - // } - } - else - { - // without weapon - // ʹÓÃÈ­Ì×ÀàµÄ»÷ÖÐЧ¹û - DATA_TYPE dt = DATA_TYPE.DT_INVALID; - var pData = ElementDataManProvider.GetElementDataMan().get_data_ptr(183, ID_SPACE.ID_SPACE_ESSENCE, ref dt); - WEAPON_SUB_TYPE pWeaponType = (WEAPON_SUB_TYPE) pData; + if (GPDataTypeHelper.ISPLAYERID(m_idHost)) + { + if (m_idSkill != 0) + { + // const A3DSkillGfxComposer* pComposer = NULL; + // + // // we use skill composed gfx to present the skill effect + // if (m_nSkillSection>0) // ¶à¶Î¼¼ÄÜ + // { + // CECMultiSectionSkillMan pMan = m_pManager.GetMultiSkillGfxComposerMan(); + // if(pMan) + // { + // pMan->Play(m_idSkill, m_nSkillSection,m_idHost, m_idCastTarget, m_targets,GNET::ElementSkill::IsGoblinSkill(m_idSkill)); + // pComposer = pMan->GetSkillGfxComposer(m_idSkill, m_nSkillSection); + // } + // } + // else + // { + // if(GNET::ElementSkill::IsGoblinSkill(m_idSkill)) + // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets, true); + // else + // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); + // pComposer = m_pManager->GetSkillGfxComposerMan()->GetSkillGfxComposer(m_idSkill); + // } + // + // if (pComposer && pComposer->m_dwFlyTime == 0) // ¼¼ÄÜgfxûÓзÉÐÐʵ¼Ê£¬ÔòÂíÉÏÍ·¶¥Ã°×Ö + // { + // m_timeToDoDamage = 1; + // } + // else + // { + // A3DVECTOR3 vecHost, vecTarget; + // // now we estimated a time to do damage + // if( m_targets.size() && _get_pos_by_id(m_idHost, vecHost) && _get_pos_by_id(m_targets[0].idTarget, vecTarget) ) + // { + // m_timeToDoDamage = int(Magnitude(vecHost - vecTarget) / 20.0f * 1000.0f); + // a_ClampFloor(m_timeToDoDamage, 10ul); + // } + // } + } + else if (m_idWeapon != 0) + { + // first determine gfx used + // const char * szFlyGFX = NULL; + // const char * szHitGFX = NULL; + // + // // using weapon gfx + // DATA_TYPE dt; + // const void * pData = g_pGame->GetElementDataMan()->get_data_ptr( + // m_idWeapon, ID_SPACE_ESSENCE, dt); + // ASSERT(dt == DT_WEAPON_ESSENCE || dt == DT_PROJECTILE_ESSENCE); + // + // if( dt == DT_PROJECTILE_ESSENCE ) + // { + // // Ô¶³ÌÎäÆ÷£¬Ê¹Óõ¯Ò©µÄЧ¹û + // PROJECTILE_ESSENCE * pProjectile = (PROJECTILE_ESSENCE *) pData; + // + // szFlyGFX = pProjectile->file_firegfx + 4; // skip gfx/ + // szHitGFX = pProjectile->file_hitgfx + 4; // skip gfx/ + // } + // else if (dt == DT_WEAPON_ESSENCE) + // { + // // ½ü³ÌÎïÀí¹¥»÷£¬Ê¹ÓÃÎäÆ÷µÄЧ¹û + // WEAPON_ESSENCE * pWeapon = (WEAPON_ESSENCE *) pData; + // WEAPON_SUB_TYPE * pWeaponType = (WEAPON_SUB_TYPE*) g_pGame->GetElementDataMan()->get_data_ptr(pWeapon->id_sub_type, ID_SPACE_ESSENCE, dt); + // ASSERT(dt == DT_WEAPON_SUB_TYPE); + // szFlyGFX = NULL; + // szHitGFX = pWeaponType->file_hitgfx + 4; // skip gfx/ + // } + // + // bool bHideFlyGfx = !CECOptimize::Instance().GetGFX().CanShowFly(m_idHost); + // bool bHideHitGfx = !CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); + // int nNumTargets = m_targets.size(); + // for(int i=0; iGetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, + // pszFlyGFX, pszHitGFX, m_timeToDoDamage, false, enumLinearMove, 1, 0, NULL, vFlyScale, vHitScale, data.dwModifier); + // } + } + else + { + // without weapon + // ʹÓÃÈ­Ì×ÀàµÄ»÷ÖÐЧ¹û + DATA_TYPE dt = DATA_TYPE.DT_INVALID; + var pData = ElementDataManProvider.GetElementDataMan().get_data_ptr(183, ID_SPACE.ID_SPACE_ESSENCE, ref dt); + WEAPON_SUB_TYPE pWeaponType = (WEAPON_SUB_TYPE)pData; - bool bHideHitGfx = false;//!CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); - string szGFX= null; - int nNumTargets = m_targets.Count(); - for(int i=0; ifile_hitgfx + 4; // skip gfx/ - string fullGfx = pWeaponType.GetFileHitGfx(); - szGFX = fullGfx.Length > 4 ? fullGfx.Substring(4) : string.Empty; - if ((data.dwModifier & (int)MOD.MOD_NULLITY) != 0) - szGFX = "程序联入\\击中\\无效攻击击中.gfx";//Program link\Hit\InvalidAttackHit.gfx - if (bHideHitGfx) szGFX = null; - - // g_pGame->GetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, NULL, - // szGFX, m_timeToDoDamage, false, GfxMoveMode.enumLinearMove, 1, 0, NULL, vFlyScale, vHitScale, data.dwModifier); - var target = EC_ManMessageMono.Instance?.GetObject(data.idTarget, 0)?.gameObject.transform; - if (target == null) - { - BMLogger.LogError("Target is null!"); - return false; - } - var vfx = CECGameRun.Instance.GetTestVfx(); - vfx.transform.position = target.position; - CECGameRun.Instance.DestroyTestVfx(vfx, 1f); - } - } - } - else if( GPDataTypeHelper.ISNPCID(m_idHost) ) - { - // if( m_idSkill != 0 ) - // { - // const A3DSkillGfxComposer* pComposer = NULL; - // // we use skill composed gfx to present the skill effect - // if (m_nSkillSection>0) // ¶à¶Î¼¼ÄÜ - // { - // CECMultiSectionSkillMan* pMan = m_pManager->GetMultiSkillGfxComposerMan(); - // if(pMan) - // { - // pMan->Play(m_idSkill, m_nSkillSection,m_idHost, m_idCastTarget, m_targets); - // pComposer = pMan->GetSkillGfxComposer(m_idSkill, m_nSkillSection); - // } - // } - // else - // { - // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); - // pComposer = m_pManager->GetSkillGfxComposerMan()->GetSkillGfxComposer(m_idSkill); - // } - // - // if (pComposer && pComposer->m_dwFlyTime == 0) // ¼¼ÄÜûÓзÉÐÐʱ¼ä£¬ÔòÖ±½ÓÍ·¶¥Ã°×Ö - // { - // m_timeToDoDamage = 1; - // } - // else - // { - // A3DVECTOR3 vecHost, vecTarget; - // // now we estimated a time to do damage - // if( m_targets.size() && _get_pos_by_id(m_idHost, vecHost) && _get_pos_by_id(m_targets[0].idTarget, vecTarget) ) - // { - // m_timeToDoDamage = int(Magnitude(vecHost - vecTarget) / 20.0f * 1000.0f); - // a_ClampFloor(m_timeToDoDamage, 10ul); - // } - // } - // } - // else - // { - // CECNPC * pNPC = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetNPC(m_idHost); - // if (!pNPC) - // return true; - // - // const char * szFlyGFX = NULL; - // const char * szHitGFX = NULL; - // - // if (pNPC->IsMonsterNPC()) - // { - // const MONSTER_ESSENCE * pEssence = ((CECMonster *)pNPC)->GetDBEssence(); - // - // szFlyGFX = pEssence->file_gfx_short + 4; // skip gfx/ - // szHitGFX = pEssence->file_gfx_short_hit + 4; // skip gfx/ - // } - // else if (pNPC->IsPetNPC()) - // { - // //szFlyGFX = ""; - // const PET_ESSENCE* pEssence = ((CECPet*)pNPC)->GetDBEssence(); - // - // szFlyGFX = pEssence->file_gfx_short + 4; //"²ß»®ÁªÈë\\ͨÓ÷ÉÐÐ\\¹­¼ý·ÉÐÐ.gfx"; - // - // szHitGFX = "²ß»®ÁªÈë\\¹ÖÎï»÷ÖÐ\\¹ÖÎïÈⲫ»÷ÖÐ.gfx"; - // } - // else - // return false; - // - // if( !szFlyGFX[0] ) - // szFlyGFX = NULL; - // - // if( !szHitGFX[0] ) - // szHitGFX = NULL; - // - // bool bHideFlyGfx = !CECOptimize::Instance().GetGFX().CanShowFly(m_idHost); - // bool bHideHitGfx = !CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); - // int nNumTargets = m_targets.size(); - // for(int i=0; iGetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, - // pszFlyGFX, pszHitGFX, m_timeToDoDamage, false, enumLinearMove, 1, - // 0, NULL, vFlyScale, vHitScale, data.dwModifier); - // } - // } - } - else - return true; - - return true; -} + bool bHideHitGfx = false;//!CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); + string szGFX = null; + int nNumTargets = m_targets.Count(); + for (int i = 0; i < nNumTargets; i++) + { + TARGET_DATA data = m_targets[i]; + + // szGFX = pWeaponType->file_hitgfx + 4; // skip gfx/ + string fullGfx = pWeaponType.GetFileHitGfx(); + szGFX = fullGfx.Length > 4 ? fullGfx.Substring(4) : string.Empty; + if ((data.dwModifier & (int)MOD.MOD_NULLITY) != 0) + szGFX = "程序联入\\击中\\无效攻击击中.gfx";//Program link\Hit\InvalidAttackHit.gfx + if (bHideHitGfx) szGFX = null; + + // g_pGame->GetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, NULL, + // szGFX, m_timeToDoDamage, false, GfxMoveMode.enumLinearMove, 1, 0, NULL, vFlyScale, vHitScale, data.dwModifier); + var target = EC_ManMessageMono.Instance?.GetObject(data.idTarget, 0)?.gameObject.transform; + if (target == null) + { + BMLogger.LogError("Target is null!"); + return false; + } + var vfx = CECGameRun.Instance.GetTestVfx(); + vfx.transform.position = target.position; + CECGameRun.Instance.DestroyTestVfx(vfx, 1f); + } + } + } + else if (GPDataTypeHelper.ISNPCID(m_idHost)) + { + // if( m_idSkill != 0 ) + // { + // const A3DSkillGfxComposer* pComposer = NULL; + // // we use skill composed gfx to present the skill effect + // if (m_nSkillSection>0) // ¶à¶Î¼¼ÄÜ + // { + // CECMultiSectionSkillMan* pMan = m_pManager->GetMultiSkillGfxComposerMan(); + // if(pMan) + // { + // pMan->Play(m_idSkill, m_nSkillSection,m_idHost, m_idCastTarget, m_targets); + // pComposer = pMan->GetSkillGfxComposer(m_idSkill, m_nSkillSection); + // } + // } + // else + // { + // m_pManager->GetSkillGfxComposerMan()->Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); + // pComposer = m_pManager->GetSkillGfxComposerMan()->GetSkillGfxComposer(m_idSkill); + // } + // + // if (pComposer && pComposer->m_dwFlyTime == 0) // ¼¼ÄÜûÓзÉÐÐʱ¼ä£¬ÔòÖ±½ÓÍ·¶¥Ã°×Ö + // { + // m_timeToDoDamage = 1; + // } + // else + // { + // A3DVECTOR3 vecHost, vecTarget; + // // now we estimated a time to do damage + // if( m_targets.size() && _get_pos_by_id(m_idHost, vecHost) && _get_pos_by_id(m_targets[0].idTarget, vecTarget) ) + // { + // m_timeToDoDamage = int(Magnitude(vecHost - vecTarget) / 20.0f * 1000.0f); + // a_ClampFloor(m_timeToDoDamage, 10ul); + // } + // } + // } + // else + // { + // CECNPC * pNPC = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetNPC(m_idHost); + // if (!pNPC) + // return true; + // + // const char * szFlyGFX = NULL; + // const char * szHitGFX = NULL; + // + // if (pNPC->IsMonsterNPC()) + // { + // const MONSTER_ESSENCE * pEssence = ((CECMonster *)pNPC)->GetDBEssence(); + // + // szFlyGFX = pEssence->file_gfx_short + 4; // skip gfx/ + // szHitGFX = pEssence->file_gfx_short_hit + 4; // skip gfx/ + // } + // else if (pNPC->IsPetNPC()) + // { + // //szFlyGFX = ""; + // const PET_ESSENCE* pEssence = ((CECPet*)pNPC)->GetDBEssence(); + // + // szFlyGFX = pEssence->file_gfx_short + 4; //"²ß»®ÁªÈë\\ͨÓ÷ÉÐÐ\\¹­¼ý·ÉÐÐ.gfx"; + // + // szHitGFX = "²ß»®ÁªÈë\\¹ÖÎï»÷ÖÐ\\¹ÖÎïÈⲫ»÷ÖÐ.gfx"; + // } + // else + // return false; + // + // if( !szFlyGFX[0] ) + // szFlyGFX = NULL; + // + // if( !szHitGFX[0] ) + // szHitGFX = NULL; + // + // bool bHideFlyGfx = !CECOptimize::Instance().GetGFX().CanShowFly(m_idHost); + // bool bHideHitGfx = !CECOptimize::Instance().GetGFX().CanShowHit(m_idHost); + // int nNumTargets = m_targets.size(); + // for(int i=0; iGetGameRun()->GetWorld()->GetSkillGfxMan()->AddSkillGfxEvent(m_idHost, data.idTarget, + // pszFlyGFX, pszHitGFX, m_timeToDoDamage, false, enumLinearMove, 1, + // 0, NULL, vFlyScale, vHitScale, data.dwModifier); + // } + // } + } + else + return true; + + return true; + } private bool DoDamage() { @@ -477,7 +695,7 @@ bool DoFire() else if (GPDataTypeHelper.ISPLAYERID(idTarget)) { - CECPlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idTarget); + CECPlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idTarget); //BMLogger.LogError("HoangDev: CECPlayer pPlayer = " + pPlayer ); if (!pPlayer) @@ -606,15 +824,15 @@ enum MOD }; enum GfxMoveMode { - enumLinearMove = 0, // Linear - enumParabolicMove, // Parabolic - enumMissileMove, // Missile - enumMeteoricMove, // Meteoric (shooting star) - enumHelixMove, // Helix (spiral) - enumCurvedMove, // Curved - enumAccMove, // Accelerated - enumOnTarget, // Targeted - enumLink, // Linked - enumRandMove, // Random movement - enumMoveModeNum + enumLinearMove = 0, // Linear + enumParabolicMove, // Parabolic + enumMissileMove, // Missile + enumMeteoricMove, // Meteoric (shooting star) + enumHelixMove, // Helix (spiral) + enumCurvedMove, // Curved + enumAccMove, // Accelerated + enumOnTarget, // Targeted + enumLink, // Linked + enumRandMove, // Random movement + enumMoveModeNum }; \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs index 3f644ea63f..393465229e 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs @@ -66,6 +66,9 @@ namespace PerfectWorld.Scripts.Managers case int value when value == EC_MsgDef.MSG_PM_PLAYERRUNOUT: OnMsgPlayerRunOut(Msg); break; + case int value when value == EC_MsgDef.MSG_PM_CASTSKILL: + TransmitMessage(Msg); + break; } } else @@ -496,12 +499,12 @@ namespace PerfectWorld.Scripts.Managers { } - public bool TransmitMessage(ECMSG msg) + public bool TransmitMessage(ECMSG Msg) { int cid = 0; var host = CECGameRun.Instance.GetHostPlayer(); - switch (msg.dwMsg) + switch (Msg.dwMsg) { /*case long value when value == EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: if (msg.dwParam2 == S2C.EQUIP_DATA) @@ -511,7 +514,7 @@ namespace PerfectWorld.Scripts.Managers break;*/ case long value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: - cid = (int)((playerbaseinfo_re)msg.dwParam1).Player.id; + cid = (int)((playerbaseinfo_re)Msg.dwParam1).Player.id; // Xoá khỏi cache //g_pGame.GetGameSession().GetC2SCmdCache().RemovePlayerBaseInfo(cid); break; @@ -527,8 +530,20 @@ namespace PerfectWorld.Scripts.Managers cid = ((cmd_object_landing)msg.dwParam1).object_id; break;*/ case long value when value == EC_MsgDef.MSG_PM_PLAYERATKRESULT: - cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes((byte[])msg.dwParam1); + cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); cid = pCmdAtk.attacker_id; + break; + case long value when value == EC_MsgDef.MSG_PM_CASTSKILL: + switch (Convert.ToInt32(Msg.dwParam2)) + { + case int value2 when value2 == CommandID.OBJECT_CAST_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[]) Msg.dwParam1)).caster; break; + /* case CommandID.OBJECT_CAST_INSTANT_SKILL: cid = ((cmd_object_cast_instant_skill*)Msg.dwParam1)->caster; break; + case CommandID.OBJECT_CAST_POS_SKILL: cid = ((cmd_object_cast_pos_skill*)Msg.dwParam1)->caster; break; + case CommandID.SKILL_INTERRUPTED: cid = ((cmd_skill_interrupted*)Msg.dwParam1)->caster; break; + case CommandID.PLAYER_CAST_RUNE_SKILL: cid = ((cmd_player_cast_rune_skill*)Msg.dwParam1)->caster; break; + case CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: cid = ((cmd_player_cast_rune_instant_skill*)Msg.dwParam1)->caster; break;*/ + } + break; // ⚠️ Các case khác cũng tương tự, chỉ việc lấy ra đúng trường id / caster / user ... // Do quá dài nên bạn có thể copy dần từng case từ C++ sang. @@ -546,13 +561,13 @@ namespace PerfectWorld.Scripts.Managers if (host != null && cid == host.GetCharacterID()) { - host.ProcessMessage(msg); + host.ProcessMessage(Msg); } else { var elsePlayer = SeekOutElsePlayer(cid); if (elsePlayer != null) - elsePlayer.ProcessMessage(msg); + elsePlayer.ProcessMessage(Msg); } return true; diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 64cb67ac78..504a003e8e 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -21,8 +21,9 @@ public abstract class CECPlayer : CECObject { [SerializeField] protected Transform parentModel; [SerializeField] protected TextMeshProUGUI txtName; - + [ResetStatic] private static PLAYER_ACTION[] _default_actions; + [ResetStatic] private static PLAYER_ACTION[] _turning_actions; PLAYER_ACTION[] m_PlayerActions; [SerializeField] internal INFO m_PlayerInfo; @@ -56,6 +57,8 @@ public abstract class CECPlayer : CECObject protected static PLAYER_LEVELEXP_CONFIG _player_levelup_exp; private CECPlayerActionController m_pActionController; private enumWingType m_wingType; + private int m_idCurSkillTarget; + protected CECSkill m_pCurSkill; protected int NUM_WEAPON_TYPE = 15; public static readonly int[] m_sciStateIDForStateAction = { 117 }; @@ -158,6 +161,7 @@ public abstract class CECPlayer : CECObject m_iFashionWeaponType = -1; m_uAttackType = DEFAULT_ACTION_TYPE; + AttachWeapon(); } @@ -182,6 +186,7 @@ public abstract class CECPlayer : CECObject public static void InitStaticRes() { + BMLogger.LogError("HoangDev: InitStaticRes"); BuildActionList(); DATA_TYPE dt = default; _player_levelup_exp = (PLAYER_LEVELEXP_CONFIG)ElementDataManProvider.GetElementDataMan() @@ -208,7 +213,7 @@ public abstract class CECPlayer : CECObject { if (_default_actions == null) { - // thay cho hashtab trong C++ + Dictionary actionMap = new Dictionary(100); @@ -234,10 +239,18 @@ public abstract class CECPlayer : CECObject { if (!actionMap.TryAdd(data.ActionName, data)) { + + } + } + if (!string.IsNullOrEmpty(data.ActionName) && data.ActionName[0] != '0') + { + if (!skillActionMap.TryAdd(data.Name, data)) + { + } } } - + /// CECStringTab actionNames = new CECStringTab(); actionNames.Init("actions_player", false); @@ -542,89 +555,88 @@ public abstract class CECPlayer : CECObject if (CECAttacksMan.Instance.FindAttackByAttacker(GetPlayerInfo().cid)) { - BMLogger.LogError("CECAttacksMan::FindAttackByAttacker != NULL"); ClearComActFlagAllRankNodes(true); } // melee attack - CECAttackEvent pAttack = CECAttacksMan.Instance.AddMeleeAttack( + CECAttackEvent pAttack1 = CECAttacksMan.Instance.AddMeleeAttack( GetPlayerInfo().cid, idTarget, idWeapon, dwModifier, nDamage, nTimeFly); - if (pAttack != null) + if (pAttack1 != null) { if (!IsDead() && (dwModifier & (uint)MOD.MOD_RETORT) == 0 && (dwModifier & (uint)MOD.MOD_ATTACK_AURA) == 0 - && PlayAttackAction(nAttackSpeed, ref piAttackTime, pAttack) + && PlayAttackAction(nAttackSpeed, ref piAttackTime, pAttack1) && (dwModifier & (uint)MOD.MOD_BEAT_BACK) == 0) { } else + { + pAttack1.m_bSignaled = true; + } + } + } + else + { + if (skillLevel == 0) + { + if (m_pCurSkill != null) + skillLevel = m_pCurSkill.GetSkillLevel(); + else + skillLevel = 1; + } + + CECAttackEvent pAttack = null; + + // first try to find if there is already a skill attack event in attackman + CECAttackerEvents attackerEvents = CECAttacksMan.Instance.FindAttackByAttacker(GetPlayerInfo().cid); + if (attackerEvents) + { + CECAttackEvent pAttack1 = attackerEvents.Find(idSkill, nSection); + if (pAttack1 != null) + { + // Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢ + pAttack1.AddTarget(idTarget, dwModifier, nDamage); + goto EXIT; + } + else + { + attackerEvents.Signal(); + } + } + if (ElementSkill.IsGoblinSkill((uint)idSkill) && + ElementSkill.GetType((uint)idSkill) == 2) + { + pAttack = CECAttacksMan.Instance.AddSkillAttack( + GetPlayerInfo().cid, GetPlayerInfo().cid, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); + } + else + { + // begin a skill attack + pAttack = CECAttacksMan.Instance.AddSkillAttack( + GetPlayerInfo().cid, m_idCurSkillTarget, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); + } + + if (pAttack != null) + { + pAttack.SetSkillSection(nSection); + if (!IsDead() && (dwModifier & (uint)MOD.MOD_RETORT) == 0 + && (dwModifier & (uint)MOD.MOD_ATTACK_AURA) == 0 + && PlaySkillAttackAction(idSkill, nAttackSpeed, null, nSection, &pAttack.m_bSignaled) + && (dwModifier & (uint)MOD.MOD_BEAT_BACK) == 0) + { + } + else { pAttack.m_bSignaled = true; } } + + EXIT: + // // For skill attacking, time is always set to 0 + if (piAttackTime != 0) + piAttackTime = 0; } - //else - //{ - // if (skillLevel == 0) - // { - // if (m_pCurSkill) - // skillLevel = m_pCurSkill.GetSkillLevel(); - // else - // skillLevel = 1; - // } - - // CECAttackEvent pAttack1 = null; - - // // first try to find if there is already a skill attack event in attackman - // CECAttackerEvents attackerEvents = g_pGame.GetGameRun().GetWorld().GetAttacksMan().FindAttackByAttacker(GetPlayerInfo().cid); - // if (attackerEvents) - // { - // CECAttackEvent pAttack1 = attackerEvents.Find(idSkill, nSection); - // if (pAttack1 != null) - // { - // // Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢ - // pAttack1.AddTarget(idTarget, dwModifier, nDamage); - // goto EXIT; - // } - // else - // { - // attackerEvents.Signal(); - // } - // } - // if (ElementSkill::IsGoblinSkill(idSkill) && - // GNET::ElementSkill::GetType(idSkill) == 2) - // { - // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( - // GetPlayerInfo().cid, GetPlayerInfo().cid, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); - // } - // else - // { - // // begin a skill attack - // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( - // GetPlayerInfo().cid, m_idCurSkillTarget, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); - // } - - // if (pAttack) - // { - // pAttack.SetSkillSection(nSection); - // if (!IsDead() && (dwModifier & CECAttackEvent::MOD_RETORT) == 0 - // && (dwModifier & CECAttackEvent::MOD_ATTACK_AURA) == 0 - // && PlaySkillAttackAction(idSkill, nAttackSpeed, NULL, nSection, &pAttack.m_bSignaled) - // && (dwModifier & CECAttackEvent::MOD_BEAT_BACK) == 0) - // { - // } - // else - // { - // pAttack.m_bSignaled = true; - // } - // } - - //EXIT: - // // For skill attacking, time is always set to 0 - // if (piAttackTime) - // *piAttackTime = 0; - //} } private void ClearComActFlagAllRankNodes(bool v) @@ -1135,9 +1147,7 @@ public abstract class CECPlayer : CECObject 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) + if (!_default_skill_actions.TryGetValue((uint)idSkill, out PLAYER_ACTION_INFO_CONFIG data) || 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) @@ -1154,6 +1164,7 @@ public abstract class CECPlayer : CECObject if (GetMoveEnv() == (int)MoveEnvironment.MOVEENV_GROUND) { szAct = EC_Utility.BuildActionName(data, weapon_type, "_Ò÷³ª_"); + BMLogger.LogError($"HoangDev: PlaySkillCastAction szAct={szAct} "); } else { @@ -1197,6 +1208,171 @@ public abstract class CECPlayer : CECObject return m_pActionController != null && m_pActionController.PlaySkillCastActionWithName(idSkill, szActName, bNoFX); } + + public bool PlaySkillAttackAction(int idSkill, int nAttackSpeed, ref int piAttackTime, int nSection = 0, CECAttackEvent attackEvent = null) + { + if (_pPlayerModel == null) + return false; + + string szAct = ""; + + int weapon_type = GetShowingWeaponType(); + + if (!_default_skill_actions.TryGetValue((uint)idSkill, out PLAYER_ACTION_INFO_CONFIG data) || + data.action_prefix == null || data.action_prefix.Length == 0 || data.action_prefix[0] == 0) + { + // Check if it's a target item skill / 检查是否为目标道具技能 + // if (GNET::ElementSkill::GetCommonCoolDown(idSkill) > 1 << 4) + // { + // data = m_PlayerActions[(int)PLAYER_ACTION_TYPE.ACT_USING_TARGET_ITEM].data; + // if (data == null || data.action_prefix == null || data.action_prefix.Length == 0 || data.action_prefix[0] == 0) + // return false; + // } + // else + return false; + } + + int nTime1, nTime2; + bool bInfinite = false; + // CECModel pRightHandWeapon = GetRightHandWeapon(); // 获取右手武器 / Get right hand weapon + bool bHideFX = false; // !CECOptimize::Instance().GetGFX().CanShowAttack(GetCharacterID(), GetClassID()); + + var atkMan = CECAttacksMan.Instance; + + if (GetMoveEnv() == (int)MoveEnvironment.MOVEENV_GROUND) + { + // 地面动作 / Ground action + + szAct = EC_Utility.BuildActionName(data, weapon_type, "_施法行_"); + GetSkillSectionActionName(ref szAct, idSkill, nSection); + + if (!PlaySkillAttackActionWithName(idSkill, szAct, bHideFX, attackEvent)) + { + return false; + } + + // nTime1 = m_pPlayerModel->GetComActTimeSpanByName(szAct); // 获取动作时长 / Get action time span + // pAct = m_pPlayerModel->GetComActByName(szAct); + // if (pAct) bInfinite |= pAct->IsInfinite(); + nTime1 = 1000; // 临时值 / Temporary value + + szAct = $"{data.action_prefix}_施法行_{data.action_weapon_suffix[weapon_type].suffix}"; + GetSkillSectionActionName(ref szAct, idSkill, nSection); + QueueSkillAttackActionWithName(idSkill, szAct, 0, bHideFX); + + // nTime2 = m_pPlayerModel->GetComActTimeSpanByName(szAct); + // pAct = m_pPlayerModel->GetComActByName(szAct); + // if (pAct) bInfinite |= pAct->IsInfinite(); + nTime2 = 1000; // 临时值 / Temporary value + } + else + { + // 空中动作 / Air action + string szActionMiddleName = null; + if ((m_wingType == enumWingType.WINGTYPE_WING && IsFlying()) || + (m_iProfession == PROFESSION.PROF_ANGEL) || + (m_iProfession == PROFESSION.PROF_ARCHOR) || + (m_iProfession == PROFESSION.PROF_MONK) || + (m_iProfession == PROFESSION.PROF_GHOST)) + { + szActionMiddleName = "空中翅膀"; // Air with wings / 空中翅膀 + } + else + { + szActionMiddleName = "空中飞剑"; // Air with sword / 空中飞剑 + } + + szAct = $"{data.action_prefix}_{szActionMiddleName}_施法行_{data.action_weapon_suffix[weapon_type].suffix}"; + GetSkillSectionActionName(ref szAct, idSkill, nSection); + + if (!PlaySkillAttackActionWithName(idSkill, szAct, bHideFX, attackEvent)) + { + return false; + } + + // if (pRightHandWeapon && IsUsingMagicWeapon()) + // pRightHandWeapon->PlayActionByName(_GenWeaponActionName(szAct, m_iGender), 1.0f, true, 200, true, ACT_CASTSKILL, bHideFX); + + // nTime1 = m_pPlayerModel->GetComActTimeSpanByName(szAct); + // pAct = m_pPlayerModel->GetComActByName(szAct); + // if (pAct) bInfinite |= pAct->IsInfinite(); + nTime1 = 1000; // 临时值 / Temporary value + + szAct = $"{data.action_prefix}_{szActionMiddleName}_施法行_{data.action_weapon_suffix[weapon_type].suffix}"; + GetSkillSectionActionName(ref szAct, idSkill, nSection); + QueueSkillAttackActionWithName(idSkill, szAct, 0, bHideFX); + + // if (pRightHandWeapon && IsUsingMagicWeapon()) + // pRightHandWeapon->QueueAction(_GenWeaponActionName(szAct, m_iGender), 0, ACT_CASTSKILL, false, false, bHideFX); + + // nTime2 = m_pPlayerModel->GetComActTimeSpanByName(szAct); + // pAct = m_pPlayerModel->GetComActByName(szAct); + // if (pAct) bInfinite |= pAct->IsInfinite(); + nTime2 = 1000; // 临时值 / Temporary value + } + + // int nExecuteTime = GNET::ElementSkill::GetExecuteTime(idSkill, 0); // 获取技能执行时间 / Get skill execute time + int nExecuteTime = 2000; // 临时值 / Temporary value + + // 调整动画速度以匹配攻击速度 / Adjust animation speed to match attack speed + if (!bInfinite) + { + if (nExecuteTime > 0) + { + float vScale = (nTime1 + nTime2) / (float)nExecuteTime; + // m_pPlayerModel->SetPlaySpeed(vScale * 1.2f); + + // if (pRightHandWeapon && IsUsingMagicWeapon()) + // pRightHandWeapon->SetPlaySpeed(vScale * 1.2f); + } + + piAttackTime = nTime1 + nTime2; + } + else + { + // 动作循环,返回技能决定的执行时间 / Action loops, return skill-determined execute time + piAttackTime = nExecuteTime; + } + + ShowWeaponByConfig(data); + // UpdateWeaponHangerPosBySkillAction(idSkill); // 根据技能播放的动作,更改武器悬挂位置 / Update weapon hanger position based on skill action + return true; + } + + // 播放技能攻击动作(带名称)/ Play skill attack action with name + public bool PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX = false, CECAttackEvent attackEvent = null) + { + return m_pActionController != null + && m_pActionController.PlaySkillAttackActionWithName(idSkill, szActName, bNoFX, attackEvent != null ? new bool[] { attackEvent.m_bSignaled } : null, 0); + } + + // 队列播放技能攻击动作(带名称)/ Queue skill attack action with name + 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_pActionController != null + && m_pActionController.QueueSkillAttackActionWithName(idSkill, szActName, nTransTime, bNoFX, bResetSpeed, bResetActFlag, pNewActFlag, dwNewFlagMode); + } + + // 获取技能段动作名称 / Get skill section action name + private void GetSkillSectionActionName(ref string szAct, int idSkill, int nSection) + { + if (nSection != 0) + { + var pAtkMan = CECAttacksMan.Instance; + if (pAtkMan != null && pAtkMan.GetSkillSectionActionSuffix(idSkill, nSection, out string suffix)) + { + szAct += "_" + suffix; + } + } + } + + // 判断是否在飞行 / Check if flying + public bool IsFlying() + { + // 临时实现 / Temporary implementation + return m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR; + } + bool GetSkillStateActionName(int skill, int state, string name1, string name2) { /* for (int i = 0; i < (int)m_SkillStateActionVec.size(); i++) diff --git a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs index b47d2d58c4..1d286de50a 100644 --- a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs +++ b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs @@ -203,7 +203,13 @@ namespace BrewMonster.Scripts.Skills public virtual int SetLevel(int level) { return 0; } // ������󼶱�? public virtual int GetMaxLevel() { return 0; } - + public static byte GetType(uint id) + { + SkillStub s = SkillStub.GetStub(id); + if (s != null) + return s.GetType(); + return 0; + } // �Ƿ��������� public virtual bool IsWarmup() { return false; } // ʹ�ú��Ƿ��Զ����� diff --git a/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs b/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs new file mode 100644 index 0000000000..335ede5380 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace BrewMonster +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public sealed class ResetStaticAttribute : Attribute + { + public bool ClearCollection { get; } + + public ResetStaticAttribute(bool clearCollection = false) + { + ClearCollection = clearCollection; + } + } +} + + diff --git a/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs.meta b/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs.meta new file mode 100644 index 0000000000..19e68673e2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Utils/ResetStaticAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c3a0cdab3c60efe4c922639dc31fd198 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs b/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs new file mode 100644 index 0000000000..474a3a6fd1 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs @@ -0,0 +1,181 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace BrewMonster +{ + public static class ResetStaticUtility + { + private const string AutoRunOnPlayPrefsKey = "ResetStaticUtility.AutoRunOnPlay"; + + public static int ResetAll(bool logEach = false, Func assemblyFilter = null) + { + int resetCount = 0; + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var assembly in assemblies) + { + if (assembly.IsDynamic) + { + continue; + } + + if (assemblyFilter == null) + { + string name = assembly.GetName().Name; + if (!(name == "Assembly-CSharp" || name == "Assembly-CSharp-firstpass" || name.StartsWith("Assembly-CSharp-"))) + { + continue; + } + } + else if (!assemblyFilter(assembly)) + { + continue; + } + + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types.Where(t => t != null).ToArray(); + } + catch (Exception ex) + { + Debug.LogWarning($"[ResetStatic] Failed to enumerate types for assembly {assembly.FullName}: {ex}"); + continue; + } + + foreach (var type in types) + { + if (type == null) + { + continue; + } + + FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + foreach (var field in fields) + { + if (field.IsLiteral || field.IsInitOnly) + { + continue; + } + + var attr = field.GetCustomAttribute(false); + if (attr == null) + { + continue; + } + + try + { + object newValue = null; + object currentValue = field.GetValue(null); + + if (field.FieldType.IsValueType) + { + newValue = Activator.CreateInstance(field.FieldType); + field.SetValue(null, newValue); + resetCount++; + if (logEach) + { + Debug.Log($"[ResetStatic] {type.FullName}.{field.Name} set to default({field.FieldType.Name})."); + } + continue; + } + + if (attr.ClearCollection && currentValue != null) + { + bool cleared = TryClearCollection(currentValue); + if (cleared) + { + resetCount++; + if (logEach) + { + Debug.Log($"[ResetStatic] {type.FullName}.{field.Name} collection cleared."); + } + continue; + } + } + + field.SetValue(null, null); + resetCount++; + if (logEach) + { + Debug.Log($"[ResetStatic] {type.FullName}.{field.Name} set to null."); + } + } + catch (Exception ex) + { + Debug.LogWarning($"[ResetStatic] Failed to reset {type.FullName}.{field.Name}: {ex}"); + } + } + } + } + + return resetCount; + } + + private static bool TryClearCollection(object instance) + { + try + { + if (instance is System.Collections.IDictionary dictionary) + { + dictionary.Clear(); + return true; + } + + if (instance is System.Collections.IList list) + { + list.Clear(); + return true; + } + + // Try generic ICollection.Clear() + var clearMethod = instance.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); + if (clearMethod != null) + { + clearMethod.Invoke(instance, null); + return true; + } + } + catch (Exception ex) + { + Debug.LogWarning($"[ResetStatic] Failed to clear collection of type {instance.GetType().FullName}: {ex}"); + } + + return false; + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void AutoResetOnPlay() + { +#if UNITY_EDITOR + bool enabled = UnityEditor.EditorPrefs.GetBool(AutoRunOnPlayPrefsKey, true); + if (!enabled) + { + return; + } +#endif + ResetAll(logEach: false); + } + +#if UNITY_EDITOR + public static bool GetAutoRunOnPlay() + { + return UnityEditor.EditorPrefs.GetBool(AutoRunOnPlayPrefsKey, true); + } + + public static void SetAutoRunOnPlay(bool enabled) + { + UnityEditor.EditorPrefs.SetBool(AutoRunOnPlayPrefsKey, enabled); + } +#endif + } +} + + diff --git a/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs.meta b/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs.meta new file mode 100644 index 0000000000..40c1547c2d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Utils/ResetStaticUtility.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1e3ede496bb135a4da6506adbcb7bbf2 \ No newline at end of file diff --git a/Assets/Scenes/NPCRender.unity b/Assets/Scenes/NPCRender.unity index e59642630a..7c0e25ec98 100644 --- a/Assets/Scenes/NPCRender.unity +++ b/Assets/Scenes/NPCRender.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:951300c91d8eb0444ef6f6d4231547c757908cba80c036bc743d186711fca527 -size 33032375 +oid sha256:37b57414ce51a79ba81ede1be7122f4a6b5b4fc856e9bb741aba9e533fd8d64c +size 33032374 diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 94ccd29f8f..656053b13e 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -61,7 +61,6 @@ 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(); @@ -409,7 +408,6 @@ public partial class CECHostPlayer : CECPlayer 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; - } } @@ -422,8 +420,7 @@ public partial class CECHostPlayer : CECPlayer bool bActionStartSkill = false; int iActionTime = 1000; //CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper(); - BMLogger.LogError("HoangDev OnMsgPlayerCastSkill Msg.dwParam2 " + (int)Msg.dwParam2); - switch ((int)Msg.dwParam2) + switch (Convert.ToInt32(Msg.dwParam2)) { case CommandID.OBJECT_CAST_SKILL: { @@ -781,7 +778,7 @@ public partial class CECHostPlayer : CECPlayer break;*/ } - if (bActionStartSkill) + /* if (bActionStartSkill) AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime); if (bDoOtherThing) @@ -798,7 +795,7 @@ public partial class CECHostPlayer : CECPlayer if (idTarget != 0 && idTarget != m_PlayerInfo.cid) NormalAttackObject(idTarget, true); } - } + }*/ } private void OnMsgHstSkillData(ECMSG Msg) { @@ -1452,7 +1449,14 @@ public partial class CECHostPlayer : CECPlayer isRun = value; } + public override void SetUpPlayer() + { + base.SetUpPlayer(); + m_IncantCnt = new CECCounter(); + m_IncantCnt.SetPeriod(1000); + m_IncantCnt.Reset(true); + } public void InitCharacter(cmd_self_info_1 role) { SetUpPlayer();