1501 lines
55 KiB
C#
1501 lines
55 KiB
C#
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 BrewMonster.Scripts;
|
|
using UnityEngine;
|
|
using Cysharp.Threading.Tasks;
|
|
|
|
namespace BrewMonster
|
|
{
|
|
public class CECAttacksMan : MonoSingleton<CECAttacksMan>
|
|
{
|
|
private LinkedList<CECAttackEvent> m_targets = new LinkedList<CECAttackEvent>();
|
|
public CECMultiSectionSkillMan m_pMultiSkillGfxComposerMan;
|
|
protected A3DSkillGfxComposerMan m_pSkillGfxComposerMan;
|
|
#if UNITY_EDITOR
|
|
public List<CECAttackEvent> m_AttackList = new List<CECAttackEvent>();
|
|
#endif
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
}
|
|
protected override void OnDestroy()
|
|
{
|
|
m_targets = null;
|
|
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();
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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
|
|
/* 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()
|
|
{
|
|
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;
|
|
//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(dwDeltaTime);
|
|
}
|
|
node = next;
|
|
}
|
|
|
|
// Tick skill GFX events (fly/hit GFX state machine)
|
|
// 更新技能特效事件(飞行/命中特效状态机)
|
|
SkillGfxMan.InstanceSub.Tick(dwDeltaTime);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// Draw gizmos for skill projectiles in Unity Editor
|
|
/// 在Unity编辑器中绘制技能弹道辅助线
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw gizmos when selected (for debugging)
|
|
/// 选择时绘制辅助线(用于调试)
|
|
/// </summary>
|
|
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();
|
|
//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;
|
|
}
|
|
/// <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)
|
|
{
|
|
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}");
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
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 flyGFX;
|
|
private GameObject hitGrdGFX;
|
|
private GameObject hitGFX;
|
|
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 };
|
|
|
|
|
|
public A3DSkillGfxComposer()
|
|
{
|
|
m_dwFlyTime = 0;
|
|
m_FlyCluster = new GfxCluster
|
|
{
|
|
m_dwInterv = 0,
|
|
m_ulCount = 1
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load SkillStub GFX parameters onto this composer.
|
|
/// 将SkillStub的GFX参数加载到此组合器上。
|
|
/// </summary>
|
|
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 = stub.m_param;
|
|
}
|
|
|
|
// GFX prefab accessors / GFX预制体访问器
|
|
public GameObject GetFlyGFX() => flyGFX;
|
|
public GameObject GetHitGFX() => hitGFX;
|
|
public GameObject GetHitGrdGFX() => hitGrdGFX;
|
|
|
|
/// <summary>
|
|
/// Load composer from file
|
|
/// 从文件加载组合器
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
string hitGfxName;
|
|
string flyGfxName;
|
|
string hitGrdGfxName;
|
|
#endif
|
|
public async UniTask<bool> Load(SkillStub skillStub, string flyGFXPath, string hitGrdGFXPath, string hitGFXPath)
|
|
{
|
|
flyGfxName = flyGFXPath;
|
|
hitGfxName = hitGFXPath;
|
|
hitGrdGfxName = hitGrdGFXPath;
|
|
|
|
// Load GFX prefabs / 加载GFX预制体
|
|
flyGFX = string.IsNullOrEmpty(flyGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + flyGfxName);
|
|
hitGFX = string.IsNullOrEmpty(hitGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGfxName);
|
|
hitGrdGFX = string.IsNullOrEmpty(hitGrdGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGrdGfxName);
|
|
|
|
//BMLogger.LogError("HoangDev: Load A3DSkillGfxComposer GFX name: " + name);
|
|
if (flyGFX == null && !string.IsNullOrEmpty(flyGfxName))
|
|
{
|
|
flyGFX = Resources.Load<GameObject>("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;
|
|
|
|
// Area / 区域
|
|
/* m_param.m_bArea = skillStub.m_bArea;
|
|
m_param.m_Shape = skillStub.m_Shape;
|
|
m_param.m_vSize = skillStub.m_vSize;
|
|
|
|
// Param value / 参数值
|
|
m_param.value = skillStub.m_param.value;*/
|
|
}
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// SpawnGFX temp hack REMOVED — GFX spawning now handled by CECSkillGfxEvent state machine
|
|
// SpawnGFX临时代码已删除 — GFX生成现在由CECSkillGfxEvent状态机处理
|
|
/// <summary>
|
|
/// Initialize composer
|
|
/// 初始化组合器
|
|
/// </summary>
|
|
public void Init(A3DSkillGfxMan pSkillGfxMan)
|
|
{
|
|
m_pSkillGfxMan = pSkillGfxMan;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play skill effect
|
|
/// 播放技能特效
|
|
/// </summary>
|
|
public void Play(int nHostID, int nCastTargetID, List<TARGET_DATA> targets, bool bIsGoblinSkill = false)
|
|
{
|
|
bool bCastInTargets = false;
|
|
|
|
// Determine GFX names from loaded prefabs / 从已加载的预制体确定GFX名称
|
|
string szFly = flyGFX != null ? flyGfxName : null;
|
|
string szHit = hitGFX != 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<TARGET_DATA>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate that a target exists and its GameObject is not destroyed
|
|
/// 验证目标存在且其GameObject未销毁
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
// 调用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<TARGET_DATA> m_targets = new List<TARGET_DATA>();
|
|
|
|
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;
|
|
debugCounter = UnityEngine.Random.Range(0, 1000);
|
|
|
|
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()
|
|
{
|
|
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);
|
|
//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()
|
|
{
|
|
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
|
|
};
|
|
|