using BrewMonster; using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.Scripts; using CSNetwork.GPDataType; using Cysharp.Threading.Tasks; using PerfectWorld.Scripts.Managers.BrewMonster.Managers; using System; using System.Collections.Generic; using UnityEngine; namespace BrewMonster { public class CECSkillGfxEvent : A3DSkillGfxEvent { protected EC_ManPlayer m_pPlayerMan; // 玩家管理器 / Player manager protected CECNPCMan m_pNPCMan; // NPC管理器 / NPC manager private GameObject m_flyGfxInstance; // 飞行特效实例 / Fly GFX instance private GameObject m_hitGfxInstance; // 命中特效实例 / Hit GFX instance public CECSkillGfxEvent(GfxMoveMode mode) : base(mode) { m_pPlayerMan = EC_ManMessageMono.Instance?.GetECManPlayer; m_pNPCMan = EC_ManMessageMono.Instance?.CECNPCMan; } /// /// Get the skill GFX composer /// 获取技能特效组合器 /// public A3DSkillGfxComposer GetComposer() { return m_pComposer; } /// /// Get the original host ID (considering reverse mode) /// 获取原始施法者ID(考虑反向模式) /// public long GetOriginalHost() { // GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始攻击者 // GFX effects display, original attacker and target positions swapped, provides method to query original attacker return m_pMoveMethod.IsReverse() ? m_nTargetID : m_nHostID; } /// /// Get the original target ID (considering reverse mode) /// 获取原始目标ID(考虑反向模式) /// public long GetOriginalTarget() { // GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始目标 // GFX effects display, original attacker and target positions swapped, provides method to query original target return m_pMoveMethod.IsReverse() ? m_nHostID : m_nTargetID; } /// /// Get target direction and up vector /// 获取目标方向和上向量 /// public override bool GetTargetDirAndUp(out Vector3 vDir, out Vector3 vUp) { if (!m_bTargetDirAndUpExist) { vDir = Vector3.zero; vUp = Vector3.zero; return false; } vDir = m_vTargetDir; vUp = m_vTargetUp; return true; } /// /// Get target center position /// 获取目标中心位置 /// public override Vector3 GetTargetCenter() { Vector3 vTargetCenter = Vector3.zero; try { // if composer has been set // use the composer's parameter to make the hook information affect. // 如果已设置组合器,使用组合器的参数来影响挂点信息 if (GetComposer() != null) { A3DSkillGfxComposer pComposer = GetComposer(); bool success = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out vTargetCenter, pComposer.m_HitPos.HitPos, false, pComposer.m_HitPos.szHook, pComposer.m_HitPos.bRelHook, pComposer.m_HitPos.vOffset, pComposer.m_HitPos.szHanger, pComposer.m_HitPos.bChildHook); if (!success) { // Return last known position or zero if target is destroyed // 如果目标已销毁,返回最后已知位置或零 return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero; } } else { bool success = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out vTargetCenter, GfxHitPos.enumHitCenter, false, null, false, Vector3.zero, null, false); if (!success) { // Return last known position or zero if target is destroyed // 如果目标已销毁,返回最后已知位置或零 return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero; } } } catch (System.Exception ex) { BMLogger.LogError( $"[SKILL_GFX_DEBUG] GetTargetCenter: Exception accessing target {m_nTargetID} - {ex.Message}"); // Return last known position or zero // 返回最后已知位置或零 return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero; } return vTargetCenter; } /// /// Tick update /// 更新 /// public override void Tick(uint dwDeltaTime) { // Track state before base.Tick() to detect transitions / 在base.Tick()前记录状态以检测转换 GfxSkillEventState prevState = m_enumState; // Update host and target positions / 更新施法者和目标位置 if (GetComposer() != null) { SGC_POS_INFO pHostPos, pTargetPos; if (m_pMoveMethod.IsReverse()) { pHostPos = m_pComposer.m_FlyEndPos; pTargetPos = m_pComposer.m_FlyPos; } else { pHostPos = m_pComposer.m_FlyPos; pTargetPos = m_pComposer.m_FlyEndPos; } m_bHostExist = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nHostID, out m_vHostPos, pHostPos.HitPos, m_bIsGoblinSkill, pHostPos.szHook, pHostPos.bRelHook, pHostPos.vOffset, pHostPos.szHanger, pHostPos.bChildHook); m_bTargetExist = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out m_vTargetPos, pTargetPos.HitPos, false, pTargetPos.szHook, pTargetPos.bRelHook, pTargetPos.vOffset, pTargetPos.szHanger, pTargetPos.bChildHook); m_bTargetDirAndUpExist = _get_dir_and_up_by_id(m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out m_vTargetDir, out m_vTargetUp); } else { m_bHostExist = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nHostID, out m_vHostPos, m_pMoveMethod.GetHitPos(), m_bIsGoblinSkill, null, false, Vector3.zero, null, false); m_bTargetExist = get_pos_by_id( m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out m_vTargetPos, m_pMoveMethod.GetHitPos(), false, null, false, Vector3.zero, null, false); } // Log target existence issues with more detail // 记录目标存在问题的更多详细信息 base.Tick(dwDeltaTime); // Spawn fly GFX when entering Flying state / 进入飞行状态时生成飞行特效 if (prevState == GfxSkillEventState.enumWait && m_enumState == GfxSkillEventState.enumFlying) { #if UNITY_EDITOR Vector3 currentPos = m_pMoveMethod.GetPos(); //BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Registering gizmo - hostPos={m_vHostPos}, targetPos={m_vTargetPos}, currentPos={currentPos}, hostExist={m_bHostExist}, targetExist={m_bTargetExist}"); if (m_vHostPos.sqrMagnitude > 0.01f && m_vTargetPos.sqrMagnitude > 0.01f) { SkillGfxGizmoDrawer.RegisterProjectile(m_nHostID, m_nTargetID, m_vHostPos, m_vTargetPos, m_pMoveMethod.GetMode()); } #endif SpawnFlyGfx(); } // Update fly GFX transform during Flying / 飞行期间更新飞行特效变换 if (m_enumState == GfxSkillEventState.enumFlying) { UpdateFlyGfxTransform(); // Update gizmo position / 更新辅助线位置 #if UNITY_EDITOR Vector3 currentPos = m_pMoveMethod.GetPos(); // Only update if position is valid // 仅在位置有效时更新 if (currentPos.sqrMagnitude > 0.01f) { SkillGfxGizmoDrawer.UpdateProjectile(m_nHostID, m_nTargetID, currentPos, m_vTargetPos); } #endif } // Update hit GFX position when m_bTraceTarget is true (follow target) / 当m_bTraceTarget为true时更新命中特效位置(跟随目标) // This matches C++ logic: hit GFX follows target position each frame when m_bTraceTarget is true // 这与C++逻辑匹配:当m_bTraceTarget为true时,命中特效每帧跟随目标位置 if (m_enumState == GfxSkillEventState.enumHit && m_bTraceTarget) { UpdateHitGfxTransform(); // Check if hit GFX has been destroyed (3 second lifetime) or target is destroyed // 检查命中特效是否已被销毁(3秒生命周期)或目标是否已销毁 if (m_hitGfxInstance == null || (!m_bTargetExist && m_nTargetID != 0)) { m_enumState = GfxSkillEventState.enumFinished; } } // Remove gizmo when hit or finished / 命中或完成时移除辅助线 if (m_enumState == GfxSkillEventState.enumHit || m_enumState == GfxSkillEventState.enumFinished) { #if UNITY_EDITOR SkillGfxGizmoDrawer.RemoveProjectile(m_nHostID, m_nTargetID); #endif } } /// /// Handle target hit event - destroy fly GFX and spawn hit GFX /// 处理命中目标事件 - 销毁飞行特效并生成命中特效 /// protected override void HitTarget(Vector3 vTarget) { base.HitTarget(vTarget); DestroyFlyGfx(); // Only destroy fly GFX if NOT tracing target // If tracing target, fly GFX will be cleaned up when buff expires // 只有在不跟踪目标时才销毁飞行特效 // 如果跟踪目标,飞行特效将在buff过期时清理 // if (!m_bTraceTarget) // { // DestroyFlyGfx(); // } // else // { // // If fly GFX exists and m_bTraceTarget is true, add to tracking list // // 如果飞行特效存在且m_bTraceTarget为true,添加到跟踪列表 // if (m_flyGfxInstance != null) // { // SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0 // } // } SpawnHitGfx(vTarget); // TODO Phase 2: Special hit effects (rune, critical, nullity) // TODO 第二阶段:特殊命中效果(符石、暴击、无效) } // ===== GFX Instance Management ===== // GFX实例管理 /// /// Spawn fly GFX at movement position /// 在移动位置生成飞行特效 /// private void SpawnFlyGfx() { if (!m_bShowFlyGfx) return; if (m_pComposer == null) { BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: m_pComposer is NULL - cannot spawn fly GFX!"); return; } GameObject prefab = m_pComposer.GetFlyGFX(); //BMLogger.LogError("FlyGfx : " + m_pComposer.flyGfxName); if (prefab == null) { BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: Fly GFX prefab is NULL - cannot spawn!"); return; } Vector3 pos = m_pMoveMethod.GetPos(); Quaternion rot = prefab.transform.rotation; m_flyGfxInstance = GameObject.Instantiate(prefab, pos, rot); SFXManager.Instance?.PlaySkillSfxAtPointAsync(m_pComposer.GetFlySfxPath(), pos).Forget(); // If m_bTraceTarget is true, add to tracking list when spawned // 如果m_bTraceTarget为true,在生成时添加到跟踪列表 if (m_bTraceTarget) { SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0 } } /// /// Update fly GFX transform to follow movement /// 更新飞行特效变换以跟随移动 /// private void UpdateFlyGfxTransform() { if (m_flyGfxInstance == null) return; m_flyGfxInstance.transform.position = m_pMoveMethod.GetPos(); // enumOnTarget effects stay at target with prefab-authored rotation (e.g. skill 182 hail) // enumOnTarget特效在目标处保持预制体原始旋转(例如技能182冰雹) if (m_pMoveMethod.GetMode() == GfxMoveMode.enumOnTarget) return; Vector3 dir = m_pMoveMethod.GetMoveDir(); if (dir.sqrMagnitude > 1e-4f) m_flyGfxInstance.transform.rotation = Quaternion.LookRotation(dir); } /// /// Update hit GFX transform to follow target when m_bTraceTarget is true /// 当m_bTraceTarget为true时更新命中特效变换以跟随目标 /// This matches C++ logic: hit GFX position is updated each frame to follow target /// 这与C++逻辑匹配:命中特效位置每帧更新以跟随目标 /// private void UpdateHitGfxTransform() { if (m_hitGfxInstance == null) return; // Update target position first / 首先更新目标位置 if (m_bTargetExist && m_nTargetID != 0) { // Get current target position using GetTargetCenter() / 使用GetTargetCenter()获取当前目标位置 Vector3 targetPos = GetTargetCenter(); m_hitGfxInstance.transform.position = targetPos; } // If target doesn't exist, hit GFX stays at last known position // 如果目标不存在,命中特效保持在最后已知位置 } /// /// Destroy fly GFX instance /// 销毁飞行特效实例 /// private void DestroyFlyGfx() { if (m_flyGfxInstance != null) { GameObject.Destroy(m_flyGfxInstance); m_flyGfxInstance = null; } } /// /// Spawn hit GFX at target position /// 在目标位置生成命中特效 /// private void SpawnHitGfx(Vector3 vTarget) { if (!m_bShowHitGfx) return; if (m_pComposer == null) { BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: m_pComposer is NULL - cannot spawn hit GFX!"); return; } // Check if target exists - if not, use ground hit GFX instead of regular hit GFX // 检查目标是否存在 - 如果不存在,使用地面命中特效而不是常规命中特效 // This matches C++ logic: m_szHitGrndGfx is used when projectile hits ground (no target) // 这与C++逻辑匹配:当投射物击中地面(无目标)时使用m_szHitGrndGfx bool bTargetExists = m_bTargetExist && m_nTargetID != 0; GameObject prefab = bTargetExists ? m_pComposer.GetHitGFX() : m_pComposer.GetHitGrdGFX(); //BMLogger.LogError("bTargetExists : " + bTargetExists); BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName); if (prefab == null) { // Fallback: if ground hit GFX is null but target doesn't exist, try regular hit GFX // 回退:如果地面命中特效为空但目标不存在,尝试使用常规命中特效 if (!bTargetExists) { prefab = m_pComposer.GetHitGFX(); } if (prefab == null) { BMLogger.LogWarning( $"[SKILL_GFX_DEBUG] SpawnHitGfx: {(bTargetExists ? "Hit" : "Ground Hit")} GFX prefab is NULL - cannot spawn!"); return; } } /* Quaternion rot = Quaternion.identity; if (m_bHostExist) { Vector3 dir = vTarget - m_vHostPos; dir.y = 0; if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir); }*/ m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, prefab.transform.rotation); string hitSfxPath; if (bTargetExists) hitSfxPath = m_pComposer.GetHitSfxPath(); else { hitSfxPath = m_pComposer.GetHitGrndSfxPath(); if (string.IsNullOrEmpty(hitSfxPath)) hitSfxPath = m_pComposer.GetHitSfxPath(); } SFXManager.Instance?.PlaySkillSfxAtPointAsync(hitSfxPath, vTarget).Forget(); // If m_bTraceTarget is true, add to tracking list (don't auto-destroy) // 如果m_bTraceTarget为true,添加到跟踪列表(不自动销毁) if (m_bTraceTarget) { SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_hitGfxInstance, 0); // Skill ID not available, use 0 //BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: Added hit GFX to trace target list (m_bTraceTarget=true)"); } else { // Destroy hit GFX after 5 seconds (unless m_bTraceTarget is true, then it follows target until destroyed) // 5秒后销毁命中特效(除非m_bTraceTarget为true,否则它会跟随目标直到被销毁) //HIT_GFX_MAX_TIMESPAN 5000 //BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: GameObject.Destroy(m_hitGfxInstance, 5.0f);"); GameObject.Destroy(m_hitGfxInstance, 5.0f); } } /// /// Clean up GFX instances on Resume (return to pool) /// 在Resume时清理GFX实例(返回池) /// public new void Resume() { // Don't destroy GFX if it's in trace target list // It will be cleaned up when buff expires // 如果GFX在跟踪目标列表中,不要销毁它 // 它将在buff过期时清理 if (m_flyGfxInstance != null) { if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_flyGfxInstance)) { DestroyFlyGfx(); } } if (m_hitGfxInstance != null) { if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_hitGfxInstance)) { // Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here // 命中特效由Unity的Destroy计时器自动销毁,不在此处置null m_hitGfxInstance = null; } } base.Resume(); } /// /// Load hit GFX with modifier checks /// 加载命中特效(带修饰符检查) /// /*public A3DGFXEx LoadHitGfx(A3DDevice pDev, string szPath) { if ((m_dwModifier & CECAttackEvent.MOD_NULLITY) != 0) { // 程序联入\\击中\\无效攻击击中.gfx // Program integration\\Hit\\Invalid attack hit.gfx szPath = "程序联入\\击中\\无效攻击击中.gfx"; } // TODO: return AfxGetGFXExMan()->LoadGfx(pDev, szPath); return null; }*/ /// /// Set hit GFX /// 设置命中特效 /// /*public void SetHitGfx(A3DGFXEx pHitGfx) { // if (m_dwModifier & CECAttackEvent::MOD_CRITICAL_STRIKE) // pHitGfx->SetActualScale(pHitGfx->GetScale() * 2.0f); m_pHitGfx = pHitGfx; }*/ private static bool get_pos_by_id( EC_ManPlayer pPlayerMan, CECNPCMan pNPCMan, int nID, out Vector3 vPos, GfxHitPos HitPos, bool bIsGoblinSkill = false, string szHook = null, bool bRelHook = false, Vector3 pOffset = default, string szHanger = null, bool bChildHook = false) { vPos = Vector3.zero; if (GPDataTypeHelper.ISPLAYERID(nID)) { CECPlayer pPlayer = pPlayerMan?.GetPlayer(nID); // Check if player exists AND GameObject is not destroyed (Unity's "fake null" handling) if (pPlayer != null && pPlayer.gameObject != null) { { if (bIsGoblinSkill) { // Get goblin model from player // 从玩家获取小精灵模型 // Note: GetGoblin() method may need to be implemented in CECPlayer // 注意:GetGoblin()方法可能需要在CECPlayer中实现 // For now, we'll try to get it via a common pattern // 目前,我们将尝试通过通用模式获取它 // Try to find goblin model - this is a placeholder until GetGoblin() is implemented // 尝试查找小精灵模型 - 这是占位符,直到实现GetGoblin() // TODO: Implement GetGoblin() in CECPlayer when goblin system is available // TODO: 当小精灵系统可用时,在CECPlayer中实现GetGoblin() // For Phase 3, we'll search for a child model named "goblin" or similar // 对于第3阶段,我们将搜索名为"goblin"或类似的子模型 CECModel pModel = pPlayer.GetPlayerCECModel(); if (pModel != null) { // Try common goblin hanger names // 尝试常见的小精灵挂载者名称 CECModel goblinModel = pModel.GetChildModel("goblin") ?? pModel.GetChildModel("pet") ?? pModel.GetChildModel("_goblin"); if (goblinModel != null) { // Use hook if specified, otherwise use model center // 如果指定了挂点则使用挂点,否则使用模型中心 if (!string.IsNullOrEmpty(szHook)) { Transform pHook = goblinModel.GetHook(szHook, true); if (pHook != null) { Transform modelTransform = goblinModel.transform; vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform); #if UNITY_EDITOR BMLogger.Log( $"[HOOK_DEBUG] Found goblin hook '{szHook}' for player ID {nID}, position={vPos}"); #endif return true; } } // Fallback to goblin position // 回退到小精灵位置 if (goblinModel.transform != null) { vPos = goblinModel.transform.position; vPos.y += 0.5f; #if UNITY_EDITOR BMLogger.Log( $"[HOOK_DEBUG] Using goblin center position for player ID {nID}, position={vPos}"); #endif return true; } } } #if UNITY_EDITOR BMLogger.LogWarning($"[HOOK_DEBUG] Goblin model not found for player ID {nID}"); #endif return false; } else { // currently hook does not affect the Goblin Skill // 目前挂点不影响小精灵技能 while (true) { if (string.IsNullOrEmpty(szHook)) break; // Get player model and hook position // 获取玩家模型和挂点位置 CECModel pModel = pPlayer.GetPlayerCECModel(); if (pModel == null) { #if UNITY_EDITOR BMLogger.LogWarning( $"[HOOK_DEBUG] Player model not found for ID {nID}, hook '{szHook}'"); #endif break; } // Handle child model (hanger) if specified // 如果指定了子模型(挂载者),则处理 if (!string.IsNullOrEmpty(szHanger) && bChildHook) { pModel = pModel.GetChildModel(szHanger); if (pModel == null) { #if UNITY_EDITOR BMLogger.LogWarning( $"[HOOK_DEBUG] Child model '{szHanger}' not found for player ID {nID}, hook '{szHook}'"); #endif break; } } // Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true)) // 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true)) Transform pHook = pModel.GetHook(szHook, false); if (pHook == null) { #if UNITY_EDITOR BMLogger.LogWarning( $"[HOOK_DEBUG] Hook '{szHook}' not found for player ID {nID}, falling back to center position"); #endif break; } // Get model transform for absolute offset calculation // 获取模型变换用于绝对偏移计算 Transform modelTransform = pModel.transform; // Calculate position based on relative/absolute offset // 根据相对/绝对偏移计算位置 vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform); #if UNITY_EDITOR BMLogger.Log( $"[HOOK_DEBUG] Found hook '{szHook}' for player ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}"); #endif return true; } if (HitPos == GfxHitPos.enumHitBottom) { vPos = pPlayer.GetPosVector3(); } else { // TODO: Get player AABB // const A3DAABB& aabb = pPlayer->GetPlayerAABB(); // vPos = aabb.Center; // vPos.y += aabb.Extents.y * .5f; vPos = pPlayer.GetPosVector3(); vPos.y += 1.0f; // Default height offset / 默认高度偏移 } } return true; } } } else if (GPDataTypeHelper.ISNPCID(nID)) { CECNPC pNPC = pNPCMan?.GetNPCFromAll(nID); // Check if NPC exists AND GameObject is not destroyed (Unity's "fake null" handling) if (pNPC != null && pNPC.gameObject != null) { { while (true) { if (string.IsNullOrEmpty(szHook)) break; // Get NPC model // 获取NPC模型 CECModel pModel = pNPC.GetModel(); if (pModel == null) { #if UNITY_EDITOR BMLogger.LogWarning($"[HOOK_DEBUG] NPC model not found for ID {nID}, hook '{szHook}'"); #endif break; } // Handle child model (hanger) if specified // 如果指定了子模型(挂载者),则处理 if (!string.IsNullOrEmpty(szHanger) && bChildHook) { pModel = pModel.GetChildModel(szHanger); if (pModel == null) { #if UNITY_EDITOR BMLogger.LogWarning( $"[HOOK_DEBUG] Child model '{szHanger}' not found for NPC ID {nID}, hook '{szHook}'"); #endif break; } } // Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true)) // 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true)) Transform pHook = pModel.GetHook(szHook, false); if (pHook == null) { #if UNITY_EDITOR BMLogger.LogWarning( $"[HOOK_DEBUG] Hook '{szHook}' not found for NPC ID {nID}, falling back to center position"); #endif break; } // Get model transform for absolute offset calculation // 获取模型变换用于绝对偏移计算 Transform modelTransform = pModel.transform; // Calculate position based on relative/absolute offset // 根据相对/绝对偏移计算位置 vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform); #if UNITY_EDITOR BMLogger.Log( $"[HOOK_DEBUG] Found hook '{szHook}' for NPC ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}"); #endif return true; } if (HitPos == GfxHitPos.enumHitBottom) { vPos = pNPC.GetPosVector3(); } else { // TODO: Get NPC AABB // const A3DAABB& aabb = pNPC->GetPickAABB(); // vPos = aabb.Center; // vPos.y += aabb.Extents.y * .5f; vPos = pNPC.GetPosVector3(); vPos.y += 1.0f; // Default height offset / 默认高度偏移 } return true; } } } return false; } /// /// Get direction and up vector by ID /// 根据ID获取方向和上向量 /// private static bool _get_dir_and_up_by_id( EC_ManPlayer pPlayerMan, CECNPCMan pNPCMan, int nId, out Vector3 vDir, out Vector3 vUp) { vDir = Vector3.zero; vUp = Vector3.zero; if (GPDataTypeHelper.ISPLAYERID(nId)) { CECPlayer pPlayer = pPlayerMan?.GetPlayer(nId); // Check if player exists AND GameObject is not destroyed (Unity's "fake null" handling) if (pPlayer != null && pPlayer.gameObject != null) { // TODO: Get player direction and up // vDir = pPlayer->GetDir(); // vUp = pPlayer->GetUp(); vDir = pPlayer.transform.forward; vUp = pPlayer.transform.up; return true; } } else if (GPDataTypeHelper.ISNPCID(nId)) { CECNPC pNPC = pNPCMan?.GetNPCFromAll(nId); // Check if NPC exists AND GameObject is not destroyed (Unity's "fake null" handling) if (pNPC != null && pNPC.gameObject != null) { // TODO: Get NPC direction and up // vDir = pNPC->GetDir(); // vUp = pNPC->GetUp(); vDir = pNPC.transform.forward; vUp = pNPC.transform.up; return true; } } return false; } public static float _get_scale_by_id( EC_ManPlayer pPlayerMan, CECNPCMan pNPCMan, int nID) { if (GPDataTypeHelper.ISPLAYERID(nID)) { CECPlayer pPlayer = pPlayerMan?.GetPlayer(nID); if (pPlayer != null) { // TODO: Get player AABB // const A3DAABB& aabb = pPlayer->GetPlayerAABB(); // return aabb.Maxs.y - aabb.Mins.y; return 1.0f; } } else if (GPDataTypeHelper.ISNPCID(nID)) { CECNPC pNPC = pNPCMan?.GetNPCFromAll(nID); if (pNPC != null) { // TODO: Get NPC AABB // const A3DAABB& aabb = pNPC->GetPickAABB(); // return aabb.Maxs.y - aabb.Mins.y; return 1.0f; } } return 1.0f; } /// /// Get ECModel GFX property by ID /// 根据ID获取ECModel GFX属性 /// public static bool _get_ecm_property_by_id(long nID, out ECMODEL_GFX_PROPERTY pProperty) { pProperty = new ECMODEL_GFX_PROPERTY(); int _32bitID = (int)nID; if (GPDataTypeHelper.ISPLAYERID(_32bitID)) { // TODO: Get player model properties /*CECPlayer* pPlayer = g_pGame->GetGameRun()->GetWorld()->GetPlayerMan()->GetPlayer(_32bitID); if (!pPlayer) return false; CECModel* pModel = pPlayer->GetPlayerModel(); if (!pModel) return false; pProperty->bGfxUseLod = pModel->IsGfxUseLOD(); pProperty->bGfxDisableCamShake = pModel->GetDisableCamShake(); pProperty->bHostECMCreatedByGfx = pModel->IsCreatedByGfx();*/ return true; } else if (GPDataTypeHelper.ISNPCID(_32bitID)) { // TODO: Get NPC properties /*CECNPC* pNPC = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetNPC(_32bitID); if (!pNPC || !pNPC->HasModel()) return false; pNPC->GetEcmProperty(&pProperty);*/ return true; } return false; } } /// /// Skill GFX Manager /// 技能特效管理器 /// public class SkillGfxMan : A3DSkillGfxMan { public static SkillGfxMan _instancesub; public static SkillGfxMan InstanceSub { get { if (_instancesub == null) { _instancesub = new SkillGfxMan(EC_Game.GetGameRun()); } return _instancesub; } set { _instancesub = value; } } private const int DEFAULT_EVENT_BUF_SIZE = 10; // 默认事件缓冲区大小 / Default event buffer size public LinkedList m_EventLst; // 活动事件列表 / Active event list protected LinkedList[] m_FreeLst; protected EC_ManPlayer m_pPlayerMan; protected CECNPCMan m_pNPCMan; // Track GFX instances that have m_bTraceTarget = true // These are typically buff-related trail effects that persist until buff expires // 跟踪具有m_bTraceTarget = true的GFX实例 // 这些通常是持续到buff结束的buff相关轨迹效果 private List m_TraceTargetGfxList = new List(); private Dictionary m_TraceTargetGfxSkillMap = new Dictionary(); public SkillGfxMan(CECGameRun pGameRun) { m_EventLst = new LinkedList(); m_FreeLst = new LinkedList[(int)GfxMoveMode.enumMoveModeNum]; // Initialize free lists for each move mode // 为每种移动模式初始化空闲列表 for (int i = 0; i < (int)GfxMoveMode.enumMoveModeNum; i++) { m_FreeLst[i] = new LinkedList(); for (int j = 0; j < DEFAULT_EVENT_BUF_SIZE; j++) { CECSkillGfxEvent pEvent = new CECSkillGfxEvent((GfxMoveMode)i); m_FreeLst[i].AddLast(pEvent); } } // TODO: Get managers from game run // m_pPlayerMan = pGameRun?.GetWorld()?.GetPlayerMan(); // m_pNPCMan = pGameRun?.GetWorld()?.GetNPCMan(); m_pPlayerMan = EC_ManMessageMono.Instance?.GetECManPlayer; m_pNPCMan = EC_ManMessageMono.Instance?.CECNPCMan; } public override void Dispose() { base.Dispose(); InstanceSub = null; } public A3DSkillGfxEvent GetEmptyEvent(GfxMoveMode mode) { int modeIndex = (int)mode; if (m_FreeLst[modeIndex].Count == 0) { return new CECSkillGfxEvent(mode); } else { CECSkillGfxEvent pEvent = m_FreeLst[modeIndex].Last.Value; m_FreeLst[modeIndex].RemoveLast(); pEvent.Resume(); return pEvent; } } /// /// Push event to active list /// 将事件推送到活动列表 /// public void PushEvent(A3DSkillGfxEvent pEvent) { if (pEvent is CECSkillGfxEvent cecEvent) { m_EventLst.AddLast(cecEvent); } } /// /// Get target scale by ID /// 根据ID获取目标缩放 /// public float GetTargetScale(long nTargetId) { return CECSkillGfxEvent._get_scale_by_id(m_pPlayerMan, m_pNPCMan, (int)nTargetId); } /// /// Get ECModel GFX property by ID /// 根据ID获取ECModel GFX属性 /// public override bool GetPropertyById(long nId, ref ECMODEL_GFX_PROPERTY pProperty) { return CECSkillGfxEvent._get_ecm_property_by_id(nId, out pProperty); } /// /// Release all events /// 释放所有事件 /// public void Release() { // Release active events // 释放活动事件 m_EventLst.Clear(); // Release free lists // 释放空闲列表 for (int i = 0; i < (int)GfxMoveMode.enumMoveModeNum; i++) { m_FreeLst[i].Clear(); } // Clean up trace target GFX // 清理跟踪目标GFX RemoveAllTraceTargetGfx(); } /// /// Add a GFX instance to trace target tracking list /// 将GFX实例添加到跟踪目标跟踪列表 /// public void AddTraceTargetGfx(GameObject gfxInstance, int skillId) { if (gfxInstance == null) return; if (!m_TraceTargetGfxList.Contains(gfxInstance)) { m_TraceTargetGfxList.Add(gfxInstance); m_TraceTargetGfxSkillMap[gfxInstance] = skillId; } } /// /// Remove all trace target GFX (called when buff states update) /// 移除所有跟踪目标GFX(在buff状态更新时调用) /// public void RemoveAllTraceTargetGfx() { foreach (GameObject gfx in m_TraceTargetGfxList) { if (gfx != null) { GameObject.Destroy(gfx, 1); } } m_TraceTargetGfxList.Clear(); m_TraceTargetGfxSkillMap.Clear(); } /// /// Remove trace target GFX for specific skill /// 移除特定技能的跟踪目标GFX /// public void RemoveTraceTargetGfxForSkill(int skillId) { List toRemove = new List(); foreach (var kvp in m_TraceTargetGfxSkillMap) { if (kvp.Value == skillId) { toRemove.Add(kvp.Key); } } foreach (GameObject gfx in toRemove) { if (gfx != null) { GameObject.Destroy(gfx); } m_TraceTargetGfxList.Remove(gfx); m_TraceTargetGfxSkillMap.Remove(gfx); } } /// /// Check if a GFX instance is tracked as trace target GFX /// 检查GFX实例是否被跟踪为跟踪目标GFX /// public bool IsTraceTargetGfx(GameObject gfx) { return m_TraceTargetGfxList.Contains(gfx); } /// /// Clean up null references (GFX destroyed elsewhere) /// 清理空引用(在其他地方销毁的GFX) /// public void CleanupTraceTargetGfx() { m_TraceTargetGfxList.RemoveAll(gfx => gfx == null); List nullKeys = new List(); foreach (var kvp in m_TraceTargetGfxSkillMap) { if (kvp.Key == null) { nullKeys.Add(kvp.Key); } } foreach (var key in nullKeys) { m_TraceTargetGfxSkillMap.Remove(key); } } /// /// Tick update all events /// 更新所有事件 /// public bool Tick(uint dwDeltaTime) { var node = m_EventLst.First; while (node != null) { var nextNode = node.Next; CECSkillGfxEvent pEvent = node.Value; if (pEvent.IsFinished()) { m_EventLst.Remove(node); pEvent.Resume(); m_FreeLst[(int)pEvent.GetMode()].AddLast(pEvent); } else { pEvent.Tick(dwDeltaTime); } node = nextNode; } return true; } /// /// Render all events /// 渲染所有事件 /// public bool Render(CECViewport pViewport) { /*var node = m_EventLst.First; while (node != null) { node.Value.Render(); node = node.Next; }*/ return true; } } /// /// Manager wrapper for SkillGfxMan /// SkillGfxMan的管理器包装类 /// public class CECSkillGfxMan : CECManager { protected SkillGfxMan m_GfxMan; // 技能特效管理器 / Skill GFX manager public CECSkillGfxMan(CECGameRun pGameRun) : base(pGameRun) { m_GfxMan = new SkillGfxMan(pGameRun); // TODO: m_GfxMan.Init(g_pGame->GetA3DDevice()); } /// /// Get the internal SkillGfxMan pointer /// 获取内部SkillGfxMan指针 /// public A3DSkillGfxMan GetPtr() { return m_GfxMan; } /// /// Release resources /// 释放资源 /// public virtual void Release() { m_GfxMan.Release(); } /// /// Tick update /// 更新 /// public virtual bool Tick(uint dwDeltaTime) { return m_GfxMan.Tick(dwDeltaTime); } /// /// Render /// 渲染 /// public bool Render(CECViewport pViewport) { return m_GfxMan.Render(pViewport); } /// /// Add skill GFX event /// 添加技能特效事件 /// /// /// Convenience overload for weapon/melee attacks (no composer). /// Scale is not needed — Unity Particle Systems handle their own scale. /// 武器/近战攻击的便捷重载(无组合器)。不需要缩放 — Unity粒子系统自己处理缩放。 /// public bool AddSkillGfxEvent( int nHostID, int nTargetID, string szFlyGfx, string szHitGfx, uint dwFlyTimeSpan, bool bTraceTarget = true, GfxMoveMode FlyMode = GfxMoveMode.enumLinearMove, int nFlyGfxCount = 1, uint dwInterval = 0, GFX_SKILL_PARAM? param = null, uint dwModifier = 0) { return m_GfxMan.AddSkillGfxEvent( null, nHostID, nTargetID, szFlyGfx, szHitGfx, dwFlyTimeSpan, bTraceTarget, FlyMode, nFlyGfxCount, dwInterval, param ?? default, dwModifier, false, // bOnlyOneHit false, // bFadeOut false, // bIsGoblinSkill false // bReverse ); } } /// /// Viewport placeholder for CECViewport /// CECViewport的占位符类 /// public class CECViewport { // TODO: Implement viewport functionality // 待实现:视口功能 } /// /// Optimize settings placeholder /// 优化设置占位符 /// public class CECOptimize { private static CECOptimize _instance; public static CECOptimize Instance => _instance ?? (_instance = new CECOptimize()); private GFXOptimize _gfxOptimize = new GFXOptimize(); public GFXOptimize GetGFX() { return _gfxOptimize; } public class GFXOptimize { public bool CanShowHit(long hostId) { // TODO: Implement optimization check // 待实现:优化检查 return true; } public bool CanShowFly(long hostId) { // TODO: Implement optimization check // 待实现:优化检查 return true; } } } /// /// Skill GFX Composer position info /// 技能特效组合器位置信息 /// public class SGC_POS_INFO { public GfxHitPos HitPos; // 命中位置类型 / Hit position type public string szHook; // 挂点名称 / Hook name public bool bRelHook; // 是否相对挂点 / Is relative to hook public Vector3 vOffset; // 偏移量 / Offset public string szHanger; // 挂载者名称 / Hanger name public bool bChildHook; // 是否子挂点 / Is child hook public SGC_POS_INFO() { HitPos = GfxHitPos.enumHitCenter; szHook = null; bRelHook = false; vOffset = Vector3.zero; szHanger = null; bChildHook = false; } } /// /// Extended A3DSkillGfxComposer with position info /// 扩展的A3DSkillGfxComposer(带位置信息) /// public partial class A3DSkillGfxComposer { public SGC_POS_INFO m_FlyPos = new SGC_POS_INFO(); // 飞行起始位置信息 / Fly start position info public SGC_POS_INFO m_FlyEndPos = new SGC_POS_INFO(); // 飞行结束位置信息 / Fly end position info public SGC_POS_INFO m_HitPos = new SGC_POS_INFO(); // 命中位置信息 / Hit position info } }