v1.2.3.b7
## Shared Systems * **DecoyTracker** - owner-aware decoy tracking shared by FrozenDecoy, GravityDecoy and MagneticDecoy, replacing the per-skill position dictionaries. * **DebugCategory** - new flag enum backing the numeric `DebugMode`, with automatic migration from the old boolean value. * **SkillsInfo.GetSkillConfig** - indexed skill config lookup replacing the linear scan used by draws and HUD rendering. * **SkillNames** - cached enum name table, removing repeated `ToString()` calls from the draw path. * **SkillUtils.HideCarriedEntities** - shared carried-weapon resolution for C4Camouflage, Ghost and Ninja. ## Performance * **Skill draw** - replaced the O(n²) config lookup; rounds exceeding the perf threshold dropped from 89% to 0.3%. * **CheckTransmit** - the weapon chain is now resolved once per hidden pawn instead of once per receiver; Ghost cost down 61%. * **Pilot** - the exhaust trail is paused and resumed instead of being destroyed and recreated on every jump release. * **MagneticDecoy** - config reads and the pawn list are hoisted out of the per-decoy loop. * **FrozenDecoy** - config reads are hoisted out of the per-tick loop. * **Debug** - the damage hook is only installed when damage logging is enabled. ## Fixes * **PsychicDefusing** - can no longer finish a defuse and award an extra round after the round has already ended. * **Spectator** - now picks a different enemy on each activation and no longer creates an unused camera prop every time the skill is switched off. * **Random** - replaced the shared `Random` instance with `Random.Shared`, preventing degenerate results from concurrent use. * **Baseball** - decoys are removed when their owner dies or loses the skill. * **FrozenDecoy, GravityDecoy, MagneticDecoy** - decoys and their ground rings are removed when the skill is taken away. * **ThirdEye** - fixed the camera check dereferencing a missing `CameraServices`, and the door toggle now verifies the entity class before casting. * **Round start** - fixed `[gamerules unavailable]` appearing on the first freeze-time end after every map change. * **Config** - added migration for keys missing from existing configuration files. ## Menu * **WASDMenuAPI** - fixed a malformed `<font>` tag that wrapped every non-hovered menu row in an unclosed attribute. * **WSAD menu** - the controls line is no longer dropped when the header line is hidden. * **HtmlHudCustomisation** - line size options left empty now hide their line instead of emitting an invalid font class. ## Debug * **DebugMode** - changed from a boolean to a number with concatenated category digits: `1` Skill, `2` Round, `3` Entity, `4` Damage. `123` enables Skill, Round and Entity. `0` disables logging. Existing `true` values migrate automatically. * **EntityManager** - entity creation, destruction and failures are now logged under the Entity category instead of the server console. * Startup now reports which debug categories are active.
This commit is contained in:
parent
5c65f4ac71
commit
0033d1e13d
29 changed files with 634 additions and 251 deletions
|
|
@ -15,93 +15,105 @@ namespace src.player
|
|||
private static readonly string debugFolder = Path.Combine(Instance.ModuleDirectory, "logs");
|
||||
private static StreamWriter? _writer;
|
||||
private static readonly object _writeLock = new();
|
||||
private static bool damageHooked;
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
sessionId = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}";
|
||||
lock (_writeLock) { _writer?.Dispose(); _writer = null; }
|
||||
|
||||
if (Config.LoadedConfig.DebugMode != true)
|
||||
if (Config.DebugFlags == DebugCategory.None)
|
||||
return;
|
||||
|
||||
Instance.RegisterEventHandler<EventPlayerConnectFull>((@event, info) =>
|
||||
if (Config.DebugEnabled(DebugCategory.Round))
|
||||
{
|
||||
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} joined the game.");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventPlayerDisconnect>((@event, info) =>
|
||||
{
|
||||
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} disconnected.");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventRoundStart>((@event, info) =>
|
||||
{
|
||||
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
|
||||
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
|
||||
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
|
||||
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score + 1} (CT {ctTeam?.Score} : {tTeam?.Score} TT) started.{WarmupTag()}");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventRoundFreezeEnd>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Freeze time ended.{WarmupTag()}");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventRoundEnd>((@event, info) =>
|
||||
{
|
||||
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
|
||||
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
|
||||
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
|
||||
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score} (CT {ctTeam?.Score} : {tTeam?.Score} TT) ended.{WarmupTag()}");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventPlayerDeath>((@event, info) =>
|
||||
{
|
||||
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
|
||||
if (victim != null)
|
||||
Instance.RegisterEventHandler<EventPlayerConnectFull>((@event, info) =>
|
||||
{
|
||||
if (attacker != null)
|
||||
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} died from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}.");
|
||||
else
|
||||
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} died.");
|
||||
}
|
||||
return HookResult.Continue;
|
||||
});
|
||||
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} joined the game.", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventBombPlanted>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Bomb planted.");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
Instance.RegisterEventHandler<EventPlayerDisconnect>((@event, info) =>
|
||||
{
|
||||
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} disconnected.", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventBombDefused>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Bomb defused.");
|
||||
return HookResult.Continue;
|
||||
});
|
||||
Instance.RegisterEventHandler<EventRoundStart>((@event, info) =>
|
||||
{
|
||||
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
|
||||
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
|
||||
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
|
||||
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score + 1} (CT {ctTeam?.Score} : {tTeam?.Score} TT) started.{WarmupTag()}", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventRoundFreezeEnd>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Freeze time ended.{WarmupTag()}", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventRoundEnd>((@event, info) =>
|
||||
{
|
||||
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager").Where(t => t != null).ToList();
|
||||
var tTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.Terrorist);
|
||||
var ctTeam = teams.FirstOrDefault(t => t.TeamNum == (int)CsTeam.CounterTerrorist);
|
||||
WriteToDebug($"Round #{tTeam?.Score + ctTeam?.Score} (CT {ctTeam?.Score} : {tTeam?.Score} TT) ended.{WarmupTag()}", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventPlayerDeath>((@event, info) =>
|
||||
{
|
||||
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
|
||||
if (victim != null)
|
||||
{
|
||||
if (attacker != null)
|
||||
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} died from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}.", DebugCategory.Round);
|
||||
else
|
||||
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} died.", DebugCategory.Round);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventBombPlanted>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Bomb planted.", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
Instance.RegisterEventHandler<EventBombDefused>((@event, info) =>
|
||||
{
|
||||
WriteToDebug($"Bomb defused.", DebugCategory.Round);
|
||||
return HookResult.Continue;
|
||||
});
|
||||
}
|
||||
|
||||
Instance.RegisterListener<OnMapStart>((mapName) =>
|
||||
{
|
||||
WriteToDebug($"Map changed: {mapName}.");
|
||||
});
|
||||
|
||||
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
|
||||
if (Config.DebugEnabled(DebugCategory.Damage))
|
||||
{
|
||||
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
|
||||
damageHooked = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); }
|
||||
catch { }
|
||||
if (damageHooked)
|
||||
{
|
||||
try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); }
|
||||
catch { }
|
||||
damageHooked = false;
|
||||
}
|
||||
|
||||
lock (_writeLock) { _writer?.Dispose(); _writer = null; }
|
||||
}
|
||||
|
|
@ -133,20 +145,27 @@ namespace src.player
|
|||
|
||||
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}. " +
|
||||
$"[dmg={param2.Damage:0.#} hp={victimPawn.Health}/{victimPawn.MaxHealth} armor={victimPawn.ArmorValue} hitgroup={nativeHitGroup} " +
|
||||
$"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}]");
|
||||
$"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}]", DebugCategory.Damage);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private static string WarmupTag()
|
||||
{
|
||||
var gameRules = Instance?.GameRules;
|
||||
|
||||
if (gameRules == null || gameRules.Handle == IntPtr.Zero)
|
||||
{
|
||||
PlayerOnTick.InitializeGameRules();
|
||||
gameRules = Instance?.GameRules;
|
||||
}
|
||||
|
||||
if (gameRules == null || gameRules.Handle == IntPtr.Zero) return " [gamerules unavailable]";
|
||||
return gameRules.WarmupPeriod ? " [WARMUP]" : string.Empty;
|
||||
}
|
||||
|
||||
public static void WriteToDebug(string message)
|
||||
public static void WriteToDebug(string message, DebugCategory category = DebugCategory.Core)
|
||||
{
|
||||
if (Config.LoadedConfig.DebugMode != true)
|
||||
if (!Config.DebugEnabled(category))
|
||||
return;
|
||||
|
||||
lock (_writeLock)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue