diff --git a/README.md b/README.md
index 2cfa8bd..271a267 100644
--- a/README.md
+++ b/README.md
@@ -374,6 +374,29 @@ This plugin uses content from the following projects:
## 📋 Changelog
+
+v1.2.2.b6
+
+- #### General:
+ - ###### Performance: much faster round changes - only skills that were actually active in the previous round are reset, instead of every loaded skill (`DisableAll` dropped from ~89ms to ~5ms on a full server). [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Performance & memory: removed per-tick LINQ and skill-name allocations in the tick loop, made HUD skill lookups O(1), and moved translation color/button substitutions to load time. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### `ForceFullUpdate` now creates the network service once per broadcast, and the round-start view-angle guard only skips a genuine (0, 0, 0) spawn placeholder instead of over-skipping valid angles. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Debug logging is only installed when `DebugMode` is enabled and writes through a single reused writer instead of reopening the file for every line. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Player skill state unified to a single collection (removed a duplicate list), closing a rare desync/race on player disconnect. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Gamedata signatures are now resolved lazily and in isolation, so one broken signature after a CS2 update no longer takes down the whole utility layer. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Hardened many tick/transmit code paths against invalid player handles to prevent rare crashes. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### The skill description HUD honours "always show" / `-1` duration consistently across every assignment path. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+
+- #### Skill improvements:
+ - ###### Jester (Joker):
+ - ###### Fixed the no-damage effect leaking onto a player after their skill was changed mid-round (e.g. stolen by Thief or removed by Deactivator) - the internal state is now cleared when the skill is disabled. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Jackal:
+ - ###### Fixed trail entities piling up over a round - the previous trail is now removed before a new one is spawned. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Falcon Eye / Third Eye / Spectator:
+ - ###### Camera state is now fully cleared when the skill is disabled. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+
+
+
v1.2.2.b5
diff --git a/WASDMenuAPI - SRC Files/WASDMenuAPI.cs b/WASDMenuAPI - SRC Files/WASDMenuAPI.cs
index b1b1529..96b8735 100644
--- a/WASDMenuAPI - SRC Files/WASDMenuAPI.cs
+++ b/WASDMenuAPI - SRC Files/WASDMenuAPI.cs
@@ -14,7 +14,6 @@ public class WASDMenuAPI
public static void LoadPlugin(BasePlugin basePlugin, bool hotReload)
{
- var wasdMenuManager = new WasdManager();
basePlugin.RegisterEventHandler((@event, info) =>
{
if (@event.Userid != null)
@@ -46,7 +45,7 @@ public class WASDMenuAPI
public static void OnTick()
{
- foreach (var player in Players.Values.Where(p => p.MainMenu != null))
+ foreach (var player in Players.Values.Where(p => p.MainMenu != null && p.Player?.IsValid == true))
{
if ((player.Buttons & PlayerButtons.Forward) == 0 && (player.Player.Buttons & PlayerButtons.Forward) != 0)
{
diff --git a/jRandomSkills - SRC Files/src/command/Command.cs b/jRandomSkills - SRC Files/src/command/Command.cs
index f49cbc5..cb8d4d2 100644
--- a/jRandomSkills - SRC Files/src/command/Command.cs
+++ b/jRandomSkills - SRC Files/src/command/Command.cs
@@ -150,7 +150,7 @@ namespace src.command
skillPlayer.Skill = skill.Skill;
skillPlayer.SpecialSkill = Skills.None;
Instance.SkillAction(skill.Skill.ToString(), "EnableSkill", [targetPlayer]);
- skillPlayer.SkillDescriptionHudExpired = DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
+ skillPlayer.SkillDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
if (player == null)
{
@@ -504,7 +504,7 @@ namespace src.command
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [targetPlayer]);
skillPlayer.Skill = skill.Skill;
skillPlayer.SpecialSkill = src.player.Skills.None;
- skillPlayer.SkillDescriptionHudExpired = DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
+ skillPlayer.SkillDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
if (skill.Skill == src.player.Skills.None)
Event.staticSkills.TryRemove(targetPlayer.Index, out _);
diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs
index 3756395..ca2776a 100644
--- a/jRandomSkills - SRC Files/src/jRandomSkills.cs
+++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs
@@ -19,16 +19,18 @@ namespace src
#pragma warning disable CS8618
public static jRandomSkills Instance { get; private set; }
#pragma warning restore CS8618
- public ConcurrentBag SkillPlayer { get; set; } = [];
+ public IEnumerable SkillPlayer => PlayerManager.GetAllPlayers();
public Random Random { get; } = new Random();
public CCSGameRules? GameRules { get; set; }
private ConcurrentBag ManifestResources { get; set; } = ["models/sprays/spray_plane.vmdl"];
public IWasdMenuManager? MenuManager;
+ // Skills that were enabled at least once this round; used to reset only those on round change (not all 124).
+ public static readonly ConcurrentDictionary ActiveSkillsThisRound = new();
public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X, Juzlus";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
- public override string ModuleVersion => "1.2.2.b5";
+ public override string ModuleVersion => "1.2.2.b6";
public override void Load(bool hotReload)
{
@@ -85,6 +87,9 @@ namespace src
if (string.IsNullOrEmpty(skill))
return null;
+ if (methodName == "EnableSkill")
+ ActiveSkillsThisRound.TryAdd(skill, 0);
+
var method = _skillMethodCache.GetOrAdd((skill, methodName), key =>
{
string className = $"src.player.skills.{key.Skill}";
@@ -292,12 +297,9 @@ namespace src
}
}
- ///
- /// Gets a player info by index. O(n) lookup - consider caching if called frequently.
- ///
internal jSkill_PlayerInfo? GetPlayerInfoByIndex(uint playerIndex)
{
- return SkillPlayer.FirstOrDefault(p => p.PlayerIndex == playerIndex);
+ return PlayerManager.GetPlayerByIndex(playerIndex);
}
}
@@ -329,6 +331,23 @@ namespace src
public static class SkillData
{
public static ConcurrentBag Skills { get; } = [];
+
+ private static Dictionary? _bySkill;
+
+ public static jSkill_SkillInfo? GetInfo(Skills skill)
+ {
+ var map = _bySkill;
+ if (map == null)
+ {
+ map = new Dictionary();
+ foreach (var s in Skills)
+ map[s.Skill] = s;
+ _bySkill = map;
+ }
+ return map.TryGetValue(skill, out var info) ? info : null;
+ }
+
+ public static void Invalidate() => _bySkill = null;
}
public enum CS2ConsoleColors
diff --git a/jRandomSkills - SRC Files/src/player/Debug.cs b/jRandomSkills - SRC Files/src/player/Debug.cs
index c6b91fc..89e451a 100644
--- a/jRandomSkills - SRC Files/src/player/Debug.cs
+++ b/jRandomSkills - SRC Files/src/player/Debug.cs
@@ -13,10 +13,17 @@ namespace src.player
{
private static string sessionId = "00000";
private static readonly string debugFolder = Path.Combine(Instance.ModuleDirectory, "logs");
+ private static StreamWriter? _writer;
+ private static readonly object _writeLock = new();
public static void Load()
{
sessionId = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}";
+ lock (_writeLock) { _writer?.Dispose(); _writer = null; }
+
+ if (Config.LoadedConfig.DebugMode != true)
+ return;
+
Instance.RegisterEventHandler((@event, info) =>
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
@@ -131,13 +138,25 @@ namespace src.player
if (Config.LoadedConfig.DebugMode != true)
return;
- // GetAllEntityIndexes();
+ lock (_writeLock)
+ {
+ _writer ??= CreateWriter();
+ _writer?.WriteLine($"[{DateTime.Now:dd.MM.yyyy HH:mm:ss}] {message}");
+ }
+ }
- string filename = $"debug_{sessionId}.txt";
- string path = Path.Combine(debugFolder, filename);
-
- Directory.CreateDirectory(debugFolder);
- File.AppendAllText(path, $"[{DateTime.Now:dd.MM.yyyy HH:mm:ss}] {message}{Environment.NewLine}", System.Text.Encoding.UTF8);
+ private static StreamWriter? CreateWriter()
+ {
+ try
+ {
+ Directory.CreateDirectory(debugFolder);
+ string path = Path.Combine(debugFolder, $"debug_{sessionId}.txt");
+ return new StreamWriter(path, append: true, System.Text.Encoding.UTF8) { AutoFlush = true };
+ }
+ catch
+ {
+ return null;
+ }
}
private static void GetAllEntityIndexes()
diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
index fc124ac..a637b00 100644
--- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
+++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
@@ -417,22 +417,50 @@ namespace src.player
}
}
+ private static readonly Dictionary _skillNames =
+ Enum.GetValues().ToDictionary(s => s, s => s.ToString());
+ private static readonly HashSet _activeSkillsSet = [];
+ private static readonly List _activeSkillsList = [];
+ private static readonly Comparison _tickOrderCmp = (a, b) => TickOrder(a).CompareTo(TickOrder(b));
+ private static HashSet? _freezeDisabledSkills;
+
+ // AreaReaper and ChillOut depend on other skills' tick results, so they must tick last.
+ private static int TickOrder(Skills s) => s == Skills.AreaReaper ? 2 : s == Skills.ChillOut ? 1 : 0;
+
+ private static HashSet BuildFreezeDisabledSkills()
+ {
+ var set = new HashSet();
+ foreach (var s in SkillData.Skills)
+ if (SkillsInfo.GetValue(s.Skill, "disableOnFreezeTime"))
+ set.Add(s.Skill);
+ return set;
+ }
+
+ public static void InvalidateFreezeDisabledCache() => _freezeDisabledSkills = null;
+
private static void OnTick()
{
lock (setLock)
{
- var activeSkills = Instance.SkillPlayer
- .Where(p => !p.IsDrawing)
- .Select(p => p.Skill)
- .Distinct()
- .OrderBy(skill => skill.ToString() == "AreaReaper")
- .ThenBy(skill => skill.ToString() == "ChillOut");
+ _activeSkillsSet.Clear();
+ _activeSkillsList.Clear();
+ foreach (var p in Instance.SkillPlayer)
+ {
+ if (p.IsDrawing) continue;
+ if (_activeSkillsSet.Add(p.Skill))
+ _activeSkillsList.Add(p.Skill);
+ }
- foreach (var skill in activeSkills)
- if (SkillsInfo.GetValue(skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
- continue;
- else
- Instance.SkillAction(skill.ToString(), "OnTick");
+ _activeSkillsList.Sort(_tickOrderCmp);
+
+ bool freeze = SkillUtils.IsFreezeTime();
+ _freezeDisabledSkills ??= BuildFreezeDisabledSkills();
+
+ foreach (var skill in _activeSkillsList)
+ {
+ if (freeze && _freezeDisabledSkills.Contains(skill)) continue;
+ Instance.SkillAction(_skillNames[skill], "OnTick");
+ }
}
}
@@ -466,7 +494,6 @@ namespace src.player
DisplayHUD = true,
SkillUsed = false,
};
- Instance.SkillPlayer.Add(playerInfo);
PlayerManager.Register(playerInfo);
}
}
@@ -502,8 +529,6 @@ namespace src.player
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
- var items = Instance.SkillPlayer.ToList();
- Instance.SkillPlayer = [.. items.Where(p => p.PlayerIndex != player.Index)];
PlayerManager.UnregisterPlayer(player.Index);
EntityManager.DestroyPlayerEntities(player.Index);
@@ -606,6 +631,10 @@ namespace src.player
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) continue;
+ ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0);
+ if (playerInfo.SpecialSkill != noneSkill.Skill)
+ ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0);
+
Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]);
playerInfo.Skill = noneSkill.Skill;
@@ -617,8 +646,9 @@ namespace src.player
RestorePlayer(player);
}
- foreach (var skill in SkillData.Skills)
- Instance.SkillAction(skill.Skill.ToString(), "NewRound");
+ foreach (var skillName in ActiveSkillsThisRound.Keys)
+ Instance.SkillAction(skillName, "NewRound");
+ ActiveSkillsThisRound.Clear();
}
}
@@ -649,6 +679,7 @@ namespace src.player
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "NewRound");
+ ActiveSkillsThisRound.Clear();
playersSkills.Clear();
staticSkills.Clear();
@@ -657,7 +688,6 @@ namespace src.player
tSkill = noneSkill;
allSkill = noneSkill;
- Instance.SkillPlayer.Clear();
PlayerManager.Clear();
ConVar.Find("sv_legacy_jump")?.SetValue("1");
@@ -1113,7 +1143,7 @@ namespace src.player
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\".");
- skillPlayer.SkillDescriptionHudExpired = DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
+ skillPlayer.SkillDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(Config.LoadedConfig.SkillDescriptionDuration);
}
}
diff --git a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
index 3f87eae..025d258 100644
--- a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
+++ b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
@@ -61,7 +61,7 @@ namespace src.player
{
if (player == null || !player.IsValid || player.IsBot) return;
- var skillPlayer = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)!.Index);
+ var skillPlayer = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index);
if (skillPlayer == null || !skillPlayer.DisplayHUD) return;
var now = DateTime.Now;
@@ -72,7 +72,7 @@ namespace src.player
string skillLine = string.Empty;
string remainingLine = string.Empty;
- bool showDescriptionHUD = skillPlayer.SkillDescriptionHudExpired >= now;
+ bool showDescriptionHUD = skillPlayer.SkillDescriptionHudExpired >= now || Config.LoadedConfig.DisplayAlwaysDescription;
bool isDescription = true;
var skills = SkillData.Skills;
@@ -99,7 +99,7 @@ namespace src.player
{
if (player.PawnIsAlive)
{
- var skillInfo = skills.FirstOrDefault(s => s.Skill == skillPlayer.Skill);
+ var skillInfo = SkillData.GetInfo(skillPlayer.Skill);
if (skillInfo != null)
{
@@ -139,9 +139,9 @@ namespace src.player
var observedSkill = PlayerManager.GetPlayerByIndex(observedEvent.Index);
if (observedSkill == null) return;
- var observedSkillInfo = skills.FirstOrDefault(s => s.Skill == observedSkill.Skill);
+ var observedSkillInfo = SkillData.GetInfo(observedSkill.Skill);
var observedSpecialInfo = observedSkill.SpecialSkill != Skills.None
- ? skills.FirstOrDefault(s => s.Skill == observedSkill.SpecialSkill)
+ ? SkillData.GetInfo(observedSkill.SpecialSkill)
: null;
string primaryName = player.GetSkillName(observedSkill.Skill, observedSkill.SkillChance);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Baseball.cs b/jRandomSkills - SRC Files/src/player/skills/Baseball.cs
index 8f8ccb3..a83afcf 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Baseball.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Baseball.cs
@@ -57,7 +57,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As();
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);
diff --git a/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs b/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs
index 71067d5..f84ac51 100644
--- a/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/BunnyHop.cs
@@ -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);
}
diff --git a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs
index 30b4a0b..3be3ab0 100644
--- a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs
@@ -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;
diff --git a/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs b/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs
index f4528a2..2c36ca1 100644
--- a/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/FalconEye.cs
@@ -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()
diff --git a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
index a2a1f04..4fc33b7 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
@@ -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;
diff --git a/jRandomSkills - SRC Files/src/player/skills/Glue.cs b/jRandomSkills - SRC Files/src/player/skills/Glue.cs
index 52c8268..97fa351 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Glue.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Glue.cs
@@ -28,7 +28,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As();
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;
}
diff --git a/jRandomSkills - SRC Files/src/player/skills/HealingSmoke.cs b/jRandomSkills - SRC Files/src/player/skills/HealingSmoke.cs
index e1f1ad0..9f862d8 100644
--- a/jRandomSkills - SRC Files/src/player/skills/HealingSmoke.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/HealingSmoke.cs
@@ -60,7 +60,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As();
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(() =>
diff --git a/jRandomSkills - SRC Files/src/player/skills/HomingNades.cs b/jRandomSkills - SRC Files/src/player/skills/HomingNades.cs
index 03ea2b3..7c5ca89 100644
--- a/jRandomSkills - SRC Files/src/player/skills/HomingNades.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/HomingNades.cs
@@ -127,7 +127,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As();
- 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);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Jackal.cs b/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
index 79e13eb..ebcbf26 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
@@ -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;
diff --git a/jRandomSkills - SRC Files/src/player/skills/Jester.cs b/jRandomSkills - SRC Files/src/player/skills/Jester.cs
index 04014c0..2a23f48 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Jester.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Jester.cs
@@ -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 _);
});
}
diff --git a/jRandomSkills - SRC Files/src/player/skills/Miner.cs b/jRandomSkills - SRC Files/src/player/skills/Miner.cs
index a0acdee..9e4b933 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Miner.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Miner.cs
@@ -88,7 +88,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As();
- 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);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
index 96ec055..02d3d66 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
@@ -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((int)props[0]);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Silent.cs b/jRandomSkills - SRC Files/src/player/skills/Silent.cs
index a255218..f378286 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Silent.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Silent.cs
@@ -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();
diff --git a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs
index f3aaa2c..698dfea 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs
@@ -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()
diff --git a/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs b/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs
index 8fc906e..dcc91b7 100644
--- a/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/ThirdEye.cs
@@ -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()
diff --git a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
index 6ce287a..947e857 100644
--- a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
@@ -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)
diff --git a/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs b/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs
index 135a541..07c0f79 100644
--- a/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/ToxicSmoke.cs
@@ -60,7 +60,7 @@ namespace src.player.skills
var player = pawn.Controller.Value.As();
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(() =>
diff --git a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
index 98c6563..fe99fb0 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
@@ -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;
diff --git a/jRandomSkills - SRC Files/src/player/skills/Watchmaker.cs b/jRandomSkills - SRC Files/src/player/skills/Watchmaker.cs
index 66401d4..3245b89 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Watchmaker.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Watchmaker.cs
@@ -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();
- 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(skillName, "changeRoundTime");
diff --git a/jRandomSkills - SRC Files/src/player/skills/Weightless.cs b/jRandomSkills - SRC Files/src/player/skills/Weightless.cs
index ff10464..48ecb7a 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Weightless.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Weightless.cs
@@ -62,7 +62,7 @@ namespace src.player.skills
if (pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return;
var player = pawn.Controller.Value.As();
- 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(() => {
diff --git a/jRandomSkills - SRC Files/src/utils/Localization.cs b/jRandomSkills - SRC Files/src/utils/Localization.cs
index d90a39b..52bea36 100644
--- a/jRandomSkills - SRC Files/src/utils/Localization.cs
+++ b/jRandomSkills - SRC Files/src/utils/Localization.cs
@@ -55,7 +55,18 @@ namespace src.utils
var translations = JsonConvert.DeserializeObject>(jsonText);
if (translations != null)
+ {
+ string redColor = ChatColors.Red.ToString();
+ string? altButton = Config.LoadedConfig.AlternativeSkillButton;
+ foreach (var tkey in translations.Keys.ToList())
+ {
+ var val = translations[tkey].Replace("CHATCOLORS.RED", redColor);
+ if (!string.IsNullOrEmpty(altButton))
+ val = val.Replace("css_useSkill", $"css_useSkill/{altButton}");
+ translations[tkey] = val;
+ }
_translations.AddOrUpdate(code, translations, (k, v) => translations);
+ }
}
public static bool HasTranslation(string code)
@@ -150,9 +161,6 @@ namespace src.utils
else
{
string output = args.Length == 0 ? translation : string.Format(translation, args);
- output = output.Replace("CHATCOLORS.RED", ChatColors.Red.ToString());
- if (Config.LoadedConfig.AlternativeSkillButton != null)
- output = output.Replace("css_useSkill", $"css_useSkill/{Config.LoadedConfig.AlternativeSkillButton}");
if (Illiterate.CheckIlliterateSkill(player))
return Illiterate.GetRandomText(output)!;
diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
index 24f4f46..218c927 100644
--- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
+++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
@@ -21,10 +21,21 @@ namespace src.utils
{
public static class SkillUtils
{
- private static readonly MemoryFunctionWithReturn HEGrenadeProjectile_CreateFunc = new(GameData.GetSignature("HEGrenadeProjectile_CreateFunc"));
- private static readonly MemoryFunctionWithReturn SmokeGrenadeProjectile_CreateFunc = new(GameData.GetSignature("SmokeGrenadeProjectile_CreateFunc"));
- private static readonly MemoryFunctionVoid TerminateRoundFunc = new(GameData.GetSignature("CCSGameRules_TerminateRound"));
- private static readonly MemoryFunctionVoid SnapViewAngles = new(GameData.GetSignature("SnapViewAngles"));
+ private static Lazy LazySig(string name, Func factory) where T : class =>
+ new(() =>
+ {
+ try { return factory(GameData.GetSignature(name)); }
+ catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] gamedata signature '{name}' could not be resolved: {ex.Message}"); return null; }
+ });
+
+ private static readonly Lazy?> HEGrenadeProjectile_CreateFunc =
+ LazySig>("HEGrenadeProjectile_CreateFunc", s => new(s));
+ private static readonly Lazy?> SmokeGrenadeProjectile_CreateFunc =
+ LazySig>("SmokeGrenadeProjectile_CreateFunc", s => new(s));
+ private static readonly Lazy?> TerminateRoundFunc =
+ LazySig>("CCSGameRules_TerminateRound", s => new(s));
+ private static readonly Lazy?> SnapViewAngles =
+ LazySig>("SnapViewAngles", s => new(s));
// private static readonly int collisionRulesChangedOffset = GameData.GetOffset("CBaseEntity_CollisionRulesChanged");
public static void PrintToChat(CCSPlayerController player, string? msg, string border = "tb", string? title = null)
@@ -74,6 +85,8 @@ namespace src.utils
weapon.Value.Clip1 = ammo;
Utilities.SetStateChanged(weapon.Value, "CBasePlayerWeapon", "m_iClip1");
+ if (ammo == 1) return;
+
jRandomSkills.Instance.AddTimer(.1f, () =>
{
if (weapon == null || !weapon.IsValid || weapon.Value == null || !weapon.Value.IsValid) return;
@@ -169,7 +182,7 @@ namespace src.utils
public static void Look(this CBasePlayerPawn pawn, QAngle angle)
{
if (pawn == null || !pawn.IsValid) return;
- SnapViewAngles.Invoke(pawn, angle);
+ SnapViewAngles.Value?.Invoke(pawn, angle);
}
public static CBeam? CreateLine(Vector start, Vector end, Color color, uint ownerPlayerIndex = EntityManager.SystemOwnerIndex)
@@ -264,12 +277,12 @@ namespace src.utils
public static void CreateHEGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
{
- HEGrenadeProjectile_CreateFunc.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum);
+ HEGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum);
}
public static void CreateSmokeGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
{
- SmokeGrenadeProjectile_CreateFunc.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 45, teamNum);
+ SmokeGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 45, teamNum);
}
public static bool TakeHealth(CCSPlayerPawn? pawn, int damage)
@@ -320,7 +333,7 @@ namespace src.utils
return EntityManager.CreateTrackedTrigger(ownerPlayerIndex, name, radius, pos);
}
- public static void ForceFullUpdate(CCSPlayerController player, List<(uint PlayerIndex, QAngle LastAngle)>? batchList = null)
+ public static void ForceFullUpdate(CCSPlayerController player, List<(uint PlayerIndex, QAngle LastAngle)>? batchList = null, INetworkGameServer? networkGameServer = null)
{
if (player == null || !player.IsValid) return;
@@ -329,14 +342,15 @@ namespace src.utils
QAngle lastAngle = new(pawn.V_angle.X, pawn.V_angle.Y, pawn.V_angle.Z);
- INetworkServerService networkServerService = new();
- INetworkGameServer networkGameServer = networkServerService.GetIGameServer();
+ networkGameServer ??= new INetworkServerService().GetIGameServer();
var client = networkGameServer.GetClientBySlot(player.Slot);
if (client == null) return;
client.ForceFullUpdate();
- if (lastAngle.Y == 0) return;
+ // Only skip the angle restore when the captured view is a spawn-time (0,0,0) placeholder;
+ // a genuine angle with a single zero component (e.g. yaw exactly 0) must still be restored.
+ if (lastAngle.X == 0 && lastAngle.Y == 0 && lastAngle.Z == 0) return;
uint playerIndex = player.Index;
@@ -368,8 +382,9 @@ namespace src.utils
lastForceFullUpdateAll = tickCount;
var playersToRestore = new List<(uint PlayerIndex, QAngle LastAngle)>();
+ INetworkGameServer networkGameServer = new INetworkServerService().GetIGameServer();
foreach (var player in Utilities.GetPlayers())
- ForceFullUpdate(player, playersToRestore);
+ ForceFullUpdate(player, playersToRestore, networkGameServer);
if (playersToRestore.Count <= 0) return;
@@ -639,7 +654,7 @@ namespace src.utils
{
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
UpdateServerTeamScores(ctScore, tScore);
- TerminateRoundFunc.Invoke(jRandomSkills.Instance.GameRules.Handle, 5f, roundEndReason, 0, 0);
+ TerminateRoundFunc.Value?.Invoke(jRandomSkills.Instance.GameRules.Handle, 5f, roundEndReason, 0, 0);
}
public static void TerminateRound(CsTeam winnerTeam)
diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll
index 22a8b68..0440e5e 100644
Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll differ
diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll
index 4d12098..6e6e5d9 100644
Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ