v1.2.2.b6

Performance & stability pass (issue #34 + audit)

General:
- DisableAll resets only skills active in the previous round instead of all loaded skills (~89ms -> ~5ms)
- Removed per-tick LINQ/allocations, O(1) HUD skill lookups, translation substitutions moved to load time
- ForceFullUpdate creates the network service once per broadcast; view-angle guard only skips a genuine (0,0,0) placeholder
- Debug hooks/logging only active when DebugMode is enabled (single reused writer)
- Player skill state unified to a single collection (fixes a rare disconnect race)
- Gamedata signatures resolved lazily/isolated; hardened tick/transmit paths against invalid handles
- Description HUD honours always-show / -1 duration consistently across all assignment paths

Skills:
- Jester: fixed the no-damage effect leaking after a mid-round skill change (Thief/Deactivator)
- Jackal: fixed trail entities accumulating over a round
- Falcon Eye / Third Eye / Spectator: camera state cleared on disable

Also: added missing translation keys as untranslated markers across language files.

Fixes #34
This commit is contained in:
ByDexter 2026-07-05 21:48:29 +03:00
parent a0f37ba1a1
commit 25cd8eb989
31 changed files with 202 additions and 79 deletions

View file

@ -57,7 +57,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As<CCSPlayerController>();
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
decoys.TryAdd(decoy.Index, 0);

View file

@ -22,7 +22,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill == skillName)
GiveBunnyHop(player);
}

View file

@ -77,7 +77,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo == null) continue;
var pawn = player.PlayerPawn.Value;

View file

@ -53,6 +53,7 @@ namespace src.player.skills
if (player == null) return;
ChangeCamera(player, true);
EntityManager.DestroyPlayerEntities(player.Index);
cameras.TryRemove(player.Index, out _);
}
public static void OnTick()

View file

@ -42,7 +42,7 @@ namespace src.player.skills
foreach (var (info, player) in infoList)
{
if (player == null) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
var observedPlayer = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == player?.Pawn?.Value?.ObserverServices?.ObserverTarget?.Value?.Handle);
var observerInfo = observedPlayer != null ? PlayerManager.GetPlayerByIndex(observedPlayer.Index) : null;

View file

@ -28,7 +28,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As<CCSPlayerController>();
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
grenade.Bounces = 555;
}

View file

@ -60,7 +60,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As<CCSPlayerController>();
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
Server.NextFrame(() =>

View file

@ -127,7 +127,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As<CCSPlayerController>();
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
Vector pos = new(grenade.AbsOrigin?.X, grenade.AbsOrigin?.Y, grenade.AbsOrigin?.Z);

View file

@ -46,7 +46,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
bool isJackalOwner = playerInfo?.Skill == skillName;
if (!isJackalOwner)
@ -90,6 +90,11 @@ namespace src.player.skills
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null
|| playerPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || playerPawn.Health <= 0) return; ;
foreach (var old in EntityManager.GetPlayerEntities(player.Index, "prop_physics_multiplayer"))
EntityManager.DestroyEntity(old);
foreach (var old in EntityManager.GetPlayerEntities(player.Index, "particle_system"))
EntityManager.DestroyEntity(old);
var relay = EntityManager.CreateTrackedPhysicsProp(player.Index);
if (relay == null || !relay.IsValid) return;

View file

@ -53,16 +53,18 @@ namespace src.player.skills
var jester = GetJesterInfo(player.Index);
if (jester == null) return;
jester.Active = false;
if (jester.Timer != null)
{
jester.Timer.Kill();
jester.Timer = null;
}
Server.NextWorldUpdate(() =>
{
if (jester.Timer != null)
{
jester.Timer?.Kill();
jester.Timer = null;
}
SkillUtils.ResetPrintHTML(player);
SetPlayerColor(player, true);
jesters.TryRemove(player.Index, out _);
});
}

View file

@ -88,7 +88,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As<CCSPlayerController>();
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
nades.TryAdd(grenade.Index, 0);

View file

@ -39,7 +39,7 @@ namespace src.player.skills
var player = @event.Userid;
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
UpdateNinja(player);
@ -50,7 +50,7 @@ namespace src.player.skills
var player = @event.Userid;
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
UpdateNinja(player);
@ -118,7 +118,7 @@ namespace src.player.skills
UpdateNinja(PlayerManager.GetPlayerFromEvent(player));
var props = EntityManager.GetPlayerEntities(PlayerManager.GetPlayerEvent(player)!.Index, "empty_prop");
var props = EntityManager.GetPlayerEntities((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index), "empty_prop");
if (props.Count == 0) continue;
var prop = Utilities.GetEntityFromIndex<CDynamicProp>((int)props[0]);

View file

@ -27,7 +27,7 @@ namespace src.player.skills
var player = Utilities.GetPlayers().FirstOrDefault(p => p.Pawn?.Value != null && p.Pawn.Value.IsValid && p.Pawn.Value.Index == userIndex);
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
um.Recipients.Clear();

View file

@ -51,6 +51,7 @@ namespace src.player.skills
if (player == null) return;
ChangeCamera(player, true);
EntityManager.DestroyPlayerEntities(player.Index);
cameras.TryRemove(player.Index, out _);
}
public static void OnTick()

View file

@ -39,6 +39,7 @@ namespace src.player.skills
if (player == null) return;
ChangeCamera(player, true);
EntityManager.DestroyPlayerEntities(player.Index);
cameras.TryRemove(player.Index, out _);
}
public static void OnTick()

View file

@ -388,7 +388,7 @@ namespace src.player.skills
continue;
}
bool isOwner = knifeOwnerIndex == PlayerManager.GetPlayerEvent(player)!.Index;
bool isOwner = knifeOwnerIndex == (PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index);
bool isObservingOwner = observedPlayerIndex.HasValue && observedPlayerIndex.Value == knifeOwnerIndex;
if (!isOwner && !isObservingOwner)

View file

@ -60,7 +60,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As<CCSPlayerController>();
if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
Server.NextFrame(() =>

View file

@ -32,7 +32,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
var observedPlayer = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == player?.Pawn?.Value?.ObserverServices?.ObserverTarget?.Value?.Handle);
var observerInfo = observedPlayer != null ? PlayerManager.GetPlayerByIndex(observedPlayer.Index) : null;

View file

@ -39,7 +39,7 @@ namespace src.player.skills
if (pawn == null || !pawn.IsValid || pawn.Controller == null || !pawn.Controller.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As<CCSPlayerController>();
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName || Instance.GameRules == null) return;
var roundTime = SkillsInfo.GetValue<int>(skillName, "changeRoundTime");

View file

@ -62,7 +62,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As<CCSPlayerController>();
var playerInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill != skillName) return;
Server.NextFrame(() => {