Files
test/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs
T
2025-11-14 19:03:42 +07:00

966 lines
35 KiB
C#

using Animancer;
using BrewMonster;
using BrewMonster.Assets.PerfectWorld.Scripts.Common;
using BrewMonster.Managers;
using BrewMonster.Scripts.Skills;
using CSNetwork.GPDataType;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using static UnityEditor.AddressableAssets.Build.Layout.BuildLayout;
using File = System.IO.File;
public class CECAttacksMan : MonoSingleton<CECAttacksMan>
{
private readonly LinkedList<CECAttackEvent> m_AttackLinkedList = new LinkedList<CECAttackEvent>();
public CECMultiSectionSkillMan m_pMultiSkillGfxComposerMan;
protected A3DSkillGfxComposerMan m_pSkillGfxComposerMan;
#if UNITY_EDITOR
public List<CECAttackEvent> m_AttackList = new List<CECAttackEvent>();
#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 = "gfx\\sgc\\nosuchthing.sgc";
else
szSGCFile = $"gfx/sgc/{pszSGCFile}";
if (FileExists(szSGCFile))
{
if (!m_pSkillGfxComposerMan.LoadOneComposer((int)idSkill, szSGCFile))
{
// a_LogOutput(1, "CECAttacksMan::CECAttacksMan(), failed to load skill [%d]'s gfx composer [%s]", idSkill, szSGCFile);
}
}
}
/*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
m_AttackList = m_AttackLinkedList.ToList();
#endif
var node = m_AttackLinkedList.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_AttackLinkedList.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_AttackLinkedList)
{
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_AttackLinkedList.AddLast(newEvent);
newEvent.UpdateTargetFlag();
return m_AttackLinkedList.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_AttackLinkedList.AddLast(newEvent);
newEvent.UpdateTargetFlag();
return m_AttackLinkedList.Last.Value;
}
// === thêm tạm để code có thể compile ===
public void AddAttack(CECAttackEvent evt)
{
m_AttackLinkedList.AddLast(evt);
}
}
public class TARGET_DATA
{
public int idTarget;
public uint dwModifier;
public int nDamage;
}
/// <summary>
/// Manager for multi-section skills
/// 多段技能管理器
/// </summary>
public class CECMultiSectionSkillMan
{
/// <summary>
/// Section information for multi-section skills
/// 多段技能的段信息
/// </summary>
[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<string, A3DSkillGfxComposer> m_SgcName2ComposerMap = new Dictionary<string, A3DSkillGfxComposer>();
// 多段技能组合器列表 / Multi-section skill composer list
private readonly List<SectionInfo> m_MultiSectionSkillComposerVec = new List<SectionInfo>();
public CECMultiSectionSkillMan()
{
}
~CECMultiSectionSkillMan()
{
Release();
}
/// <summary>
/// Release resources
/// 释放资源
/// </summary>
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();
}
/// <summary>
/// Load configuration from file
/// 从文件加载配置
/// </summary>
/// <param name="szFile">Configuration file path / 配置文件路径</param>
/// <returns>Success / 是否成功</returns>
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;
}
/// <summary>
/// Play multi-section skill effect
/// 播放多段技能特效
/// </summary>
/// <param name="nSkillID">Skill ID / 技能ID</param>
/// <param name="section">Skill section / 技能段数</param>
/// <param name="nHostID">Host character ID / 施法者ID</param>
/// <param name="nCastTargetID">Cast target ID / 施法目标ID</param>
/// <param name="Targets">Target data list / 目标数据列表</param>
/// <param name="bIsGoblinSkill">Is goblin skill / 是否为精灵技能</param>
public void Play(int nSkillID, int section, int nHostID, int nCastTargetID,
List<TARGET_DATA> Targets, bool bIsGoblinSkill = false)
{
foreach (var info in m_MultiSectionSkillComposerVec)
{
if (nSkillID == info.skill_id && section == info.section && info.pComposer != null)
{
// TODO: Implement A3DSkillGfxComposer.Play method
// info.pComposer.Play(nHostID, nCastTargetID, Targets, bIsGoblinSkill);
BMLogger.Log($"CECMultiSectionSkillMan.Play: Skill {nSkillID}, Section {section}");
return;
}
}
}
/// <summary>
/// Get skill GFX composer for specific skill and section
/// 获取指定技能和段数的特效组合器
/// </summary>
/// <param name="skill">Skill ID / 技能ID</param>
/// <param name="section">Section number / 段号</param>
/// <returns>Skill GFX composer or null / 技能特效组合器或null</returns>
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;
}
/// <summary>
/// Get section information for specific skill and section
/// 获取指定技能和段数的段信息
/// </summary>
/// <param name="skill">Skill ID / 技能ID</param>
/// <param name="section">Section number / 段号</param>
/// <returns>Section info or null / 段信息或null</returns>
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;
}
}
/// <summary>
/// Placeholder class for A3DSkillGfxComposer
/// A3DSkillGfxComposer的占位类
/// </summary>
public class A3DSkillGfxComposer
{
public uint m_dwFlyTime; // 飞行时间 / Fly time in milliseconds
public A3DSkillGfxComposer()
{
m_dwFlyTime = 0;
}
/// <summary>
/// Load composer from file
/// 从文件加载组合器
/// </summary>
public bool Load(string szFile)
{
// C# equivalent of: AFileImage file;
// C# equivalent of: if (!file.Open(szFile, AFILE_OPENEXIST)) return false;
string fullPath = Path.Combine(Application.streamingAssetsPath, szFile);
if (!File.Exists(fullPath))
return false;
// C# equivalent of: if (!Load(&file))
using (var fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read))
{
if (!Load(fileStream))
{
// C# equivalent of: file.Close(); (handled by using statement)
return false;
}
}
// C# equivalent of: file.Close(); (handled by using statement)
return true;
}
/// <summary>
/// Load composer from stream (internal implementation)
/// 从流加载组合器(内部实现)
/// </summary>
private bool Load(Stream stream)
{
// TODO: Implement the actual parsing logic from A3DSkillGfxComposer::Load(AFileImage* pFile)
// 待实现:从 A3DSkillGfxComposer::Load(AFileImage* pFile) 移植实际的解析逻辑
BMLogger.LogWarning("A3DSkillGfxComposer.Load(Stream): Parsing logic not yet implemented");
return false;
}
/// <summary>
/// Initialize composer
/// 初始化组合器
/// </summary>
public void Init(object pSkillGfxMan)
{
// TODO: Implement initialization
}
/// <summary>
/// Play skill effect
/// 播放技能特效
/// </summary>
public void Play(int nHostID, int nCastTargetID, List<TARGET_DATA> targets, bool bIsGoblinSkill = false)
{
// TODO: Implement skill effect playback
// 待实现:技能特效播放逻辑
BMLogger.Log($"A3DSkillGfxComposer.Play: Host {nHostID}, CastTarget {nCastTargetID}, Targets:{targets?.Count ?? 0}, Goblin:{bIsGoblinSkill}");
}
}
[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<TARGET_DATA> m_targets = new List<TARGET_DATA>();
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)
{
//BMLogger.LogError($"HoangDev: m_timeToDoDamage:{m_timeToDoDamage} , dwDeltaTime: {dwDeltaTime}");
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()
{
BMLogger.Log("CECAttackEvent.DoFire called");
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)
{
// TODO: Implement ElementSkill.IsGoblinSkill check
pMan.Play(m_idSkill, m_nSkillSection, m_idHost, m_idCastTarget, m_targets, ElementSkill.IsGoblinSkill((uint)m_idSkill));
pComposer = pMan.GetSkillGfxComposer(m_idSkill, m_nSkillSection);
}
}
else
{
// TODO: Implement SkillGfxComposerMan
if (ElementSkill.IsGoblinSkill((uint)m_idSkill))
m_pManager.GetSkillGfxComposerMan().Play(m_idSkill, m_idHost, m_idCastTarget, m_targets, true);
else
m_pManager.GetSkillGfxComposerMan().Play(m_idSkill, m_idHost, m_idCastTarget, m_targets);
pComposer = m_pManager.GetSkillGfxComposerMan().GetSkillGfxComposer(m_idSkill);
BMLogger.LogWarning("SkillGfxComposerMan not yet implemented");
}
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))
{
m_timeToDoDamage = (uint)Mathf.Max((vecHost - vecTarget).magnitude / 20.0f * 1000.0f, 10.0f);
}
}
}
else if (m_idWeapon != 0)
{
BMLogger.Log($"CECAttackEvent.DoFire: m_idWeapon={m_idWeapon}");
// 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/*/
}
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 = pWeaponType.GetFileHitGfx();
szHitGFX = fullHitGfx.Length > 4 ? fullHitGfx.Substring(4) : string.Empty; // skip gfx/
}
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.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);
BMLogger.Log($"TODO: AddSkillGfxEvent - Host:{m_idHost}, Target:{data.idTarget}, FlyGfx:{pszFlyGFX}, HitGfx:{pszHitGFX}");
}
}
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];
string fullGfx = pWeaponType.GetFileHitGfx();
szGFX = fullGfx.Length > 4 ? fullGfx.Substring(4) : string.Empty; // skip gfx/
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;
}
var vfx = CECGameRun.Instance.GetTestVfx();
vfx.transform.position = target.position;
CECGameRun.Instance.DestroyTestVfx(vfx, 1f);
}
}
}
else if (GPDataTypeHelper.ISNPCID(m_idHost))
{
if (m_idSkill != 0)
{
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);
BMLogger.LogWarning("SkillGfxComposerMan not yet implemented");
}
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/
BMLogger.LogWarning("Monster essence not yet implemented");
}
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
BMLogger.LogWarning("Pet essence not yet implemented");
}
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);
BMLogger.Log($"TODO: AddSkillGfxEvent - Host:{m_idHost}, Target:{data.idTarget}, FlyGfx:{pszFlyGFX}, HitGfx:{pszHitGFX}");
}
}
}
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<CECAttackEvent> m_list = new List<CECAttackEvent>();
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()
{
foreach (var evt in m_list)
evt.m_bSignaled = true;
m_list.Clear();
}
public static implicit operator bool(CECAttackerEvents events)
{
return !events.IsEmpty();
}
}
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, // ·´»÷
};
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
};