done cast skill move linear

This commit is contained in:
VDH
2026-02-26 15:01:46 +07:00
parent 9d4dfbc6ba
commit 7ee3394834
21 changed files with 1098 additions and 320 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60c9fc32910746f134c7ade2390c674afb51eae2d258e8292b0bb9e817d02732
size 282839
oid sha256:53692fc411388ad4af78104667731d6952578dbdd4a549d5706ecb7b64986d63
size 284380
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:486a1814b7c89098f245397c87a04b43c8b9d7b906d4a7de20930badea5292fb
size 107133
oid sha256:8c97935b0995b6688065daf1645d0f26e8b964dd5bf92b6cd66c37c5f97b5586
size 104077
@@ -128,7 +128,7 @@ namespace BrewMonster.Scripts
if (_loadedPrefabAssets.ContainsKey(assetPath))
{
BMLogger.Log($"AddressableManager: Asset already loaded: {assetPath} is valid: (${_loadedPrefabAssets[assetPath].Result != null})");
//BMLogger.Log($"AddressableManager: Asset already loaded: {assetPath} is valid: (${_loadedPrefabAssets[assetPath].Result != null})");
return _loadedPrefabAssets[assetPath].Result;
}
+73 -36
View File
@@ -1,37 +1,74 @@
#define ENALBE_LOGGING
using UnityEngine;
namespace BrewMonster
{
public class BMLogger
{
public static void Log(string message)
{
#if ENALBE_LOGGING
Debug.Log(message);
#endif
}
public static void LogError(string message)
{
#if ENALBE_LOGGING
Debug.LogError(message);
#endif
}
public static void LogWarning(string message)
{
#if ENALBE_LOGGING
Debug.LogWarning(message);
#endif
}
public static void LogMono(object source, string message)
{
#if ENALBE_LOGGING && UNITY_EDITOR
if (DebugRegistry.IsEnabled(source))
UnityEngine.Debug.LogError($"[{source}] {message}");
#endif
}
}
#define ENALBE_LOGGING
using System;
using System.IO;
using UnityEngine;
namespace BrewMonster
{
public class BMLogger
{
// File logging callback - set by SessionFileLogger
private static Action<string> s_FileLogCallback = null;
public static void SetFileLogCallback(Action<string> callback)
{
s_FileLogCallback = callback;
}
public static void ClearFileLogCallback()
{
s_FileLogCallback = null;
}
private static void WriteToFile(string message)
{
if (s_FileLogCallback != null)
{
try
{
s_FileLogCallback(message);
}
catch (Exception ex)
{
Debug.LogError($"BMLogger: Failed to write to file: {ex.Message}");
}
}
}
public static void Log(string message)
{
#if ENALBE_LOGGING
Debug.Log(message);
WriteToFile(message);
#endif
}
public static void LogError(string message)
{
#if ENALBE_LOGGING
Debug.LogError(message);
WriteToFile(message);
#endif
}
public static void LogWarning(string message)
{
#if ENALBE_LOGGING
Debug.LogWarning(message);
WriteToFile(message);
#endif
}
public static void LogMono(object source, string message)
{
#if ENALBE_LOGGING && UNITY_EDITOR
if (DebugRegistry.IsEnabled(source))
{
string fullMessage = $"[{source}] {message}";
UnityEngine.Debug.LogError(fullMessage);
WriteToFile(fullMessage);
}
#endif
}
}
}
@@ -28,7 +28,10 @@ namespace BrewMonster
_instance = this as T;
Initialize();
}
protected virtual void OnDestroy()
{
_instance = null;
}
/// <summary>Override this method to initialize the singleton</summary>
protected virtual void Initialize()
{
@@ -0,0 +1,226 @@
using System;
using System.IO;
using UnityEngine;
namespace BrewMonster
{
/// <summary>
/// Handles file logging for each Unity play session.
/// Overwrites the same log file when play mode starts, with timestamp in filename to detect updates.
/// Intercepts BMLogger calls and writes them to the session log file.
///
/// Usage:
/// - Automatically initializes when play mode starts
/// - Call BMLogger.LogError("HIHIHIHI") and it will appear in the log file
/// - Each play session overwrites the log file (old files are cleaned up)
/// - Filename includes timestamp so you can see when it was last updated
/// - Log files are saved in the "Logs" directory in your project root
/// </summary>
public class SessionFileLogger : MonoBehaviour
{
private static SessionFileLogger s_Instance = null;
private string m_LogFilePath = null;
private StreamWriter m_LogWriter = null;
private readonly object m_LockObject = new object();
[Header("Log Settings")]
[Tooltip("Directory where log files will be saved (relative to project root)")]
public string logDirectory = "Logs";
[Tooltip("Log file name prefix")]
public string logFileNamePrefix = "SessionLog";
/// <summary>
/// Auto-initializes when play mode starts.
/// Creates the SessionFileLogger GameObject automatically.
/// This ensures a fresh log file is created for each play session.
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void AutoInitialize()
{
// Clean up any existing instance (in case of play mode restart)
if (s_Instance != null)
{
if (s_Instance.m_LogWriter != null)
{
s_Instance.Cleanup();
}
if (s_Instance.gameObject != null)
{
DestroyImmediate(s_Instance.gameObject);
}
s_Instance = null;
}
// Create new instance for this play session
GameObject loggerObj = new GameObject("SessionFileLogger");
s_Instance = loggerObj.AddComponent<SessionFileLogger>();
}
private void Awake()
{
// Ensure only one instance exists
if (s_Instance != null && s_Instance != this)
{
Destroy(this);
return;
}
s_Instance = this;
DontDestroyOnLoad(gameObject);
InitializeLogFile();
}
private void OnDestroy()
{
if (s_Instance == this)
{
Cleanup();
s_Instance = null;
}
}
private void OnApplicationQuit()
{
Cleanup();
}
/// <summary>
/// Initializes a new log file for the current play session.
/// Overwrites old log files and creates a new one with timestamp in filename.
/// </summary>
private void InitializeLogFile()
{
try
{
// Get project root directory
string projectRoot = Application.dataPath.Replace("/Assets", "").Replace("\\Assets", "");
string logDir = Path.Combine(projectRoot, logDirectory);
// Ensure directory exists
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
// Clean up old log files with the same prefix (keep only the latest)
CleanupOldLogFiles(logDir);
// Generate log file path with timestamp (so you can see when it was updated)
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
string fileName = $"{logFileNamePrefix}_{timestamp}.txt";
m_LogFilePath = Path.Combine(logDir, fileName);
// Create/overwrite the log file
m_LogWriter = new StreamWriter(m_LogFilePath, false);
m_LogWriter.WriteLine($"=== Play Session Log ===");
m_LogWriter.WriteLine($"Started: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
m_LogWriter.WriteLine($"Project: {Application.productName}");
m_LogWriter.WriteLine($"Unity Version: {Application.unityVersion}");
m_LogWriter.WriteLine($"==========================================");
m_LogWriter.WriteLine();
m_LogWriter.Flush();
// Set up BMLogger callback
BMLogger.SetFileLogCallback(WriteLogMessage);
Debug.Log($"[SessionFileLogger] Log file initialized: {m_LogFilePath}");
}
catch (Exception ex)
{
Debug.LogError($"[SessionFileLogger] Failed to initialize log file: {ex.Message}");
}
}
/// <summary>
/// Deletes old log files with the same prefix to keep only the current session file.
/// </summary>
private void CleanupOldLogFiles(string logDir)
{
try
{
if (!Directory.Exists(logDir))
return;
string searchPattern = $"{logFileNamePrefix}_*.txt";
string[] oldFiles = Directory.GetFiles(logDir, searchPattern);
foreach (string oldFile in oldFiles)
{
try
{
File.Delete(oldFile);
}
catch (Exception ex)
{
Debug.LogWarning($"[SessionFileLogger] Failed to delete old log file {oldFile}: {ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"[SessionFileLogger] Error cleaning up old log files: {ex.Message}");
}
}
/// <summary>
/// Writes a log message to the file.
/// Called by BMLogger when Log, LogError, or LogWarning is called.
/// </summary>
private void WriteLogMessage(string message)
{
if (m_LogWriter == null || string.IsNullOrEmpty(m_LogFilePath))
return;
lock (m_LockObject)
{
try
{
string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
m_LogWriter.WriteLine($"[{timestamp}] {message}");
m_LogWriter.Flush();
}
catch (Exception ex)
{
Debug.LogError($"[SessionFileLogger] Failed to write log message: {ex.Message}");
}
}
}
/// <summary>
/// Cleans up the log file and removes BMLogger callback.
/// </summary>
private void Cleanup()
{
lock (m_LockObject)
{
try
{
if (m_LogWriter != null)
{
m_LogWriter.WriteLine();
m_LogWriter.WriteLine($"==========================================");
m_LogWriter.WriteLine($"Session ended: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
m_LogWriter.Flush();
m_LogWriter.Close();
m_LogWriter = null;
}
BMLogger.ClearFileLogCallback();
}
catch (Exception ex)
{
Debug.LogError($"[SessionFileLogger] Error during cleanup: {ex.Message}");
}
}
}
/// <summary>
/// Gets the current log file path (for debugging/inspection).
/// </summary>
public static string GetLogFilePath()
{
return s_Instance != null ? s_Instance.m_LogFilePath : null;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9118777afdffebc47861ed63bbb15c9a
@@ -60,8 +60,6 @@ namespace BrewMonster
bool bReverse
)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddSkillGfxEvent: host={nHostID}, target={nTargetID}, fly={szFlyGfx ?? "NULL"}, hit={szHitGfx ?? "NULL"}, flyTime={dwFlyTimeSpan}, mode={FlyMode}, count={nFlyGfxCount}, interval={dwInterval}");
bool bRet = true, bCluster;
uint dwDelayTime;
@@ -76,12 +74,9 @@ namespace BrewMonster
bCluster = true;
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddSkillGfxEvent: Creating {nFlyGfxCount} event(s), cluster={bCluster}, delay={dwDelayTime}");
for (int i = 0; i < nFlyGfxCount; i++)
{
string value = bOnlyOneHit && i != nFlyGfxCount - 1 ? "" : szHitGfx;
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddSkillGfxEvent: Creating event {i + 1}/{nFlyGfxCount}, hitGfx={value ?? "NULL"}");
if (!AddOneSkillGfxEvent(
pComposer,
@@ -101,13 +96,8 @@ namespace BrewMonster
bReverse
))
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddSkillGfxEvent: AddOneSkillGfxEvent FAILED for event {i + 1}");
bRet = false;
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddSkillGfxEvent: AddOneSkillGfxEvent SUCCESS for event {i + 1}");
}
dwDelayTime += dwInterval;
}
@@ -131,7 +121,6 @@ namespace BrewMonster
bool bIsGoblinSkill,
bool bReverse)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneSkillGfxEvent: host={nHostID}, target={nTargetID}, fly={szFlyGfx ?? "NULL"}, hit={szHitGfx ?? "NULL"}, mode={mode}, flyTime={dwFlyTimeSpan}, delay={dwDelayTime}");
// Validate host ID
// 验证施法者ID
@@ -143,13 +132,8 @@ namespace BrewMonster
// Validate target ID - allow 0 for area skills, but warn about suspiciously large negative values
// 验证目标ID - 允许0用于区域技能,但对可疑的大负值发出警告
if (nTargetID < -1000000000) // Suspiciously large negative value (likely uninitialized)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneSkillGfxEvent: WARNING - Suspicious target ID ({nTargetID}), this may be uninitialized. Event will be created but may fail target lookup.");
}
A3DSkillGfxEvent pEvent = SkillGfxMan.InstanceSub.GetEmptyEvent(mode);
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneSkillGfxEvent: Event created from pool, type={pEvent.GetType().Name}");
pEvent.SetComposer(pComposer);
pEvent.SetHostID(nHostID);
@@ -183,7 +167,6 @@ namespace BrewMonster
pEvent.Tick(0);
#endif
PushEvent(pEvent);
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneSkillGfxEvent: Event pushed to SkillGfxMan, active events={SkillGfxMan.InstanceSub.m_EventLst.Count}");
return true;
}
public virtual bool GetPropertyById(long nId, ref ECMODEL_GFX_PROPERTY pProperty) => false;
@@ -17,12 +17,22 @@ namespace BrewMonster
{
public class CECAttacksMan : MonoSingleton<CECAttacksMan>
{
private readonly LinkedList<CECAttackEvent> m_targets = new LinkedList<CECAttackEvent>();
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 = null;
base.OnDestroy();
}
private void Start()
{
StartLoad();
@@ -154,7 +164,9 @@ namespace BrewMonster
//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);
else {
node.Value.Tick(dwDeltaTime);
}
node = next;
}
@@ -631,14 +643,10 @@ namespace BrewMonster
{
bool bCastInTargets = false;
BMLogger.LogError($"[SKILL_GFX_DEBUG] Composer.Play: host={nHostID}, castTarget={nCastTargetID}, targets={(targets?.Count ?? 0)}, isGoblin={bIsGoblinSkill}");
// Determine GFX names from loaded prefabs / 从已加载的预制体确定GFX名称
string szFly = flyGFX != null ? flyGfxName : null;
string szHit = hitGFX != null ? hitGfxName : null;
BMLogger.LogError($"[SKILL_GFX_DEBUG] Composer.Play: flyGFX={(flyGFX != null ? "LOADED" : "NULL")}, hitGFX={(hitGFX != null ? "LOADED" : "NULL")}, szFly={szFly ?? "NULL"}, szHit={szHit ?? "NULL"}");
// TODO Phase 2: Optimization checks / 第二阶段:优化检查
// if (!CECOptimize.Instance.GetGFX().CanShowFly(nHostID)) szFly = null;
// if (!CECOptimize.Instance.GetGFX().CanShowHit(nHostID)) szHit = null;
@@ -668,7 +676,6 @@ namespace BrewMonster
int originalCount = targets.Count;
targets = validTargets;
BMLogger.LogError($"[SKILL_GFX_DEBUG] Composer.Play: Processing {targets.Count} valid targets (filtered from {originalCount})");
for (int i = 0; i < targets.Count; i++)
{
@@ -676,7 +683,6 @@ namespace BrewMonster
if (nCastTargetID == tar.idTarget)
bCastInTargets = true;
BMLogger.LogError($"[SKILL_GFX_DEBUG] Composer.Play: Calling AddOneTarget for target {i}/{targets.Count}, id={tar.idTarget}");
AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, i == 0, bIsGoblinSkill);
}
}
@@ -695,7 +701,6 @@ namespace BrewMonster
tar.idTarget = nCastTargetID;
tar.dwModifier = 0;
BMLogger.LogError($"[SKILL_GFX_DEBUG] Composer.Play: Cast target not in targets list, adding separately");
AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, false, bIsGoblinSkill);
}
@@ -710,12 +715,32 @@ namespace BrewMonster
if (GPDataTypeHelper.ISNPCID(idTarget))
{
var npc = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPCFromAll(idTarget);
return npc != null && npc.gameObject != null;
// 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);
return player != null && player.gameObject != null;
if (player == null) return false;
try
{
return player.gameObject != null;
}
catch (System.Exception)
{
// Object was destroyed - return false
return false;
}
}
return false;
}
@@ -732,9 +757,6 @@ namespace BrewMonster
float fScale;
bool bReverse;
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneTarget: castTarget={nCastTargetID}, host={nHostID}, target={tar.idTarget}, fly={szFly ?? "NULL"}, hit={szHit ?? "NULL"}, first={bFirst}");
// 根据目标模式决定Host和Target的映射 / Determine Host and Target mapping based on target mode
switch (m_TargetMode)
{
case GfxTargetMode.enumTargetToHost:
@@ -753,8 +775,6 @@ namespace BrewMonster
break;
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneTarget: After mapping - _Host={_Host}, _Target={_Target}, reverse={bReverse}, moveMode={m_MoveMode}, flyTime={m_dwFlyTime}");
// 计算缩放 / Calculate scale
/* if (m_bRelScl)
fScale = SkillGfxMan.InstanceSub.GetTargetScale(_Target) / m_fDefTarScl * m_fHitGfxScale;
@@ -781,8 +801,6 @@ namespace BrewMonster
return;
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneTarget: Calling m_pSkillGfxMan.AddSkillGfxEvent(host={_Host}, target={_Target}, fly={szFly ?? "NULL"}, hit={szHit ?? "NULL"}, flyTime={m_dwFlyTime}, moveMode={m_MoveMode})");
// 调用GFX管理器添加技能特效事件 / Call GFX manager to add skill GFX event
m_pSkillGfxMan.AddSkillGfxEvent(
this,
@@ -829,6 +847,9 @@ public class CECAttackEvent
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,
@@ -844,6 +865,7 @@ public class CECAttackEvent
m_timeToBeFired = (uint)nTimeToBeFired;
m_timeToDoDamage = (uint)nTimeToDoDamage;
m_bFinished = false;
debugCounter = UnityEngine.Random.Range(0, 1000);
AddTarget(idTarget, dwModifier, nDamage);
}
@@ -895,13 +917,8 @@ public class CECAttackEvent
public void SetSkillSection(int nSection) { m_nSkillSection = nSection; }
bool DoFire()
{
float vFlyScale = 1.0f;
float vHitScale = 1.0f;
m_bDoFired = true;
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: skill={m_idSkill}, host={m_idHost}, castTarget={m_idCastTarget}, targets={m_targets.Count}, section={m_nSkillSection}");
if (GPDataTypeHelper.ISPLAYERID(m_idHost))
{
@@ -916,10 +933,8 @@ public class CECAttackEvent
if (pMan != null)
{
bool isGoblin = ElementSkill.IsGoblinSkill((uint)m_idSkill);
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: Multi-section skill, calling pMan.Play()");
pMan.Play(m_idSkill, m_nSkillSection, m_idHost, m_idCastTarget, m_targets, isGoblin);
pComposer = pMan.GetSkillGfxComposer(m_idSkill, m_nSkillSection);
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: Multi-section composer={(pComposer != null ? "FOUND" : "NULL")}");
}
else
{
@@ -932,18 +947,15 @@ public class CECAttackEvent
// Get the composer manager
var composerMan = m_pManager.GetSkillGfxComposerMan();
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: composerMan={(composerMan != null ? "FOUND" : "NULL")}");
if (composerMan != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: Calling composerMan.Play(skill={m_idSkill}, host={m_idHost}, castTarget={m_idCastTarget}, targets={m_targets.Count}, isGoblin={isGoblin})");
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);
BMLogger.LogError($"[SKILL_GFX_DEBUG] DoFire: Composer lookup result={(pComposer != null ? "FOUND" : "NULL")}, flyTime={(pComposer != null ? pComposer.m_dwFlyTime : 0)}");
}
else
{
@@ -1484,4 +1496,4 @@ public enum GfxSkillValType
enumGfxSkillInt,
enumGfxSkillFloat,
enumGfxSkillValTypeNum
};
};
+271 -36
View File
@@ -14,12 +14,14 @@ using UnityEngine;
public class CECNPCMan : IMsgHandler
{
private Dictionary<int, CECNPC> m_NPCTab = new Dictionary<int, CECNPC>(512);
private Dictionary<int, int> m_UkNPCTab = new Dictionary<int, int>(32);
private Dictionary<int, CECNPC> m_NPCTab ;
private Dictionary<int, int> m_UkNPCTab ;
List<CECNPC> m_aDisappearNPCs = new List<CECNPC>(32);
List<CECNPC> m_aDisappearNPCs ;
public int HandlerId => (int)MANAGER_INDEX.MAN_NPC;
// Static counter to track calls - resets to 0 when new instance is created (play mode starts)
// List of NPCs to remove. It's needed in every tick.
// Having this as a global variable is more efficient than creating a new list every tick.
CECNPC[] aRemove = new CECNPC[64];
@@ -29,6 +31,187 @@ public class CECNPCMan : IMsgHandler
public CECNPCMan()
{
//
m_NPCTab = new Dictionary<int, CECNPC>(512);
m_UkNPCTab = new Dictionary<int, int>(32);
m_aDisappearNPCs = new List<CECNPC>(32);
// Reset debug counter when new instance is created (play mode starts)
}
/// <summary>
/// Adds or updates an NPC in the m_NPCTab dictionary with comprehensive logging.
/// </summary>
/// <param name="nid">NPC ID</param>
/// <param name="npc">NPC object to add</param>
/// <param name="reason">Reason for adding (for logging purposes)</param>
/// <returns>True if added successfully, false if npc is null or invalid</returns>
private bool AddNPCToTable(int nid, CECNPC npc, string reason = "Unknown")
{
string stackTrace = System.Environment.StackTrace.Split('\n')[1].Trim();
int countBefore = m_NPCTab.Count;
bool keyExists = m_NPCTab.ContainsKey(nid);
CECNPC oldValue = keyExists ? m_NPCTab[nid] : null;
// Validate input
if (npc == null)
{
BMLogger.LogError($"[DICT_TRACE] AddNPCToTable: FAILED - npc is NULL for nid={nid}, reason={reason}");
return false;
}
// Check if old value exists and is different
if (keyExists && oldValue != null)
{
bool oldIsDestroyed = false;
try
{
oldIsDestroyed = oldValue.gameObject == null;
}
catch (System.Exception)
{
oldIsDestroyed = true;
}
if (oldValue != npc)
{
BMLogger.LogError($"[DICT_TRACE] AddNPCToTable: REPLACING existing NPC - nid={nid}, oldValue={(oldValue != null ? oldValue.name : "NULL")}, oldIsDestroyed={oldIsDestroyed}, newValue={npc.name}");
}
}
// Check new value state
bool newIsNull = npc == null;
bool newGameObjectIsNull = false;
string newNPCName = "NULL";
try
{
newGameObjectIsNull = npc.gameObject == null;
newNPCName = npc.name;
}
catch (System.Exception ex)
{
newGameObjectIsNull = true;
BMLogger.LogError($"[DICT_TRACE] AddNPCToTable: Exception checking new npc.gameObject for nid={nid}: {ex.Message}");
}
// Add to dictionary
m_NPCTab[nid] = npc;
int countAfter = m_NPCTab.Count;
// Verify the value was set correctly
bool verifySuccess = m_NPCTab.TryGetValue(nid, out var verifyNPC);
bool verifyIsNull = verifyNPC == null;
bool verifyGameObjectIsNull = false;
try
{
if (verifyNPC != null)
verifyGameObjectIsNull = verifyNPC.gameObject == null;
}
catch (System.Exception ex)
{
verifyGameObjectIsNull = true;
BMLogger.LogError($"[DICT_TRACE] AddNPCToTable: Exception verifying npc.gameObject for nid={nid}: {ex.Message}");
}
if (verifyIsNull || verifyGameObjectIsNull)
{
BMLogger.LogError($"[DICT_TRACE] AddNPCToTable: WARNING - Value is NULL or destroyed immediately after setting! nid={nid}, reason={reason}");
}
return true;
}
/// <summary>
/// Removes an NPC from the m_NPCTab dictionary with comprehensive logging.
/// </summary>
/// <param name="nid">NPC ID to remove</param>
/// <param name="reason">Reason for removal (for logging purposes)</param>
/// <returns>True if removed successfully, false if key didn't exist</returns>
private bool RemoveNPCFromTable(int nid, string reason = "Unknown")
{
string stackTrace = System.Environment.StackTrace.Split('\n')[1].Trim();
int countBefore = m_NPCTab.Count;
bool keyExists = m_NPCTab.ContainsKey(nid);
CECNPC valueBeforeRemove = null;
bool valueIsNull = false;
bool gameObjectIsNull = false;
string npcName = "NULL";
if (!keyExists)
{
BMLogger.LogError($"[DICT_TRACE] RemoveNPCFromTable: KEY NOT FOUND - nid={nid}, reason={reason}");
return false;
}
// Check value state before removal
valueBeforeRemove = m_NPCTab[nid];
valueIsNull = valueBeforeRemove == null;
if (!valueIsNull)
{
try
{
gameObjectIsNull = valueBeforeRemove.gameObject == null;
npcName = valueBeforeRemove.name;
}
catch (System.Exception ex)
{
gameObjectIsNull = true;
BMLogger.LogError($"[DICT_TRACE] RemoveNPCFromTable: Exception accessing value before remove for nid={nid}: {ex.Message}");
}
}
// Remove from dictionary
bool removed = m_NPCTab.Remove(nid);
int countAfter = m_NPCTab.Count;
// Verify removal
bool keyStillExists = m_NPCTab.ContainsKey(nid);
if (keyStillExists)
{
BMLogger.LogError($"[DICT_TRACE] RemoveNPCFromTable: ERROR - Key still exists after removal! nid={nid}, reason={reason}");
}
if (valueIsNull || gameObjectIsNull)
{
BMLogger.LogError($"[DICT_TRACE] RemoveNPCFromTable: REMOVED NULL/DESTROYED VALUE - nid={nid}, valueIsNull={valueIsNull}, gameObjectIsNull={gameObjectIsNull}, reason={reason}. This explains why key existed but value was null!");
}
return removed;
}
/// <summary>
/// Clean up destroyed objects from dictionaries. Call this when play mode starts to remove stale references.
/// </summary>
public void CleanupDestroyedObjects()
{
// Clean up destroyed NPCs from main table
var keysToRemove = new List<int>();
int nullCount = 0;
foreach (var kvp in m_NPCTab)
{
if (kvp.Value == null)
{
nullCount++;
keysToRemove.Add(kvp.Key);
}
else if (kvp.Value.gameObject == null)
{
nullCount++;
keysToRemove.Add(kvp.Key);
}
}
foreach (var key in keysToRemove)
{
RemoveNPCFromTable(key, "CleanupDestroyedObjects - null/destroyed entry");
}
// Clean up destroyed NPCs from disappear table
int beforeDisappearCount = m_aDisappearNPCs.Count;
m_aDisappearNPCs.RemoveAll(npc => npc == null || npc.gameObject == null);
}
public bool ProcessMessage(ECMSG Msg)
{
@@ -94,9 +277,14 @@ public class CECNPCMan : IMsgHandler
public void Tick()
{
iRemoveCnt = 0;
// Tick all NPCs
foreach (var pNPC in m_NPCTab.Values)
{
if (pNPC == null || pNPC.gameObject == null)
continue; // Skip destroyed objects
if (pNPC.ShouldDisappear())
{
if (iRemoveCnt < SIZE_REMOVETAB)
@@ -109,7 +297,13 @@ public class CECNPCMan : IMsgHandler
}
for (int i = 0; i < iRemoveCnt; i++)
NPCLeave(aRemove[i].GetNPCID());
{
if (aRemove[i] != null)
{
int nid = aRemove[i].GetNPCID();
NPCLeave(nid);
}
}
// Tick all NPCs who are in disappear table
iRemoveCnt = 0;
@@ -136,7 +330,11 @@ public class CECNPCMan : IMsgHandler
for (int i = 0; i < iRemoveCnt; i++)
{
ReleaseNPC(aRemove[i]);
if (aRemove[i] != null)
{
int nid = aRemove[i].GetNPCID();
ReleaseNPC(aRemove[i]);
}
}
// Update NPCs in various ranges (Active, visible, mini-map etc.)
@@ -148,7 +346,6 @@ public class CECNPCMan : IMsgHandler
}
private void OnMsgNPCDisappear(ECMSG Msg)
{
BMLogger.Log("HoangDev : OnMsgNPCDisappear ");
var pCmd = GPDataTypeHelper.FromBytes<cmd_object_disappear>((byte[])Msg.dwParam1);
NPCDisappear(pCmd.id);
@@ -173,13 +370,20 @@ public class CECNPCMan : IMsgHandler
NPCLeave(nid, true, false);
m_aDisappearNPCs.Add(pNPC);
}
else
{
BMLogger.LogError($"[NPC_REMOVAL_TRACE] NPCDisappear: NPC {nid} NOT FOUND in table");
}
}
void NPCLeave(int nid, bool bUpdateMMArray = true, bool bRelease = true)
{
// Release NPC
CECNPC pNPC = GetNPC(nid);
if (!pNPC)
{
BMLogger.LogError($"[NPC_REMOVAL_TRACE] NPCLeave: NPC {nid} NOT FOUND in table, cannot remove");
return;
}
/*if (bUpdateMMArray)
RemoveNPCFromMiniMap(pNPC);*/
@@ -191,16 +395,16 @@ public class CECNPCMan : IMsgHandler
hostplayer.SelectTarget(0);
// Remove it from active NPC table
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.NPCLeave: Removing NPC from m_NPCTab - nid={nid}, table size before={m_NPCTab.Count}");
bool removed = m_NPCTab.Remove(nid);
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.NPCLeave: NPC removed={(removed ? "SUCCESS" : "FAILED (not in table)" )}, table size after={m_NPCTab.Count}");
bool removed = RemoveNPCFromTable(nid, $"NPCLeave - bRelease={bRelease}");
// Forbid reloading npc's resources
//QueueNPCUndoLoad(nid, pNPC->GetBornStamp());
// Release NPC resource
if (bRelease)
{
ReleaseNPC(pNPC);
}
else
{
CECHostPlayer pHost = hostplayer;
@@ -215,13 +419,21 @@ public class CECNPCMan : IMsgHandler
{
if (pNPC)
{
int nid = pNPC.GetNPCID();
// Remove tab-selected array
CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer();
if (pHost)
pHost.RemoveObjectFromTabSels(pNPC);
pNPC.Release();
pNPC.DestroySelf();
}
else
{
BMLogger.LogError($"[NPC_REMOVAL_TRACE] ReleaseNPC: pNPC is NULL, cannot release");
}
}
private bool TransmitMessage(ECMSG Msg)
@@ -323,16 +535,19 @@ public class CECNPCMan : IMsgHandler
// if (!bDelay)
// NPCDisappear(nid);
}
else
{
if (pNPC == null)
BMLogger.LogError($"[NPC_REMOVAL_TRACE] OnMsgNPCDied: NPC {nid} NOT FOUND in table");
else if (pNPC.IsAboutToDie())
BMLogger.LogError($"[NPC_REMOVAL_TRACE] OnMsgNPCDied: NPC {nid} already about to die, skipping");
}
return true;
}
private bool OnMsgNPCStopMove(ECMSG msg)
{
cmd_object_stop_move pCmd = EC_Utility.ByteArrayToStructure<cmd_object_stop_move>((byte[])msg.dwParam1);
if (-2041571143 == pCmd.id)
{
BMLogger.Log("HoangDev: OnMsgNPCStopMove NPCID: " + pCmd.id);
}
CECNPC pNPC = SeekOutNPC(pCmd.id);
if (pNPC)
pNPC.StopMoveTo(pCmd);
@@ -377,17 +592,14 @@ public class CECNPCMan : IMsgHandler
private bool OnMsgNPCInfo(ECMSG msg)
{
int commandId = Convert.ToInt32(msg.dwParam2);
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: Received NPC info message, commandID={commandId}");
switch (commandId)
{
case CommandID.NPC_INFO_LIST:
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: Processing NPC_INFO_LIST");
// msg.dwParam1 chính là buffer chứa placeholder data (không có header cmd_npc_info_list)
cmd_npc_info_list pCmd = MemoryMarshal.Read<cmd_npc_info_list>(((byte[])msg.dwParam1).AsSpan());
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: NPC_INFO_LIST contains {pCmd.count} NPC(s)");
int offset = Marshal.OffsetOf<cmd_npc_info_list>("placeholder").ToInt32();
byte[] buffer = (byte[])msg.dwParam1;
@@ -398,7 +610,6 @@ public class CECNPCMan : IMsgHandler
// giống const info_npc& Info = *(const info_npc*)pDataBuf;
info_npc info = MemoryMarshal.Read<info_npc>(pDataBuf);
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: Processing NPC {i + 1}/{pCmd.count} - nid={info.nid}, tid={info.tid}");
int iSize = info_npc.HEADER_SIZE;
if ((info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0)
@@ -429,20 +640,16 @@ public class CECNPCMan : IMsgHandler
case CommandID.NPC_ENTER_SLICE:
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: Processing NPC_ENTER_SLICE");
var buffer = (byte[])msg.dwParam1;
info_npc info = MemoryMarshal.Read<info_npc>(buffer.AsSpan(0, info_npc.HEADER_SIZE));
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: NPC_ENTER_SLICE - nid={info.nid}, tid={info.tid}");
NPCEnter(info, false, buffer, info_npc.HEADER_SIZE);
break;
}
case CommandID.NPC_ENTER_WORLD:
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: Processing NPC_ENTER_WORLD");
var buffer = (byte[])msg.dwParam1;
info_npc info = MemoryMarshal.Read<info_npc>(buffer.AsSpan(0, info_npc.HEADER_SIZE));
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.OnMsgNPCInfo: NPC_ENTER_WORLD - nid={info.nid}, tid={info.tid}");
NPCEnter(info, true, buffer, info_npc.HEADER_SIZE);
break;
}
@@ -483,7 +690,7 @@ public class CECNPCMan : IMsgHandler
var npc = GetNPC(Info.nid);
if (npc != null)
{
m_NPCTab.Remove(Info.nid);
RemoveNPCFromTable(Info.nid, "NPCEnter - replacing existing NPC");
GameObject.Destroy(npc.gameObject);
}
@@ -506,9 +713,7 @@ public class CECNPCMan : IMsgHandler
}
// Thêm NPC vào bảng
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.NPCEnter: Adding NPC to m_NPCTab - nid={Info.nid}, tid={Info.tid}, npc={(npc != null ? "created" : "NULL")}");
m_NPCTab[Info.nid] = npc;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.NPCEnter: NPC added successfully. m_NPCTab[Info.nid] = npc {m_NPCTab[Info.nid].name} NPC(s)");
AddNPCToTable(Info.nid, npc, $"NPCEnter - nid={Info.nid}, tid={Info.tid}, bBornInSight={bBornInSight}");
return true;
}
// Get NPC by id and optional bornStamp
@@ -517,6 +722,30 @@ public class CECNPCMan : IMsgHandler
if (!m_NPCTab.TryGetValue(nid, out var npc))
return null;
// Validate that the NPC object is not destroyed (Unity destroyed objects pass != null but throw on access)
if (npc == null)
{
// Clean up destroyed object from dictionary
RemoveNPCFromTable(nid, "GetNPC - null value detected");
return null;
}
try
{
if (npc.gameObject == null)
{
// Clean up destroyed object from dictionary
RemoveNPCFromTable(nid, "GetNPC - destroyed GameObject detected");
return null;
}
}
catch (System.Exception ex)
{
BMLogger.LogError($"[DICT_TRACE] GetNPC: Exception accessing npc.gameObject for nid={nid}: {ex.Message}, removing from dictionary");
// Clean up destroyed object from dictionary
RemoveNPCFromTable(nid, $"GetNPC - exception accessing GameObject: {ex.Message}");
return null;
}
return npc;
}
@@ -540,26 +769,32 @@ public class CECNPCMan : IMsgHandler
}
public CECNPC GetNPCFromAll(int nid)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.GetNPCFromAll: Looking for NPC nid={nid}, m_NPCTab.Count={m_NPCTab.Count}");
// Get stack trace to see who's calling this method
CECNPC pNPC = GetNPC(nid);
if (pNPC != null)
// Check for null/destroyed object BEFORE accessing properties (Unity destroyed objects pass != null but throw on access)
if (pNPC != null && pNPC.gameObject != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.GetNPCFromAll: GetNPC returned {pNPC.name}");
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.GetNPCFromAll: NPC {nid} FOUND in m_NPCTab");
return pNPC;
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.GetNPCFromAll: NPC {nid} NOT FOUND in m_NPCTab! Available NPC IDs: {string.Join(", ", m_NPCTab.Keys)}");
// Search from disappear array - provides grace period for GFX events (matches C++ behavior)
for (int i = 0; i < m_aDisappearNPCs.Count; i++)
{
CECNPC pDisappearNPC = m_aDisappearNPCs[i];
if (pDisappearNPC != null && pDisappearNPC.gameObject != null && pDisappearNPC.GetNPCID() == nid)
// Use Unity's == null check which properly handles destroyed objects
if (pDisappearNPC == null) continue;
if (pDisappearNPC.gameObject == null) continue;
try
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CECNPCMan.GetNPCFromAll: NPC {nid} FOUND in m_aDisappearNPCs");
return pDisappearNPC; // Return NPC even if removed from main table
if (pDisappearNPC.GetNPCID() == nid)
{
return pDisappearNPC;
}
}
catch (System.Exception)
{
// Object was destroyed between null check and access - skip it
continue;
}
}
@@ -227,44 +227,22 @@ namespace BrewMonster
// Log target existence issues with more detail
// 记录目标存在问题的更多详细信息
if (!m_bTargetExist && m_nTargetID != 0)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: WARNING - Target {m_nTargetID} does not exist (host={m_nHostID}, exist={m_bHostExist}, pos={m_vHostPos}), state={prevState}. Target may have been destroyed or ID is invalid.");
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: host={m_nHostID} (exist={m_bHostExist}, pos={m_vHostPos}), target={m_nTargetID} (exist={m_bTargetExist}, pos={m_vTargetPos}), state={prevState}");
}
base.Tick(dwDeltaTime);
// Log state transitions
if (prevState != m_enumState)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: State transition {prevState} → {m_enumState}, host={m_nHostID}, target={m_nTargetID}");
}
// Spawn fly GFX when entering Flying state / 进入飞行状态时生成飞行特效
if (prevState == GfxSkillEventState.enumWait && m_enumState == GfxSkillEventState.enumFlying)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Transitioning to Flying, calling SpawnFlyGfx()");
// Register for gizmo drawing BEFORE spawning (so we capture the initial position)
// 在生成前注册用于辅助线绘制(以便捕获初始位置)
#if UNITY_EDITOR
Vector3 currentPos = m_pMoveMethod.GetPos();
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Registering gizmo - hostPos={m_vHostPos}, targetPos={m_vTargetPos}, currentPos={currentPos}, hostExist={m_bHostExist}, targetExist={m_bTargetExist}");
// Only register if positions are valid (not zero)
// 仅在位置有效(非零)时注册
if (m_vHostPos.sqrMagnitude > 0.01f && m_vTargetPos.sqrMagnitude > 0.01f)
{
SkillGfxGizmoDrawer.RegisterProjectile(m_nHostID, m_nTargetID, m_vHostPos, m_vTargetPos, m_pMoveMethod.GetMode());
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Gizmo registration SKIPPED - invalid positions!");
}
#endif
SpawnFlyGfx();
@@ -302,7 +280,6 @@ namespace BrewMonster
/// </summary>
protected override void HitTarget(Vector3 vTarget)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] HitTarget: Entry, host={m_nHostID}, target={m_nTargetID}, pos={vTarget}");
base.HitTarget(vTarget);
DestroyFlyGfx();
SpawnHitGfx(vTarget);
@@ -320,7 +297,6 @@ namespace BrewMonster
/// </summary>
private void SpawnFlyGfx()
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: Entry, host={m_nHostID}, target={m_nTargetID}");
if (m_pComposer == null)
{
@@ -339,17 +315,8 @@ namespace BrewMonster
Vector3 dir = m_pMoveMethod.GetMoveDir();
Quaternion rot = dir.sqrMagnitude > 1e-4f ? Quaternion.LookRotation(dir) : Quaternion.identity;
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: Instantiating prefab={prefab.name} at pos={pos}, dir={dir}");
m_flyGfxInstance = GameObject.Instantiate(prefab, pos, rot);
if (m_flyGfxInstance != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: SUCCESS - Fly GFX spawned at {pos}, instance={m_flyGfxInstance.name}");
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: FAILED - Instantiate returned NULL!");
}
}
/// <summary>
@@ -384,7 +351,6 @@ namespace BrewMonster
/// </summary>
private void SpawnHitGfx(Vector3 vTarget)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: Entry, host={m_nHostID}, target={m_nTargetID}, pos={vTarget}");
if (m_pComposer == null)
{
@@ -407,17 +373,8 @@ namespace BrewMonster
if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir);
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: Instantiating prefab={prefab.name} at pos={vTarget}");
m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, rot);
if (m_hitGfxInstance != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: SUCCESS - Hit GFX spawned at {vTarget}, instance={m_hitGfxInstance.name}");
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: FAILED - Instantiate returned NULL!");
}
GameObject.Destroy(m_hitGfxInstance, 3.0f); // auto-cleanup / 自动清理
}
@@ -485,17 +442,14 @@ namespace BrewMonster
{
vPos = Vector3.zero;
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: Entry - nID={nID}, isPlayerID={GPDataTypeHelper.ISPLAYERID(nID)}, isNPCID={GPDataTypeHelper.ISNPCID(nID)}, pPlayerMan={(pPlayerMan != null ? "exists" : "NULL")}, pNPCMan={(pNPCMan != null ? "exists" : "NULL")}");
if (GPDataTypeHelper.ISPLAYERID(nID))
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: ID {nID} is a PLAYER ID");
CECPlayer pPlayer = pPlayerMan?.GetPlayer(nID);
// Check if player exists AND GameObject is not destroyed (Unity's "fake null" handling)
if (pPlayer != null && pPlayer.gameObject != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: Player {nID} found, getting position");
{
if (bIsGoblinSkill)
{
@@ -562,20 +516,14 @@ namespace BrewMonster
return true;
}
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: Player {nID} NOT FOUND or GameObject destroyed in pPlayerMan (pPlayerMan is {(pPlayerMan != null ? "not null" : "NULL")})");
}
}
else if (GPDataTypeHelper.ISNPCID(nID))
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: ID {nID} is an NPC ID");
CECNPC pNPC = pNPCMan?.GetNPCFromAll(nID);
// Check if NPC exists AND GameObject is not destroyed (Unity's "fake null" handling)
if (pNPC != null && pNPC.gameObject != null)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: NPC {nID} found, getting position");
{
while (true)
{
@@ -614,17 +562,8 @@ namespace BrewMonster
return true;
}
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: NPC {nID} NOT FOUND or GameObject destroyed in pNPCMan (pNPCMan is {(pNPCMan != null ? "not null" : "NULL")})");
}
}
else
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: ID {nID} is NEITHER a player ID nor an NPC ID! This is likely an invalid ID.");
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] _get_pos_by_id: Returning FALSE for ID {nID}");
return false;
}
@@ -779,10 +718,9 @@ namespace BrewMonster
private const int DEFAULT_EVENT_BUF_SIZE = 10; // 默认事件缓冲区大小 / Default event buffer size
public LinkedList<CECSkillGfxEvent> m_EventLst; // 活动事件列表 / Active event list
protected LinkedList<CECSkillGfxEvent>[] m_FreeLst; // 空闲事件列表(按移动模式分类) / Free event lists (categorized by move mode)
protected EC_ManPlayer m_pPlayerMan; // 玩家管理器 / Player manager
protected CECNPCMan m_pNPCMan; // NPC管理器 / NPC manager
protected LinkedList<CECSkillGfxEvent>[] m_FreeLst;
protected EC_ManPlayer m_pPlayerMan;
protected CECNPCMan m_pNPCMan;
public SkillGfxMan(CECGameRun pGameRun)
{
@@ -809,10 +747,7 @@ namespace BrewMonster
m_pNPCMan = EC_ManMessageMono.Instance?.CECNPCMan;
}
/// <summary>
/// Get empty event from pool or create new one
/// 从池中获取空事件或创建新事件
/// </summary>
public A3DSkillGfxEvent GetEmptyEvent(GfxMoveMode mode)
{
int modeIndex = (int)mode;
@@ -884,11 +819,6 @@ namespace BrewMonster
/// </summary>
public bool Tick(uint dwDeltaTime)
{
if (m_EventLst.Count > 0)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] SkillGfxMan.Tick: Processing {m_EventLst.Count} active event(s), deltaTime={dwDeltaTime}ms");
}
var node = m_EventLst.First;
while (node != null)
@@ -34,7 +34,6 @@ namespace BrewMonster
/// </summary>
public static void RegisterProjectile(long hostID, long targetID, Vector3 startPos, Vector3 targetPos, GfxMoveMode moveMode)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] Gizmo: Registering projectile host={hostID}, target={targetID}, start={startPos}, target={targetPos}, mode={moveMode}");
var gizmo = new GizmoData
{
@@ -47,7 +46,6 @@ namespace BrewMonster
moveMode = moveMode
};
m_GizmoList.Add(gizmo);
BMLogger.LogError($"[SKILL_GFX_DEBUG] Gizmo: Registered! Total gizmos={m_GizmoList.Count}");
}
/// <summary>
+2 -2
View File
@@ -794,8 +794,9 @@ public class CECNPC : CECObject
// No delay die, enter disappear process immediately
if (!bDelay)
{
Disappear();
}
StartWork((int)WorkType.WT_NORMAL, (int)WorkID.WORK_DEAD, m_dwStates);
@@ -803,7 +804,6 @@ public class CECNPC : CECObject
}
public void Disappear()
{
BMLogger.Log("CECNPC::Disappear");
FadeOut();
m_DisappearCnt.SetCounter(1);
PlayModelAction((int)NPCActionIndex.ACT_NPC_DISAPPEAR);
@@ -16,6 +16,7 @@ namespace CSNetwork
{
if (_instance == null)
{
BMLogger.LogError("EC_ManMessage instance is null, creating a new one. This should only happen once, if it happens more than once, there might be an issue with the lifecycle of EC_ManMessage");
_instance = new EC_ManMessage();
}
return _instance;
@@ -28,7 +29,8 @@ namespace CSNetwork
private static ECMSG _currentMsg;
private EC_ManMessage() { }
private EC_ManMessage() {
}
/// <summary>Post a message to the message queue</summary>
public static void PostMessage(long dwMsg, int iManager, int iSubID, object p1 = null, object p2 = null, object p3 = null, object p4 = null, MsgDataBase[] pData = null)
@@ -56,6 +58,11 @@ namespace CSNetwork
/// <summary>Register a message handler, it should be called before the game starts</summary>
public static void RegisterHandler(IMsgHandler handler)
{
if(Instance.m_MsgHandlerList.ContainsKey(handler.HandlerId))
{
BMLogger.LogError("Handler ID: " + handler.HandlerId + " is already registered");
return;
}
Instance.m_MsgHandlerList.Add(handler.HandlerId, handler);
}
@@ -81,6 +88,7 @@ namespace CSNetwork
public static void Dispose()
{
BMLogger.LogError("EC_ManMessage is being disposed, this should only happen when the game is exiting");
_instance = null;
}
}
@@ -1,89 +1,94 @@
using System;
using UnityEngine;
using CSNetwork;
using CSNetwork.GPDataType;
using PerfectWorld.Scripts.Managers.BrewMonster.Managers;
using PerfectWorld.Scripts.Managers;
namespace BrewMonster.Managers
{
[Serializable]
public class EC_ManMessageMono : MonoBehaviour
{
private static EC_ManMessageMono instance;
public static EC_ManMessageMono Instance
{
get
{
if (instance == null)
{
instance = FindAnyObjectByType<EC_ManMessageMono>();
}
return instance;
}
}
public EC_ManPlayer EC_ManPlayer;
public EC_ManMatter EC_ManMatter;
public EC_ManPlayer GetECManPlayer { get => EC_ManPlayer;}
public EC_ManMatter GetECManMatter { get => EC_ManMatter;}
public CECNPCMan CECNPCMan { get; private set; }
private void Awake()
{
instance = this;
//TODO: Remove later
EC_ManPlayer = new EC_ManPlayer();
CECNPCMan = new CECNPCMan();
EC_ManMessage.RegisterHandler(EC_ManPlayer);
EC_ManMessage.RegisterHandler(CECNPCMan);
EC_ManMatter = new EC_ManMatter();
EC_ManMessage.RegisterHandler(EC_ManMatter);
Debug.Log($"EC_ManMessage RegisterHandlerRegisterHandlerRegisterHandler");
}
private void OnDestroy()
{
EC_ManMessage.Dispose();
}
private void Update()
{
EC_ManMessage.Tick();
CECNPCMan.Tick();
}
// Get object by specified ID
// iAliveFlag: 0, both alive and dead; 1, must be alive; 2, must be dead
public CECObject GetObject(long idObject, int iAliveFlag)
{
CECObject pObject = null;
if (GPDataTypeHelper.ISNPCID((int)idObject))
{
if (!(pObject = CECNPCMan.GetNPC((int)idObject)))
return null;
if ((iAliveFlag == 1 && (pObject as CECNPC).IsDead()) ||
(iAliveFlag == 2 && !(pObject as CECNPC).IsDead()))
return null;
}
else if (GPDataTypeHelper.ISPLAYERID((int)idObject))
{
if (!(pObject = EC_ManPlayer.GetPlayer((int)idObject)))
return null;
if ((iAliveFlag == 1 && (pObject as CECPlayer).IsDead()) ||
(iAliveFlag == 2 && !(pObject as CECPlayer).IsDead()))
return null;
}
else if (GPDataTypeHelper.ISMATTERID((int)idObject))
{
pObject = EC_ManMatter.GetMatter((int)idObject);
}
return pObject;
}
}
using System;
using UnityEngine;
using CSNetwork;
using CSNetwork.GPDataType;
using PerfectWorld.Scripts.Managers.BrewMonster.Managers;
using PerfectWorld.Scripts.Managers;
namespace BrewMonster.Managers
{
[Serializable]
public class EC_ManMessageMono : MonoBehaviour
{
private static EC_ManMessageMono instance;
public static EC_ManMessageMono Instance
{
get
{
if (instance == null)
{
instance = FindAnyObjectByType<EC_ManMessageMono>();
}
return instance;
}
}
public EC_ManPlayer EC_ManPlayer;
public EC_ManMatter EC_ManMatter;
public EC_ManPlayer GetECManPlayer { get => EC_ManPlayer;}
public EC_ManMatter GetECManMatter { get => EC_ManMatter;}
public CECNPCMan CECNPCMan { get; private set; }
private void Awake()
{
instance = this;
//TODO: Remove later
EC_ManPlayer = new EC_ManPlayer();
CECNPCMan = new CECNPCMan();
// Clean up any destroyed objects that might persist from previous play session
CECNPCMan.CleanupDestroyedObjects();
EC_ManMessage.RegisterHandler(EC_ManPlayer);
EC_ManMessage.RegisterHandler(CECNPCMan);
EC_ManMatter = new EC_ManMatter();
EC_ManMessage.RegisterHandler(EC_ManMatter);
}
private void OnDestroy()
{
Dispose();
EC_ManMessage.Dispose();
}
private void Dispose()
{
instance = null;
}
private void Update()
{
EC_ManMessage.Tick();
CECNPCMan.Tick();
}
// Get object by specified ID
// iAliveFlag: 0, both alive and dead; 1, must be alive; 2, must be dead
public CECObject GetObject(long idObject, int iAliveFlag)
{
CECObject pObject = null;
if (GPDataTypeHelper.ISNPCID((int)idObject))
{
if (!(pObject = CECNPCMan.GetNPC((int)idObject)))
return null;
if ((iAliveFlag == 1 && (pObject as CECNPC).IsDead()) ||
(iAliveFlag == 2 && !(pObject as CECNPC).IsDead()))
return null;
}
else if (GPDataTypeHelper.ISPLAYERID((int)idObject))
{
if (!(pObject = EC_ManPlayer.GetPlayer((int)idObject)))
return null;
if ((iAliveFlag == 1 && (pObject as CECPlayer).IsDead()) ||
(iAliveFlag == 2 && !(pObject as CECPlayer).IsDead()))
return null;
}
else if (GPDataTypeHelper.ISMATTERID((int)idObject))
{
pObject = EC_ManMatter.GetMatter((int)idObject);
}
return pObject;
}
}
}
@@ -29,7 +29,9 @@ namespace BrewMonster
public void ShowLoadingScene(bool active)
{
goContent.SetActive(active);
#if !UNITY_EDITOR
goContent.SetActive(active);
#endif
}
async UniTaskVoid ObserveLoadingAsync(CancellationToken token)
@@ -4,6 +4,10 @@ namespace BrewMonster
{
public class ObjectSpawner : MonoSingleton<ObjectSpawner>
{
protected override void Awake()
{
base.Awake();
}
public GameObject InstantiateObject(GameObject prefab, Transform parent =null, bool setThisAsParent = true)
{
if (prefab == null)
@@ -78,7 +78,6 @@ namespace BrewMonster
List<TARGET_DATA> Targets,
bool bIsGoblinSkill = false)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] ComposerMan.Play: skill={nSkillID}, host={nHostID}, castTarget={nCastTargetID}, targets={(Targets?.Count ?? 0)}, isGoblin={bIsGoblinSkill}");
if (!m_ComposerMap.TryGetValue(nSkillID, out var composer) || composer == null)
{
@@ -86,14 +85,12 @@ namespace BrewMonster
return;
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] ComposerMan.Play: Composer FOUND, calling composer.Play()");
// Forward to composer (stubbed for now if Play not implemented)
try
{
// Provide a minimal default fly time estimation behavior when composer has no fly time configured
// Real effect triggering should be implemented inside composer.Play
BMLogger.LogError($"[SKILL_GFX_DEBUG] ComposerMan.Play: nCastTargetID={nCastTargetID}; Targets.count={Targets.Count}");
composer.Play(nHostID, nCastTargetID, Targets, bIsGoblinSkill);
}
catch (System.MissingMethodException)
@@ -20,39 +20,31 @@ namespace BrewMonster
/// </summary>
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Entry - host={vHost}, target={vTarget}, area={m_bArea}, maxFlyTime={m_dwMaxFlyTime}");
if (m_bArea)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Area skill - calculating range and random offset");
CalcRange((vTarget - vHost).normalized);
m_vPos = vHost + GetRandOff();
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Area skill - startPos={m_vPos} (host={vHost} + offset)");
}
else
{
m_vPos = vHost;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Non-area skill - startPos={m_vPos} (same as host)");
}
m_vMoveDir = vTarget - m_vPos;
float fDist = Normalize(ref m_vMoveDir);
float fMax = _fly_speed * m_dwMaxFlyTime;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Distance={fDist}, maxDistance={fMax} (speed={_fly_speed} * time={m_dwMaxFlyTime}), moveDir={m_vMoveDir}");
if (fMax >= fDist)
{
m_fSpeed = _fly_speed;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Using default speed={m_fSpeed} (distance fits in time)");
}
else
{
m_fSpeed = fDist / m_dwMaxFlyTime;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Using calculated speed={m_fSpeed} (distance={fDist} / time={m_dwMaxFlyTime})");
}
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.StartMove: Complete - pos={m_vPos}, dir={m_vMoveDir}, speed={m_fSpeed}");
}
/// <summary>
@@ -66,18 +58,15 @@ namespace BrewMonster
float fDist = Normalize(ref vFlyDir);
float fFlyDist = m_fSpeed * dwDeltaTime;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.TickMove: Entry - oldPos={oldPos}, targetPos={vTargetPos}, deltaTime={dwDeltaTime}, speed={m_fSpeed}, distance={fDist}, flyDist={fFlyDist}");
if (fFlyDist >= fDist)
{
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.TickMove: Target HIT! (flyDist={fFlyDist} >= distance={fDist})");
return true; // target hit / 命中目标
}
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
BMLogger.LogError($"[SKILL_GFX_DEBUG] CGfxLinearMove.TickMove: Moved - newPos={m_vPos}, moveDir={m_vMoveDir}, distanceRemaining={fDist - fFlyDist}");
return false;
}
}
File diff suppressed because one or more lines are too long
+1 -5
View File
@@ -60,8 +60,4 @@ QualitySettings:
excludedTargetPlatforms:
- Standalone
m_TextureMipmapLimitGroupNames: []
m_PerPlatformDefaultQuality:
Android: 0
Standalone: 0
VisionOS: 0
iPhone: 0
m_PerPlatformDefaultQuality: {}