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 System.Text; using ModelRenderer.Scripts.Common; using UnityEngine; namespace BrewMonster { public class CECAttacksMan : MonoSingleton { private readonly LinkedList m_targets = new LinkedList(); public CECMultiSectionSkillMan m_pMultiSkillGfxComposerMan; protected A3DSkillGfxComposerMan m_pSkillGfxComposerMan; #if UNITY_EDITOR public List m_AttackList = new List(); #endif private void Start() { SetupAttacksMan(); uint idSkill = 0; while (true) { idSkill = ElementSkill.NextSkill(idSkill); if (idSkill == 0) break; string pszSGCFile = ElementSkill.GetEffect(idSkill); while (pszSGCFile.StartsWith("\\")) pszSGCFile = pszSGCFile.Substring(1); string szSGCFile; if (string.IsNullOrEmpty(pszSGCFile)) szSGCFile = "nosuchthing"; else szSGCFile = $"{pszSGCFile}"; if (!m_pSkillGfxComposerMan.LoadOneComposer((int)idSkill, szSGCFile)) { // a_LogOutput(1, "CECAttacksMan::CECAttacksMan(), failed to load skill [%d]'s gfx composer [%s]", idSkill, szSGCFile); } } //TODO: convert this part /* char szMultiSectionFile[MAX_PATH] = { 0 }; strcpy(szMultiSectionFile, "configs\\multi_section_skill.txt"); m_pMultiSkillGfxComposerMan = new CECMultiSectionSkillMan(); if (!m_pMultiSkillGfxComposerMan || !m_pMultiSkillGfxComposerMan->LoadConfig(szMultiSectionFile)) { a_LogOutput(1, "CECAttacksMan::CECAttacksMan(), failed to load multi skill sgc config file [%s]", szMultiSectionFile); } strcpy(szMultiSectionFile, "configs\\skill_state_action.txt"); if (!LoadSkillStateActionConfig(szMultiSectionFile)) a_LogOutput(1, "CECAttacksMan::CECAttacksMan(), failed to load multi skill action config file [%s]", szMultiSectionFile);*/ } public void SetupAttacksMan() { m_pSkillGfxComposerMan = new A3DSkillGfxComposerMan(); uint idSkill = 0; } private void Update() { #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; //BMLogger.LogError("HoangDev: Update CECAttackEvent node.Value.m_bFinished: " + node.Value.m_bFinished); if (node.Value.m_bFinished) m_targets.Remove(node); else node.Value.Tick((uint)(Time.deltaTime * 1000)); node = next; } } 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(); //BMLogger.LogError("HoangDev: FindAttackByAttacker idHost: " + m_AttackLinkedList.Count); 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 GFX; private GfxMoveMode m_MoveMode; private GfxTargetMode m_TargetMode; private GfxAttackMode m_AttFlyMode; private GfxAttackMode m_AttHitMode; private bool m_bRelScl; public A3DSkillGfxComposer() { m_dwFlyTime = 0; m_FlyCluster = new GfxCluster { m_dwInterv = 0, m_ulCount = 1 }; } /// /// Load composer from file /// 从文件加载组合器 /// public bool Load(string sgcFile) { string name = sgcFile.Replace(".sgc", ""); GFX = Resources.Load("GFX/"+name); //BMLogger.LogError("HoangDev: Load A3DSkillGfxComposer GFX name: " + name); if (GFX == null) { GFX = Resources.Load("GFX/" + "PlaceHolder"); } return true; } public void SpawnGFX(long IDTarget) { var obj = EC_ManMessageMono.Instance.GetObject(IDTarget, 0); if (obj != null && GFX != null) { GameObject.Instantiate(GFX, obj.transform.position, GFX.gameObject.transform.rotation, obj.transform); } } /// /// 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; /* char szFly = m_szFlyGfx[0] ? m_szFlyGfx : NULL; char szHit = m_szHitGfx[0] ? m_szHitGfx : NULL;*/ /*# ifndef _SKILLGFXCOMPOSER if (!CECOptimize::Instance().GetGFX().CanShowFly(nHostID)) { a_LogOutput(1, "[SKILL_GFX_FLOW] ====> Fly GFX hidden by optimization"); szFly = NULL; } if (!CECOptimize::Instance().GetGFX().CanShowHit(nHostID)) { a_LogOutput(1, "[SKILL_GFX_FLOW] ====> Hit GFX hidden by optimization"); szHit = NULL; } #endif*/ // Log target details if (targets != null && targets.Count > 0) { 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); } } else { } if (nCastTargetID != 0 && !bCastInTargets) { TARGET_DATA tar = default; tar.idTarget = nCastTargetID; tar.dwModifier = 0; AddOneTarget(nCastTargetID, nHostID, ""/*szFly*/, ""/*szHit*/, tar, false, bIsGoblinSkill); } } 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; // 根据目标模式决定Host和Target的映射 / Determine Host and Target mapping based on target mode 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 = m_pSkillGfxMan.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; } } // 调用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, 0/*m_fFlyGfxScale*/, 0/*fScale*/, 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; 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; 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() { float vFlyScale = 1.0f; float vHitScale = 1.0f; 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 { } } 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 { } } 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"; // 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); 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 { // TODO: Implement SkillGfxComposerMan // m_pManager.GetSkillGfxComposerMan().Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); // pComposer = m_pManager.GetSkillGfxComposerMan().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); //BMLogger.LogError("HoangDev: CECPlayer pPlayer = " + pPlayer ); 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() { // update all targets' bAboutToDie flag int nNumTargets = m_targets.Count; for (int i = 0; i < nNumTargets; i++) { TARGET_DATA data = m_targets[i]; /* if( data.dwModifier & MOD_DEADLYSTRIKE ) { int idTarget = data.idTarget; if (ISNPCID(idTarget)) { CECNPC* pNPC = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetNPC(idTarget); if (!pNPC) return true; pNPC->SetAboutToDie(true); } else if (ISPLAYERID(idTarget)) { CECPlayer* pPlayer = g_pGame->GetGameRun()->GetWorld()->GetPlayerMan()->GetPlayer(idTarget); if (!pPlayer) return true; pPlayer->SetAboutToDie(true); } }*/ } 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 };