using BrewMonster; using BrewMonster.Managers; using BrewMonster.Scripts.Skills; using CSNetwork.GPDataType; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using ModelRenderer.Scripts.Common; using BrewMonster.Scripts; using BrewMonster.Config; using UnityEngine; using Cysharp.Threading.Tasks; namespace BrewMonster { public class CECAttacksMan : MonoSingleton { [SerializeField] private SkillStateActionConfig skillStateActionConfig; private struct SkillStateActionEntry { public int skill; public int state; public string beHitAction; public string stayDownAction; } private readonly List m_SkillStateActionVec = new List(); private LinkedList m_targets = new LinkedList(); public CECMultiSectionSkillMan m_pMultiSkillGfxComposerMan; protected A3DSkillGfxComposerMan m_pSkillGfxComposerMan; #if UNITY_EDITOR public List m_AttackList = new List(); #endif protected override void Awake() { base.Awake(); } protected override void OnDestroy() { m_targets = null; m_SkillStateActionVec.Clear(); SkillGfxMan.InstanceSub.Dispose(); base.OnDestroy(); } private void Start() { StartLoad(); } private async void StartLoad() { SetupAttacksMan(); // Get the skill map to check if it's populated var skillMap = SkillStub.GetMap(); if (skillMap == null || skillMap.Count == 0) { BMLogger.LogWarning("CECAttacksMan::Start() - Skill map is empty, skipping GFX loading"); return; } LoadAllSkillGfxAsync(); } /// /// Load GFX for a specific skill on-demand (async, non-blocking) /// Call this when a skill is about to be used for the first time /// public async void LoadSkillGfxOnDemand(uint skillId) { // Check if already loaded if (m_pSkillGfxComposerMan.GetSkillGfxComposer((int)skillId) != null) return; // Already loaded // Get SkillStub instance / 获取技能存根实例 SkillStub skillStub = SkillStub.GetStub(skillId); if (skillStub == null) { BMLogger.LogWarning($"CECAttacksMan::LoadSkillGfxOnDemand() - SkillStub not found for skill {skillId}"); return; } (string flyGFXPath, string hitGrdGFXPath, string hitGFXPath) = ElementSkill.GetAllGFX(skillId); // Pass skillStub to LoadOneComposerAsync / 将技能存根传递给LoadOneComposerAsync bool loaded = await m_pSkillGfxComposerMan.LoadOneComposerAsync((int)skillId, skillStub, flyGFXPath, hitGrdGFXPath, hitGFXPath); if (!loaded) { BMLogger.LogWarning($"CECAttacksMan::LoadSkillGfxOnDemand() - Failed to load GFX for skill {skillId}"); } } public async void LoadAllSkillGfxAsync() { uint idSkill = 0; var skillMap = SkillStub.GetMap(); if (skillMap == null || skillMap.Count == 0) { BMLogger.LogWarning("CECAttacksMan::LoadAllSkillGfxAsync() - Skill map is empty"); return; } BMLogger.Log($"CECAttacksMan::LoadAllSkillGfxAsync() - Loading GFX for {skillMap.Count} skills..."); int loadedCount = 0; int failedCount = 0; while (true) { idSkill = ElementSkill.NextSkill(idSkill); if (idSkill == 0) break; // Get SkillStub instance / 获取技能存根实例 SkillStub skillStub = SkillStub.GetStub(idSkill); if (skillStub == null) { BMLogger.LogWarning( $"CECAttacksMan::LoadAllSkillGfxAsync() - SkillStub not found for skill {idSkill}"); failedCount++; continue; } (string flyGFXPath, string hitGrdGFXPath, string hitGFXPath) = ElementSkill.GetAllGFX(idSkill); // Use await instead of blocking .Result to prevent freezing // Pass skillStub to LoadOneComposerAsync / 将技能存根传递给LoadOneComposerAsync bool loaded = await m_pSkillGfxComposerMan.LoadOneComposerAsync((int)idSkill, skillStub, flyGFXPath, hitGrdGFXPath, hitGFXPath); if (loaded) loadedCount++; else failedCount++; // Yield every 10 skills to keep Unity responsive if ((loadedCount + failedCount) % 10 == 0) { await UniTask.Yield(); } } BMLogger.Log( $"CECAttacksMan::LoadAllSkillGfxAsync() - Complete. Loaded: {loadedCount}, Failed: {failedCount}"); //TODO: convert this part m_pMultiSkillGfxComposerMan = new CECMultiSectionSkillMan(); if (m_pMultiSkillGfxComposerMan == null || !m_pMultiSkillGfxComposerMan.LoadConfig("multi_section_skill")) { BMLogger.LogError("CECAttacksMan::CECAttacksMan(), failed to load multi skill sgc config file "); } if (!LoadSkillStateActionConfig()) BMLogger.LogError( "CECAttacksMan::LoadSkillStateActionConfig(), failed — assign Skill State Action Config on CECAttacksMan."); } public void SetupAttacksMan() { m_pSkillGfxComposerMan = new A3DSkillGfxComposerMan(); uint idSkill = 0; } /// /// Loads skill/state → action names into from (parity with C++ CECAttacksMan::LoadSkillStateActionConfig). /// public bool LoadSkillStateActionConfig() { m_SkillStateActionVec.Clear(); SkillStateActionConfig cfg = skillStateActionConfig; if (cfg == null) { BMLogger.LogWarning( "CECAttacksMan::LoadSkillStateActionConfig(), skillStateActionConfig is not assigned on CECAttacksMan."); return false; } IReadOnlyList rows = cfg.Entries; if (rows == null || rows.Count == 0) { BMLogger.LogWarning("CECAttacksMan::LoadSkillStateActionConfig(), config has no entries."); return false; } for (int i = 0; i < rows.Count; i++) { SkillStateActionRow r = rows[i]; m_SkillStateActionVec.Add(new SkillStateActionEntry { skill = r.skill, state = r.state, beHitAction = r.beHitAction ?? string.Empty, stayDownAction = r.stayDownAction ?? string.Empty }); } return true; } public bool GetSkillStateActionName(int skill, int state, out string name1, out string name2) { name1 = string.Empty; name2 = string.Empty; for (int i = 0; i < m_SkillStateActionVec.Count; i++) { SkillStateActionEntry e = m_SkillStateActionVec[i]; if (e.skill == skill && e.state == state) { name1 = e.beHitAction ?? string.Empty; name2 = e.stayDownAction ?? string.Empty; return true; } } return false; } /// /// Returns all skill_state_action rows for a skill (anim-test / debug lookup). /// public bool TryGetSkillStateActions(int skillId, out IReadOnlyList rows) { rows = null; SkillStateActionConfig cfg = skillStateActionConfig; if (cfg?.Entries == null || cfg.Entries.Count == 0) return false; var matches = new List(); for (int i = 0; i < cfg.Entries.Count; i++) { SkillStateActionRow row = cfg.Entries[i]; if (row.skill == skillId) matches.Add(row); } if (matches.Count == 0) return false; rows = matches; return true; } private void Update() { uint dwDeltaTime = (uint)(Time.deltaTime * 1000); #if UNITY_EDITOR if (m_AttackList.Count == 0) m_AttackList = m_targets.ToList(); #endif var node = m_targets.First; while (node != null) { var next = node.Next; if (node.Value.m_bFinished) m_targets.Remove(node); else { node.Value.Tick(dwDeltaTime); } node = next; } // Tick skill GFX events (fly/hit GFX state machine) // 更新技能特效事件(飞行/命中特效状态机) SkillGfxMan.InstanceSub.Tick(dwDeltaTime); } #if UNITY_EDITOR /// /// Draw gizmos for skill projectiles in Unity Editor /// 在Unity编辑器中绘制技能弹道辅助线 /// private void OnDrawGizmos() { // Always draw gizmos (not just when selected) // 始终绘制辅助线(不仅在选择时) int gizmoCount = SkillGfxGizmoDrawer.GetGizmoCount(); // Draw test gizmo at origin to verify OnDrawGizmos is working // 在原点绘制测试辅助线以验证OnDrawGizmos是否工作 if (gizmoCount == 0 && Time.frameCount % 120 == 0) // Log every 2 seconds when no gizmos { // Draw a small test sphere at origin to verify gizmos work // 在原点绘制小测试球体以验证辅助线是否工作 Gizmos.color = Color.magenta; Gizmos.DrawWireSphere(Vector3.zero, 1.0f); } if (gizmoCount > 0) { // Only log occasionally to avoid spam // 仅偶尔记录以避免刷屏 if (Time.frameCount % 60 == 0) { //BMLogger.LogError($"[SKILL_GFX_DEBUG] OnDrawGizmos: Drawing {gizmoCount} gizmo(s)"); } } SkillGfxGizmoDrawer.DrawGizmos(); } /// /// Draw gizmos when selected (for debugging) /// 选择时绘制辅助线(用于调试) /// private void OnDrawGizmosSelected() { // Also draw when selected for extra visibility // 选择时也绘制以增加可见性 SkillGfxGizmoDrawer.DrawGizmos(); } #endif bool FileExists(string relativePath) { string fullPath = Path.Combine(Application.streamingAssetsPath, relativePath); return System.IO.File.Exists(fullPath); } public CECAttackerEvents FindAttackByAttacker(int idHost) { CECAttackerEvents result = new CECAttackerEvents(); foreach (var attack in m_targets) { if (attack.m_idHost == idHost) { result.Add(attack); } } return result; } public CECAttackEvent AddMeleeAttack(int idHost, int idTarget, int idWeapon, uint dwModifier, int nDamage, int nTimeFly = 10) { var newEvent = new CECAttackEvent( this, idHost, 0, // idCastTarget idTarget, idWeapon, 0, // idSkill 0, // nSkillLevel dwModifier, nDamage, 200, // timeToBeFired nTimeFly // timeToDoDamage ); m_targets.AddLast(newEvent); newEvent.UpdateTargetFlag(); return m_targets.Last.Value; } public A3DSkillGfxComposerMan GetSkillGfxComposerMan() { return m_pSkillGfxComposerMan; } 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 != null && !string.IsNullOrEmpty(info.action_suffix)) // 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( this, idHost, idCastTarget, idTarget, idWeapon, idSkill, nSkillLevel, dwModifier, nDamage, 200, // timeToBeFired 1000 // timeToDoDamage ); m_targets.AddLast(newEvent); #if UNITY_EDITOR if (m_AttackList.Count == 0) m_AttackList = m_targets.ToList(); #endif newEvent.UpdateTargetFlag(); return m_targets.Last.Value; } // === thêm tạm để code có thể compile === public void AddAttack(CECAttackEvent evt) { m_targets.AddLast(evt); } } public class TARGET_DATA { public int idTarget; public uint dwModifier; public int nDamage; } /// /// 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) { BMLogger.Log( $"[SKILL_GFX_FLOW] >>> CECMultiSectionSkillMan.Play called | SkillID: {nSkillID}, Section: {section}, HostID: {nHostID}, CastTargetID: {nCastTargetID}, Targets: {Targets?.Count ?? 0}, IsGoblin: {bIsGoblinSkill}"); BMLogger.Log( $"[SKILL_GFX_FLOW] >>> Searching through {m_MultiSectionSkillComposerVec.Count} multi-section skill entries"); foreach (var info in m_MultiSectionSkillComposerVec) { if (nSkillID == info.skill_id && section == info.section && info.pComposer != null) { BMLogger.Log( $"[SKILL_GFX_FLOW] >>> Found matching multi-section skill composer! | ActionSuffix: {info.action_suffix}"); BMLogger.Log($"[SKILL_GFX_FLOW] >>> Calling multi-section composer.Play"); info.pComposer.Play(nHostID, nCastTargetID, Targets, bIsGoblinSkill); BMLogger.Log($"[SKILL_GFX_FLOW] >>> Multi-section composer.Play completed"); return; } } BMLogger.LogWarning( $"[SKILL_GFX_FLOW] >>> No matching multi-section skill composer found for SkillID: {nSkillID}, Section: {section}"); } /// /// 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; } } public partial class A3DSkillGfxComposer { public uint m_dwFlyTime; // 飞行时间 / Fly time in milliseconds private bool m_bTraceTarget; GfxCluster m_FlyCluster; GFX_SKILL_PARAM m_param; private bool m_bOneHit; private bool m_bFadeOut; A3DSkillGfxMan m_pSkillGfxMan; private GameObject m_szFlyGfx; private GameObject m_szHitGrndGfx; private GameObject m_szHitGfx; private GfxMoveMode m_MoveMode; private GfxTargetMode m_TargetMode; private GfxAttackMode m_AttFlyMode; private GfxAttackMode m_AttHitMode; private bool m_bRelScl; private float m_fDefTarScl = 1.8f; private GfxCluster m_HitCluster = new GfxCluster { m_ulCount = 1, m_dwInterv = 0 }; private string m_szFlySfxPath = string.Empty; private string m_szHitSfxPath = string.Empty; private string m_szHitGrndSfxPath = string.Empty; public A3DSkillGfxComposer() { m_dwFlyTime = 0; m_FlyCluster = new GfxCluster { m_dwInterv = 0, m_ulCount = 1 }; } /// /// Load SkillStub GFX parameters onto this composer. /// 将SkillStub的GFX参数加载到此组合器上。 /// public void LoadFromSkillStub(BrewMonster.Scripts.Skills.SkillStub stub) { if (stub == null) return; m_MoveMode = stub.m_MoveMode; m_TargetMode = stub.m_TargetMode; m_AttFlyMode = stub.m_AttFlyMode; m_AttHitMode = stub.m_AttHitMode; m_dwFlyTime = stub.m_dwFlyTime; m_bTraceTarget = stub.m_bTraceTarget; m_FlyCluster = new GfxCluster { m_ulCount = stub.m_FlyClusterCount, m_dwInterv = stub.m_FlyClusterInterval }; m_bOneHit = stub.m_bOneHit; m_bFadeOut = stub.m_bFadeOut; m_bRelScl = stub.m_bRelScl; m_fDefTarScl = stub.m_fDefTarScl; m_param = BuildSkillParam(stub); m_szFlySfxPath = stub.m_szFlySfxPath ?? string.Empty; m_szHitSfxPath = stub.m_szHitSfxPath ?? string.Empty; m_szHitGrndSfxPath = stub.m_szHitGrndSfxPath ?? string.Empty; } static GFX_SKILL_PARAM BuildSkillParam(BrewMonster.Scripts.Skills.SkillStub stub) { var param = new GFX_SKILL_PARAM { m_bArea = stub.m_bArea, m_Shape = stub.m_Shape, m_vSize = new A3DVECTOR3(stub.m_vGfxSize.x, stub.m_vGfxSize.y, stub.m_vGfxSize.z) }; switch (stub.m_paramType) { case GfxSkillValType.enumGfxSkillBool: param.value = new GFX_SKILL_PARAM.ValueUnion { bVal = stub.m_paramFloatVal != 0f }; break; case GfxSkillValType.enumGfxSkillFloat: param.value = new GFX_SKILL_PARAM.ValueUnion { fVal = stub.m_paramFloatVal }; break; default: param.value = new GFX_SKILL_PARAM.ValueUnion { nVal = (int)stub.m_paramFloatVal }; break; } return param; } // GFX prefab accessors / GFX预制体访问器 public GameObject GetFlyGFX() => m_szFlyGfx; public GameObject GetHitGFX() => m_szHitGfx; public GameObject GetHitGrdGFX() => m_szHitGrndGfx; public string GetFlySfxPath() => m_szFlySfxPath; public string GetHitSfxPath() => m_szHitSfxPath; public string GetHitGrndSfxPath() => m_szHitGrndSfxPath; /// /// Load composer from file /// 从文件加载组合器 /// public string hitGfxName; public string flyGfxName; public string hitGrdGfxName; public async UniTask Load(SkillStub skillStub, string flyGFXPath, string hitGrdGFXPath, string hitGFXPath) { flyGfxName = flyGFXPath; hitGfxName = hitGFXPath; hitGrdGfxName = hitGrdGFXPath; m_szFlyGfx = string.IsNullOrEmpty(flyGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + flyGfxName); m_szHitGfx = string.IsNullOrEmpty(hitGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGfxName); m_szHitGrndGfx = string.IsNullOrEmpty(hitGrdGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGrdGfxName); if (m_szFlyGfx == null && !string.IsNullOrEmpty(flyGfxName)) { m_szFlyGfx = Resources.Load("GFX/" + "PlaceHolder"); } if (m_szHitGfx == null && !string.IsNullOrEmpty(hitGfxName)) { m_szHitGfx = Resources.Load("GFX/" + "PlaceHolder"); } if (m_szHitGrndGfx == null && !string.IsNullOrEmpty(hitGrdGfxName)) { m_szHitGrndGfx = Resources.Load("GFX/" + "PlaceHolder"); } // Read parameters from SkillStub / 从技能存根读取参数 if (skillStub != null) { m_MoveMode = skillStub.m_MoveMode; m_TargetMode = skillStub.m_TargetMode; m_AttFlyMode = skillStub.m_AttFlyMode; m_AttHitMode = skillStub.m_AttHitMode; m_dwFlyTime = skillStub.m_dwFlyTime; m_bTraceTarget = skillStub.m_bTraceTarget; // Clustering / 集群 m_FlyCluster.m_ulCount = skillStub.m_FlyClusterCount; m_FlyCluster.m_dwInterv = skillStub.m_FlyClusterInterval; m_HitCluster.m_ulCount = skillStub.m_HitClusterCount; m_HitCluster.m_dwInterv = skillStub.m_HitClusterInterval; // Behavior / 行为 m_bOneHit = skillStub.m_bOneHit; m_bFadeOut = skillStub.m_bFadeOut; m_bRelScl = skillStub.m_bRelScl; m_fDefTarScl = skillStub.m_fDefTarScl; m_param = BuildSkillParam(skillStub); m_szFlySfxPath = skillStub.m_szFlySfxPath ?? string.Empty; m_szHitSfxPath = skillStub.m_szHitSfxPath ?? string.Empty; m_szHitGrndSfxPath = skillStub.m_szHitGrndSfxPath ?? string.Empty; } else { // Set defaults if no skillStub provided / 如果没有提供技能存根则设置默认值 m_MoveMode = GfxMoveMode.enumLinearMove; m_TargetMode = GfxTargetMode.enumHostToTarget; m_AttFlyMode = GfxAttackMode.enumAttPoint; m_AttHitMode = GfxAttackMode.enumAttPoint; m_dwFlyTime = 0; m_bTraceTarget = false; m_FlyCluster.m_ulCount = 1; m_FlyCluster.m_dwInterv = 0; m_HitCluster.m_ulCount = 1; m_HitCluster.m_dwInterv = 0; m_bOneHit = true; m_bFadeOut = false; m_bRelScl = true; m_fDefTarScl = 1.8f; m_szFlySfxPath = string.Empty; m_szHitSfxPath = string.Empty; m_szHitGrndSfxPath = string.Empty; } return true; } // SpawnGFX temp hack REMOVED — GFX spawning now handled by CECSkillGfxEvent state machine // SpawnGFX临时代码已删除 — GFX生成现在由CECSkillGfxEvent状态机处理 /// /// Initialize composer /// 初始化组合器 /// public void Init(A3DSkillGfxMan pSkillGfxMan) { m_pSkillGfxMan = pSkillGfxMan; } /// /// Play skill effect /// 播放技能特效 /// public void Play(int nHostID, int nCastTargetID, List targets, bool bIsGoblinSkill = false) { bool bCastInTargets = false; #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("A3DSkillGfxComposer.Play", "composer_play_start", "B", new DebugSessionPayload { hostId = nHostID, castTargetId = nCastTargetID, targetCount = targets?.Count ?? 0, frame = Time.frameCount }); #endif #endregion // Determine GFX names from loaded prefabs / 从已加载的预制体确定GFX名称 string szFly = m_szFlyGfx != null ? flyGfxName : null; string szHit = m_szHitGfx != null ? hitGfxName : null; // TODO Phase 2: Optimization checks / 第二阶段:优化检查 // if (!CECOptimize.Instance.GetGFX().CanShowFly(nHostID)) szFly = null; // if (!CECOptimize.Instance.GetGFX().CanShowHit(nHostID)) szHit = null; // Validate targets exist before processing (filter out destroyed targets) // 在处理前验证目标是否存在(过滤已销毁的目标) if (targets != null && targets.Count > 0) { var validTargets = new List(); foreach (var tar in targets) { if (ValidateTargetExists(tar.idTarget)) { validTargets.Add(tar); } else { BMLogger.LogWarning( $"[SKILL_GFX_DEBUG] Composer.Play: Target {tar.idTarget} is destroyed, skipping"); } } if (validTargets.Count == 0) { BMLogger.LogWarning($"[SKILL_GFX_DEBUG] Composer.Play: All targets destroyed, skipping GFX"); return; } int originalCount = targets.Count; targets = validTargets; for (int i = 0; i < targets.Count; i++) { var tar = targets[i]; if (nCastTargetID == tar.idTarget) bCastInTargets = true; AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, i == 0, bIsGoblinSkill); } } #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("A3DSkillGfxComposer.Play", "composer_play_end", "B", new DebugSessionPayload { hostId = nHostID, castTargetId = nCastTargetID, targetCount = targets?.Count ?? 0, castInTargets = bCastInTargets, frame = Time.frameCount }); #endif #endregion if (nCastTargetID != 0 && !bCastInTargets) { // Validate cast target exists before adding // 在添加前验证施法目标是否存在 if (!ValidateTargetExists(nCastTargetID)) { BMLogger.LogWarning( $"[SKILL_GFX_DEBUG] Composer.Play: Cast target {nCastTargetID} is destroyed, skipping"); return; } TARGET_DATA tar = default; tar.idTarget = nCastTargetID; tar.dwModifier = 0; AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, false, bIsGoblinSkill); } } /// /// Validate that a target exists and its GameObject is not destroyed /// 验证目标存在且其GameObject未销毁 /// private bool ValidateTargetExists(int idTarget) { if (GPDataTypeHelper.ISNPCID(idTarget)) { var npc = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPCFromAll(idTarget); // Use Unity's == null check which properly handles destroyed objects // Unity destroyed objects pass != null but throw exceptions when accessed if (npc == null) return false; try { return npc.gameObject != null; } catch (System.Exception) { // Object was destroyed - return false return false; } } else if (GPDataTypeHelper.ISPLAYERID(idTarget)) { var player = EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(idTarget); if (player == null) return false; try { return player.gameObject != null; } catch (System.Exception) { // Object was destroyed - return false return false; } } return false; } public void AddOneTarget( int nCastTargetID, int nHostID, string szFly, string szHit, TARGET_DATA tar, bool bFirst, bool bIsGoblinSkill) { int _Host, _Target; float fScale; bool bReverse; switch (m_TargetMode) { case GfxTargetMode.enumTargetToHost: case GfxTargetMode.enumHostDescend: case GfxTargetMode.enumHostAscend: case GfxTargetMode.enumHostSelf: case GfxTargetMode.enumTargetLinkHost: _Host = tar.idTarget; _Target = nHostID; bReverse = true; break; default: _Host = nHostID; _Target = tar.idTarget; bReverse = false; break; } // 计算缩放 / Calculate scale /* if (m_bRelScl) fScale = SkillGfxMan.InstanceSub.GetTargetScale(_Target) / m_fDefTarScl * m_fHitGfxScale; else fScale = m_fHitGfxScale;*/ // 根据目标类型决定是否显示特效 / Determine whether to show effects based on target type if ((nCastTargetID != 0 && tar.idTarget != nCastTargetID) || (nCastTargetID == 0 && !bFirst)) { if (m_AttFlyMode == GfxAttackMode.enumAttArea) { szFly = null; } if (m_AttHitMode == GfxAttackMode.enumAttArea) { szHit = null; } } if (m_pSkillGfxMan == null) { BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneTarget: m_pSkillGfxMan is NULL - cannot add event!"); return; } #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("A3DSkillGfxComposer.AddOneTarget", "add_one_target", "B", new DebugSessionPayload { hostId = _Host, targetId = _Target, castTargetId = nCastTargetID, flyClusterCount = (int)m_FlyCluster.m_ulCount, frame = Time.frameCount }); #endif #endregion // 调用GFX管理器添加技能特效事件 / Call GFX manager to add skill GFX event m_pSkillGfxMan.AddSkillGfxEvent( this, _Host, _Target, szFly, szHit, m_dwFlyTime, m_bTraceTarget, m_MoveMode, (int)m_FlyCluster.m_ulCount, m_FlyCluster.m_dwInterv, m_param, tar.dwModifier, m_bOneHit, m_bFadeOut, bIsGoblinSkill, bReverse ); } } } [Serializable] public class CECAttackEvent { public CECAttacksMan? m_pManager; public bool m_bSignaled; public bool m_bDoFired; public bool m_bDoDamaged; public bool m_bFinished; public uint m_timeLived; public uint m_timeToBeFired; public uint m_timeToDoDamage; public int m_idHost; public int m_idCastTarget; public List m_targets = new List(); public int m_idWeapon; public int m_idSkill; public int m_nSkillLevel; public int m_nSkillSection; #if UNITY_EDITOR int debugCounter = 0; // Debug counter to track Tick calls #endif public CECAttackEvent() { } public CECAttackEvent(CECAttacksMan? pManager, int idHost, int idCastTarget, int idTarget, int idWeapon, int idSkill, int nSkillLevel, uint dwModifier, int nDamage, int nTimeToBeFired, int nTimeToDoDamage) { m_pManager = pManager; m_idHost = idHost; m_idCastTarget = idCastTarget; m_idWeapon = idWeapon; m_idSkill = idSkill; m_nSkillLevel = nSkillLevel; m_timeToBeFired = (uint)nTimeToBeFired; m_timeToDoDamage = (uint)nTimeToDoDamage; m_bFinished = false; #if UNITY_EDITOR debugCounter = UnityEngine.Random.Range(0, 1000); #endif AddTarget(idTarget, dwModifier, nDamage); } public bool Tick(uint dwDeltaTime) { m_timeLived += dwDeltaTime; if (!m_bSignaled) { if (m_timeLived > 3500) { m_bFinished = true; DoFire(); DoDamage(); } return true; } else { if (m_timeToBeFired != 0) { if (m_timeToBeFired <= dwDeltaTime) { m_timeToBeFired = 0; // Fire here DoFire(); } else m_timeToBeFired -= dwDeltaTime; } else if (m_timeToDoDamage != 0) { if (m_timeToDoDamage <= dwDeltaTime) { m_timeToDoDamage = 0; // Do damage here DoDamage(); } else m_timeToDoDamage -= dwDeltaTime; } } return true; } public void SetSkillSection(int nSection) { m_nSkillSection = nSection; } bool DoFire() { #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("CECAttackEvent.DoFire", "do_fire", "A", new DebugSessionPayload { skillId = m_idSkill, hostId = m_idHost, castTargetId = m_idCastTarget, targetCount = m_targets.Count, alreadyFired = m_bDoFired, frame = Time.frameCount }); #endif #endregion m_bDoFired = true; if (GPDataTypeHelper.ISPLAYERID(m_idHost)) { if (m_idSkill != 0) { A3DSkillGfxComposer pComposer = null; // we use skill composed gfx to present the skill effect / 使用技能特效组合器来表现技能效果 if (m_nSkillSection > 0) // 多段技能 / Multi-section skill { CECMultiSectionSkillMan pMan = m_pManager?.m_pMultiSkillGfxComposerMan; if (pMan != null) { bool isGoblin = ElementSkill.IsGoblinSkill((uint)m_idSkill); pMan.Play(m_idSkill, m_nSkillSection, m_idHost, m_idCastTarget, m_targets, isGoblin); pComposer = pMan.GetSkillGfxComposer(m_idSkill, m_nSkillSection); } else { BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: Multi-section pMan is NULL!"); } } else { bool isGoblin = ElementSkill.IsGoblinSkill((uint)m_idSkill); // Get the composer manager var composerMan = m_pManager.GetSkillGfxComposerMan(); if (composerMan != null) { if (isGoblin) composerMan.Play(m_idSkill, m_idHost, m_idCastTarget, m_targets, true); else composerMan.Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); pComposer = composerMan.GetSkillGfxComposer(m_idSkill); } else { BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: composerMan is NULL - cannot play skill GFX!"); } } if (pComposer != null && pComposer.m_dwFlyTime == 0) // 技能gfx没有飞行实际,则马上头顶冒字 / Skill has no fly time, show damage immediately { m_timeToDoDamage = 1; } else { // now we estimated a time to do damage / 现在估算伤害时间 if (m_targets.Count > 0 && GetPosByID(m_idHost, out Vector3 vecHost) && GetPosByID(m_targets[0].idTarget, out Vector3 vecTarget)) { float distance = (vecHost - vecTarget).magnitude; m_timeToDoDamage = (uint)Mathf.Max(distance / 20.0f * 1000.0f, 10.0f); } else { } } } else if (m_idWeapon != 0) { // first determine gfx used / 首先确定使用的特效 string szflyGFX = null; string szHitGFX = null; // using weapon gfx / 使用武器特效 DATA_TYPE dt = DATA_TYPE.DT_INVALID; var pData = ElementDataManProvider.GetElementDataMan() .get_data_ptr((uint)m_idWeapon, ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (dt == DATA_TYPE.DT_PROJECTILE_ESSENCE) { // 远程武器,使用弹药的效果 / Ranged weapon, use projectile effects PROJECTILE_ESSENCE pProjectile = (PROJECTILE_ESSENCE)pData; /*string fullFlyGfx = pProjectile.GetFileFireGfx(); szflyGFX = fullFlyGfx.Length > 4 ? fullFlyGfx.Substring(4) : string.Empty; // skip gfx/ string fullHitGfx = pProjectile.GetFileHitGfx(); szHitGFX = fullHitGfx.Length > 4 ? fullHitGfx.Substring(4) : string.Empty; // skip gfx/*/ szHitGFX = ByteToStringUtils.ByteArrayToCP936String(pProjectile.file_hitgfx); BMLogger.Log( $"{ByteToStringUtils.UshortArrayToUnicodeString(pProjectile.name)} Use hit effect: {szHitGFX}"); } else if (dt == DATA_TYPE.DT_WEAPON_ESSENCE) { // 近程物理攻击,使用武器的效果 / Melee physical attack, use weapon effects WEAPON_ESSENCE pWeapon = (WEAPON_ESSENCE)pData; DATA_TYPE dtSub = DATA_TYPE.DT_INVALID; var pWeaponTypeData = ElementDataManProvider.GetElementDataMan() .get_data_ptr(pWeapon.id_sub_type, ID_SPACE.ID_SPACE_ESSENCE, ref dtSub); WEAPON_SUB_TYPE pWeaponType = (WEAPON_SUB_TYPE)pWeaponTypeData; // szflyGFX = null; // string fullHitGfx = n//pWeaponType.GetFileHitGfx(); // szHitGFX = fullHitGfx.Length > 4 ? fullHitGfx.Substring(4) : string.Empty; // skip gfx/ szHitGFX = ByteToStringUtils.ByteArrayToCP936String(pWeaponType.file_hitgfx); BMLogger.Log( $"{ByteToStringUtils.UshortArrayToUnicodeString(pWeapon.name)} Use hit effect: {szHitGFX}"); } bool bHideFlyGfx = false; // TODO: !CECOptimize.Instance.GetGFX().CanShowFly(m_idHost); bool bHideHitGfx = false; // TODO: !CECOptimize.Instance.GetGFX().CanShowHit(m_idHost); int nNumTargets = m_targets.Count; for (int i = 0; i < nNumTargets; i++) { TARGET_DATA data = m_targets[i]; string pszflyGFX = ""; //szflyGFX?.ToLower(); string pszHitGFX = ""; //szHitGFX?.ToLower(); if (!string.IsNullOrEmpty(szflyGFX)) { pszflyGFX = szflyGFX.ToLower().Replace("\\", "/"); } if (!string.IsNullOrEmpty(szHitGFX)) { pszHitGFX = szHitGFX.ToLower().Replace("\\", "/"); } if ((data.dwModifier & (uint)MOD.MOD_NULLITY) != 0) pszHitGFX = "程序联入\\击中\\无效攻击击中.gfx"; if (bHideFlyGfx) pszflyGFX = null; if (bHideHitGfx) pszHitGFX = null; // TODO: Implement AddSkillGfxEvent // CECGameRun.Instance.GetWorld().GetSkillGfxMan().AddSkillGfxEvent(m_idHost, data.idTarget, // pszflyGFX, pszHitGFX, 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; } //todo: not set default like this // var fullGfx = "gfx/程序联入/击中/刀剑击中.gfx"; CECGameRun.Instance.ShowVfx(pszHitGFX, target.position, null, 1f); } } else { // without weapon / 没有武器 // 使用拳套类的击中效果 / Use fist/glove hit effects 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; // TODO: !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]; var fullGfx = ByteToStringUtils.ByteArrayToCP936String(pWeaponType.file_hitgfx); fullGfx = fullGfx.Substring(4); // szGFX = fullGfx.Length > 4 ? fullGfx.Substring(4) : string.Empty; // skip gfx/ // szGFX = pWeaponType.file_hitgfx // if ((data.dwModifier & (uint)MOD.MOD_NULLITY) != 0) // szGFX = "程序联入\\击中\\无效攻击击中.gfx"; // Program link\Hit\InvalidAttackHit.gfx // if (bHideHitGfx) szGFX = null; // TODO: Implement AddSkillGfxEvent // CECGameRun.Instance.GetWorld().GetSkillGfxMan().AddSkillGfxEvent(m_idHost, data.idTarget, null, // szGFX, m_timeToDoDamage, false, GfxMoveMode.enumLinearMove, 1, 0, null, vFlyScale, vHitScale, data.dwModifier); // Temporary implementation for testing / 临时实现用于测试 var target = EC_ManMessageMono.Instance?.GetObject(data.idTarget, 0)?.gameObject.transform; if (target == null) { BMLogger.LogError("Target is null!"); return false; } //todo: not set default like this fullGfx = "程序联入/击中/拳套击中"; CECGameRun.Instance.ShowVfx(fullGfx, target.position, null, 1f); } } } else if (GPDataTypeHelper.ISNPCID(m_idHost)) { if (m_idSkill != 0) { A3DSkillGfxComposer pComposer = null; // we use skill composed gfx to present the skill effect / 使用技能特效组合器来表现技能效果 if (m_nSkillSection > 0) // 多段技能 / Multi-section skill { CECMultiSectionSkillMan pMan = m_pManager?.m_pMultiSkillGfxComposerMan; if (pMan != null) { pMan.Play(m_idSkill, m_nSkillSection, m_idHost, m_idCastTarget, m_targets, false); pComposer = pMan.GetSkillGfxComposer(m_idSkill, m_nSkillSection); } } else { // NPC regular skill GFX / NPC常规技能特效 var composerMan = m_pManager?.GetSkillGfxComposerMan(); if (composerMan != null) { composerMan.Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); pComposer = composerMan.GetSkillGfxComposer(m_idSkill); } } if (pComposer != null && pComposer.m_dwFlyTime == 0) // 技能没有飞行时间,则直接头顶冒字 / Skill has no fly time, show damage immediately { m_timeToDoDamage = 1; } else { // now we estimated a time to do damage / 现在估算伤害时间 if (m_targets.Count > 0 && GetPosByID(m_idHost, out Vector3 vecHost) && GetPosByID(m_targets[0].idTarget, out Vector3 vecTarget)) { m_timeToDoDamage = (uint)Mathf.Max((vecHost - vecTarget).magnitude / 20.0f * 1000.0f, 10.0f); } } } else { CECNPC pNPC = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPC(m_idHost); if (pNPC == null) return true; string szflyGFX = null; string szHitGFX = null; if (pNPC.IsMonsterNPC()) { // TODO: Get monster essence data // MONSTER_ESSENCE pEssence = ((CECMonster)pNPC).GetDBEssence(); // string fullFlyGfx = pEssence.file_gfx_short; // szflyGFX = fullFlyGfx.Length > 4 ? fullFlyGfx.Substring(4) : string.Empty; // skip gfx/ // string fullHitGfx = pEssence.file_gfx_short_hit; // szHitGFX = fullHitGfx.Length > 4 ? fullHitGfx.Substring(4) : string.Empty; // skip gfx/ } else if (pNPC.IsPetNPC()) { // TODO: Get pet essence data // PET_ESSENCE pEssence = ((CECPet)pNPC).GetDBEssence(); // string fullFlyGfx = pEssence.file_gfx_short; // szflyGFX = fullFlyGfx.Length > 4 ? fullFlyGfx.Substring(4) : string.Empty; // szHitGFX = "策划联入\\怪物击中\\怪物肉搏击中.gfx"; // Planning link\Monster hit\Monster melee hit.gfx } else return false; if (string.IsNullOrEmpty(szflyGFX)) szflyGFX = null; if (string.IsNullOrEmpty(szHitGFX)) szHitGFX = null; bool bHideFlyGfx = false; // TODO: !CECOptimize.Instance.GetGFX().CanShowFly(m_idHost); bool bHideHitGfx = false; // TODO: !CECOptimize.Instance.GetGFX().CanShowHit(m_idHost); int nNumTargets = m_targets.Count; for (int i = 0; i < nNumTargets; i++) { TARGET_DATA data = m_targets[i]; string pszflyGFX = szflyGFX; string pszHitGFX = szHitGFX; if (data.nDamage <= 0) { pszHitGFX = null; } else if ((data.dwModifier & (uint)MOD.MOD_NULLITY) != 0) { pszHitGFX = "程序联入\\击中\\无效攻击击中.gfx"; // Program link\Hit\InvalidAttackHit.gfx } if (bHideFlyGfx) pszflyGFX = null; if (bHideHitGfx) pszHitGFX = null; // TODO: Implement AddSkillGfxEvent // CECGameRun.Instance.GetWorld().GetSkillGfxMan().AddSkillGfxEvent(m_idHost, data.idTarget, // pszflyGFX, pszHitGFX, m_timeToDoDamage, false, GfxMoveMode.enumLinearMove, 1, // 0, null, vFlyScale, vHitScale, data.dwModifier); } } } else { return true; } return true; } // Helper method to get position by ID / 通过ID获取位置的辅助方法 private bool GetPosByID(int nID, out Vector3 vPos) { vPos = Vector3.zero; if (GPDataTypeHelper.ISPLAYERID(nID)) { CECPlayer pPlayer = EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(nID); if (pPlayer != null) { vPos = pPlayer.GetPosVector3(); return true; } } else if (GPDataTypeHelper.ISNPCID(nID)) { CECNPC pNPC = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPCFromAll(nID); if (pNPC != null) { vPos = pNPC.GetPosVector3(); return true; } } return false; } private bool DoDamage() { m_bDoDamaged = true; m_bFinished = true; /* CECGameRun pGameRun = g_pGame-GetGameRun(); int idHostPlayer = pGameRun->GetHostPlayer()->GetCharacterID();*/ // Get host name /* ACString strHostName; CECObject* pHostObject = pGameRun->GetWorld()->GetObject(m_idHost, 0); if (pHostObject) { if (ISNPCID(m_idHost)) strHostName = ((CECNPC*)pHostObject)->GetName(); else if (ISPLAYERID(m_idHost)) strHostName = GetPlayerName((CECPlayer*)pHostObject); }*/ int nNumTargets = m_targets.Count; for (int i = 0; i < nNumTargets; i++) { TARGET_DATA data = m_targets[i]; int idTarget = data.idTarget; string strName; if (GPDataTypeHelper.ISNPCID(idTarget)) { CECNPC pNPC = null; if ((data.dwModifier & (uint)MOD.MOD_SUCCESS) != 0) pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPCFromAll(idTarget); else { pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPCFromAll(idTarget); if (!pNPC) return true; //strName = pNPC->GetNameToShow(); } if (!pNPC) return true; pNPC.Damaged(data.nDamage, data.dwModifier); } else if (GPDataTypeHelper.ISPLAYERID(idTarget)) { CECPlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idTarget); if (!pPlayer) return true; //strName = GetPlayerName(pPlayer); pPlayer.Damaged(data.nDamage, data.dwModifier, m_idSkill); } /* if (data.nDamage > 0) { if (m_idHost == idHostPlayer) { if (!strName.IsEmpty()) pGameRun->AddFixedChannelMsg(FIXMSG_DODAMAGE, GP_CHAT_DAMAGE, strName, data.nDamage); } else if (data.idTarget == idHostPlayer) { if (!strHostName.IsEmpty()) pGameRun->AddFixedChannelMsg(FIXMSG_BEDAMAGED, GP_CHAT_DAMAGE, strHostName, data.nDamage); } }*/ } return true; } public bool AddTarget(int idTarget, uint dwModifier, int nDamage) { m_targets.Add(new TARGET_DATA { idTarget = idTarget, dwModifier = dwModifier, nDamage = nDamage }); return true; } public bool UpdateTargetFlag() { int nNumTargets = m_targets.Count; for (int i = 0; i < nNumTargets; i++) { TARGET_DATA data = m_targets[i]; } return true; } } public class CECAttackerEvents { private readonly List m_list = new List(); public void Add(CECAttackEvent? evt) { if (evt != null) m_list.Add(evt); } public bool IsEmpty() => m_list.Count == 0; public int Count() => m_list.Count; public CECAttackEvent? Find(int idSkill = 0, int nSkillSection = 0) { foreach (var evt in m_list) { if (evt.m_idSkill == idSkill && evt.m_nSkillSection == nSkillSection) return evt; } return null; } public void Signal() { BMLogger.Log($"[SKILL_GFX_FLOW] === SIGNALING {m_list.Count} ATTACK EVENTS ==="); foreach (var evt in m_list) { BMLogger.Log($"[SKILL_GFX_FLOW] Signaling event | SkillID: {evt.m_idSkill}, HostID: {evt.m_idHost}"); evt.m_bSignaled = true; } m_list.Clear(); } public static implicit operator bool(CECAttackerEvents events) { return !events.IsEmpty(); } } public enum MOD { MOD_PHYSIC_ATTACK_RUNE = 0x0001, // ÎïÀí¹¥»÷ÓÅ»¯·ûÉúЧ MOD_MAGIC_ATTACK_RUNE = 0x0002, // ·¨Êõ¹¥»÷ÓÅ»¯·ûÉúЧ MOD_PHYSIC_DEFENCE_RUNE = 0x0004, // ÎïÀí·ÀÓùÓÅ»¯·ûÉúЧ MOD_MAGIC_DEFENCE_RUNE = 0x0008, // ·¨Êõ·ÀÓùÓÅ»¯·ûÉúЧ MOD_CRITICAL_STRIKE = 0x0010, // ±¬»÷ MOD_RETORT = 0x0020, // ·´Õð MOD_NULLITY = 0x0040, // ÎÞЧ¹¥»÷ MOD_IMMUNE = 0x0080, // ÃâÒßÁ˴˴ι¥»÷£¬ÓÅÏȼ¶¸ßÓÚÎÞЧ MOD_ENCHANT_FAILED = 0x0100, // enchant ʧ°Ü MOD_SUCCESS = 0x0200, // ³É¹¦ MOD_DODGE_DAMAGE = 0x0400, // É˺¦¶ãÉÁ MOD_DODGE_DEBUFF = 0x0800, // ״̬¶ãÉÁ MOD_ATTACK_AURA = 0x1000, // ¹â»·¹¥»÷ MOD_REBOUND = 0x2000, // ·´µ¯ MOD_BEAT_BACK = 0x4000, // ·´»÷ }; public 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 }; public enum GfxTargetMode { enumHostToTarget = 0, enumTargetToHost, enumTargetDescend, enumTargetAscend, enumHostDescend, enumHostAscend, enumTargetSelf, enumHostSelf, enumHostLinkTarget, enumTargetLinkHost, enumTargetModeNum }; public enum GfxAttackMode { enumAttPoint, enumAttArea, enumAttModeNum }; public struct GfxCluster { public uint m_ulCount; public uint m_dwInterv; }; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct GFX_SKILL_PARAM { public ValueUnion value; public bool m_bArea; public EmitShape m_Shape; public A3DVECTOR3 m_vSize; [StructLayout(LayoutKind.Explicit)] public struct ValueUnion { [FieldOffset(0)] public bool bVal; [FieldOffset(0)] public int nVal; [FieldOffset(0)] public float fVal; } } public enum EmitShape { enumBox = 0, enumSphere, enumCylinder, enumShapeNum }; public enum GfxSkillValType { enumGfxSkillBool = 0, enumGfxSkillInt, enumGfxSkillFloat, enumGfxSkillValTypeNum };