diff --git a/.github/en.md b/.github/en.md
index 02744e9..c9f512a 100644
--- a/.github/en.md
+++ b/.github/en.md
@@ -358,6 +358,36 @@ This plugin uses content from the following projects:
## 馃搵 Changelog
+
+v1.2.2.b7
+
+- #### General:
+ - ###### Updated the gamedata signatures for the latest CS2 update (build 24134959) and bumped CounterStrikeSharp to 1.0.371. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Fixed a NullReferenceException thrown on weapon pickup when the weapon data pointed at freed schema memory. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### FullFoceUpdate has been changed to an optional setting in the configuration. Enabling it reduces the risk of PVS errors, but places a significant load on the server (disabled by default). [by: [@Juzlus](https://github.com/Juzlus)]
+
+- #### Skill improvements:
+ - ###### Ghost:
+ - ###### Can now plant the C4. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Free Planter / Short Bomb:
+ - ###### The on-screen bomb countdown now matches the configured detonation time (`mp_c4timer` is set at round start instead of only the blow time being overridden after the plant). [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Rich Boy:
+ - ###### The money bonus now respects `mp_maxmoney` instead of being capped at a hardcoded 16000. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Second Chance:
+ - ###### Now reliably respawns at base on a lethal hit, including fall damage (the revive moved to a pre-damage hook so the death is cancelled before it is committed). [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Shadow:
+ - ###### Fixed a false "no suitable area" when teleporting behind a target at close range. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Radar Hack:
+ - ###### Now reveals enemies only to the skill holder instead of the holder's whole team, and no longer reveals invisible or camouflaged enemies. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### C4 Camouflage:
+ - ###### The disguised bomb carrier no longer appears on the enemy radar. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Hot Bomb:
+ - ###### The "hot bomb" hint no longer shows in a round where nobody has the skill. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Jester:
+ - ###### Fixes crashes that occur when a skill is being disabled. [by: [@Juzlus](https://github.com/Juzlus)]
+
+
+
v1.2.2.b6
diff --git a/.github/pl.md b/.github/pl.md
index 2c8dfee..7f6ca06 100644
--- a/.github/pl.md
+++ b/.github/pl.md
@@ -356,6 +356,36 @@ Plugin korzysta z zawarto艣ci nast臋puj膮cych projekt贸w:
## 馃搵 Lista Zmian
+
+v1.2.2.b7
+
+- #### Og贸lne:
+ - ###### Zaktualizowano sygnatury gamedata do najnowszej aktualizacji CS2 (build 24134959) oraz zaktualizowano CounterStrikeSharp do wersji 1.0.371. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### FNaprawiono `NullReferenceException` wyst臋puj膮cy podczas podnoszenia broni, gdy dane broni wskazywa艂y na zwolnion膮 pami臋膰 schematu. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### `FullForceUpdate` zosta艂 zmieniony na opcjonalne ustawienie w konfiguracji. Jego w艂膮czenie zmniejsza ryzyko b艂臋d贸w PVS, ale powoduje znaczne obci膮偶enie serwera (domy艣lnie wy艂膮czone). [by: [@Juzlus](https://github.com/Juzlus)]
+
+- #### Poprawki mocy:
+ - ###### Duszek:
+ - ###### Teraz mo偶e podk艂ada膰 C4. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Samowolka / Kr贸tka Bomba:
+ - ###### Odliczanie bomby wy艣wietlane na ekranie jest teraz zgodne z ustawionym czasem detonacji (`mp_c4timer`). [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Bogacz:
+ - ###### Bonus za pieni膮dze teraz respektuje `mp_maxmoney` zamiast by膰 ograniczony na sztywno do warto艣ci 16000. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Druga Szansa:
+ - ###### Teraz niezawodnie odradza gracza w bazie po otrzymaniu 艣miertelnego obra偶enia, r贸wnie偶 od obra偶e艅 od upadku. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Cie艅:
+ - ###### Naprawiono fa艂szywy komunikat "brak miejsca" podczas teleportacji za cel z bliskiej odleg艂o艣ci. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Radarowiec:
+ - ###### Teraz ujawnia przeciwnik贸w tylko posiadaczowi mocy zamiast ca艂ej jego dru偶ynie oraz nie ujawnia ju偶 niewidzialnych ani zamaskowanych przeciwnik贸w. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### C4 Kamufla偶:
+ - ###### Niewidzialny gracz nios膮cy bombe nie pojawia si臋 ju偶 na radarze przeciwnik贸w. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### Gor膮ca bomba:
+ - ###### Podpowied藕 dotycz膮ca "gor膮cej bomby" nie pojawia si臋 ju偶 w rundach, w kt贸rych nikt nie posiada tej mocy. [by: [@ByDexterTR](https://github.com/ByDexterTR)]
+ - ###### B艂azen:
+ - ###### Naprawiono crashe wyst臋puj膮ce podczas wy艂膮czania mocy. [by: [@Juzlus](https://github.com/Juzlus)]
+
+
+
v1.2.2.b6
diff --git a/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json b/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
index b4a5330..3e0bc0e 100644
--- a/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
+++ b/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
@@ -26,5 +26,23 @@
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B DA 48 8B F1 48 8D 55",
"linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8"
}
+ },
+ "CServerSideClient_m_nDeltaTick": {
+ "offsets": {
+ "windows": 348,
+ "linux": 348
+ }
+ },
+ "INetworkGameServer_Slots": {
+ "offsets": {
+ "windows": 584,
+ "linux": 584
+ }
+ },
+ "INetworkServerService_GetIGameServer": {
+ "offsets": {
+ "windows": 23,
+ "linux": 24
+ }
}
}
diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs
index 5dd9c62..c6d5062 100644
--- a/jRandomSkills - SRC Files/src/jRandomSkills.cs
+++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs
@@ -28,7 +28,7 @@ namespace src
public static readonly ConcurrentDictionary ActiveSkillsThisRound = new();
public override string ModuleName => "[CS2] [ jRandomSkills ]";
- public override string ModuleAuthor => "D3X, Juzlus";
+ public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.2.b7";
diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
index 377474d..28cc95a 100644
--- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
+++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
@@ -1093,24 +1093,31 @@ namespace src.player
if (randomSkill.Skill == Skills.Illiterate)
Illiterate.Enable();
+ var playerIndex = player.Index;
Instance?.AddTimer(.2f, () =>
{
- if (player == null || !player.IsValid) return;
+ var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
+ if (playerTarget == null || !playerTarget.IsValid) return;
if (randomSkill.Display)
- SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}",
- border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t");
+ SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}",
+ border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t");
if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () =>
{
- if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return;
- Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
+ var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
+ if (playerTarget == null || !playerTarget.IsValid) return;
+
+ if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return;
+ Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill);
+ Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]);
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
else
{
- if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return;
- Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]);
+ if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return;
+ Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill);
+ Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]);
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
diff --git a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs
index 3f8bd4f..09041f3 100644
--- a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs
@@ -159,6 +159,8 @@ namespace src.player.skills
invisiblePlayers.TryAdd(player.Index, 0);
SkillUtils.SetPlayerInvisibility(player, .5f);
+
+ SkillUtils.ForceFullUpdateToAll();
}
public static void DisableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs
index 5535eb3..96f7b56 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs
@@ -106,6 +106,8 @@ namespace src.player.skills
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
CreatePlayerPosProp(player);
+
+ SkillUtils.ForceFullUpdateToAll();
}
private static void CreatePlayerPosProp(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
index 82901ca..aa5fccd 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Glaz.cs
@@ -111,6 +111,8 @@ namespace src.player.skills
SkillUtils.TryGiveWeapon(player, CsItem.SmokeGrenade);
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, grenadeLimit);
+
+ SkillUtils.ForceFullUpdateToAll();
}
public static void DisableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/Jackal.cs b/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
index 716d1c6..ae83c78 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Jackal.cs
@@ -145,6 +145,8 @@ namespace src.player.skills
mainSkillTimer ??= Instance.AddTimer(2.5f, () => UpdateAllTrails(),
CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+
+ SkillUtils.ForceFullUpdateToAll();
}
private static void UpdateAllTrails()
diff --git a/jRandomSkills - SRC Files/src/player/skills/Jester.cs b/jRandomSkills - SRC Files/src/player/skills/Jester.cs
index 2a23f48..7f2d029 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Jester.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Jester.cs
@@ -53,18 +53,17 @@ namespace src.player.skills
var jester = GetJesterInfo(player.Index);
if (jester == null) return;
+ jester.Generation++;
jester.Active = false;
- if (jester.Timer != null)
- {
- jester.Timer.Kill();
- jester.Timer = null;
- }
+ jester.Timer = null;
+
+ jesters.TryRemove(player.Index, out _);
Server.NextWorldUpdate(() =>
{
SkillUtils.ResetPrintHTML(player);
SetPlayerColor(player, true);
- jesters.TryRemove(player.Index, out _);
+
});
}
@@ -154,23 +153,37 @@ namespace src.player.skills
uint playerIndex = player.Index;
- jesters.TryAdd(playerIndex, new JesterInfo {
- PlayerIndex = playerIndex,
- Active = false,
- Timer = Instance.AddTimer(wait, () => ChangeMode(playerIndex), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE)
- });
+ if (!jesters.TryGetValue(playerIndex, out var jester))
+ {
+ jester = new JesterInfo
+ {
+ PlayerIndex = playerIndex,
+ Active = false,
+ Generation = 0
+ };
+
+ jesters[playerIndex] = jester;
+ }
+
+ jester.Generation++;
+ int generation = jester.Generation;
+
+ jester.Timer = Instance.AddTimer(wait, () =>
+ {
+ if (!jesters.TryGetValue(playerIndex, out var info))
+ return;
+
+ if (info.Generation != generation)
+ return;
+
+ ChangeMode(playerIndex);
+ }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
private static void ChangeMode(uint playerIndex, bool? forceActive = null)
{
if (!jesters.TryGetValue(playerIndex, out var jester)) return;
- if (jester.Timer != null)
- {
- jester.Timer?.Kill();
- jester.Timer = null;
- }
-
var player = Utilities.GetPlayerFromIndex((int)playerIndex);
if (player == null || !player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE)
{
@@ -194,7 +207,19 @@ namespace src.player.skills
var maxTime = SkillsInfo.GetValue(skillName, "maxTime");
float wait = (float)Instance.Random.NextDouble() * (maxTime - minTime) + minTime;
- jester.Timer = Instance.AddTimer(wait, () => ChangeMode(playerIndex), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+ jester.Generation++;
+ int generation = jester.Generation;
+
+ jester.Timer = Instance.AddTimer(wait, () =>
+ {
+ if (!jesters.TryGetValue(playerIndex, out var info))
+ return;
+
+ if (info.Generation != generation)
+ return;
+
+ ChangeMode(playerIndex);
+ }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
private static void SetPlayerColor(CCSPlayerController? player, bool forceDisable = false)
@@ -205,9 +230,9 @@ namespace src.player.skills
if (pawn == null || !pawn.IsValid) return;
var jester = GetJesterInfo(player.Index);
- if (jester == null) return;
+ if (!forceDisable && jester == null) return;
- var color = jester.Active && !forceDisable ? Color.FromArgb(255, 128, 0, 128) : Color.FromArgb(255, 255, 255, 255);
+ var color = jester?.Active == true && !forceDisable ? Color.FromArgb(255, 128, 0, 128) : Color.FromArgb(255, 255, 255, 255);
pawn.Render = color;
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
@@ -249,6 +274,7 @@ namespace src.player.skills
{
public required uint PlayerIndex { get; set; }
public bool Active { get; set; } = false;
+ public int Generation { get; set; } = 0;
public Timer? Timer { get; set; } = null;
}
diff --git a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
index 8c133d4..6e0c8a0 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs
@@ -132,6 +132,8 @@ namespace src.player.skills
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
CreatePlayerPosProp(player);
+
+ SkillUtils.ForceFullUpdateToAll();
}
public static void DisableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
index 4b0fd36..2f99271 100644
--- a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
@@ -127,6 +127,7 @@ namespace src.player.skills
public static void EnableSkill(CCSPlayerController _)
{
Event.EnableTransmit();
+ SkillUtils.ForceFullUpdateToAll();
}
public static void DisableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
index 361498f..6aa0681 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Wallhack.cs
@@ -115,6 +115,8 @@ namespace src.player.skills
if (glows.IsEmpty)
SetGlowEffectForAll();
+
+ SkillUtils.ForceFullUpdateToAll();
}
public static void DisableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/utils/CServerSideClient.cs b/jRandomSkills - SRC Files/src/utils/CServerSideClient.cs
new file mode 100644
index 0000000..02701b6
--- /dev/null
+++ b/jRandomSkills - SRC Files/src/utils/CServerSideClient.cs
@@ -0,0 +1,107 @@
+// https://discord.com/channels/1160907911501991946/1508172390863994910/1508180670659166348
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using CounterStrikeSharp.API;
+using CounterStrikeSharp.API.Core;
+using CounterStrikeSharp.API.Modules.Memory;
+
+namespace jRandomSkills.src.utils
+{
+
+ #region Native Structs
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct CUtlMemory where T : unmanaged
+ {
+ public T* m_pMemory;
+ public int m_nAllocationCount;
+ public int m_nGrowSize;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct CUtlVector where T : unmanaged
+ {
+ public int m_Size;
+ public CUtlMemory m_Memory;
+
+ public int Count => m_Size;
+
+ public ref T Element(int index)
+ {
+ if (index < 0 || index >= m_Size)
+ throw new IndexOutOfRangeException();
+
+ if (m_Memory.m_pMemory == null)
+ throw new NullReferenceException("m_pMemory is null. The vector is empty or the GameData offset is outdated.");
+
+ return ref m_Memory.m_pMemory[index];
+ }
+ }
+
+ #endregion
+
+ #region Network Services
+
+ public class INetworkServerService : NativeObject
+ {
+ private readonly VirtualFunctionWithReturn GetIGameServerFunc;
+
+ public INetworkServerService() : base(NativeAPI.GetValveInterface(0, "NetworkServerService_001"))
+ {
+ GetIGameServerFunc = new VirtualFunctionWithReturn(Handle, GameData.GetOffset("INetworkServerService_GetIGameServer"));
+ }
+
+ public INetworkGameServer GetIGameServer()
+ {
+ return new INetworkGameServer(GetIGameServerFunc.Invoke(Handle));
+ }
+ }
+
+ public unsafe class INetworkGameServer(nint ptr) : NativeObject(ptr)
+ {
+ private static readonly int SlotsOffset = GameData.GetOffset("INetworkGameServer_Slots");
+
+ private ref CUtlVector Slots => ref Unsafe.AsRef>((void*)(Handle + SlotsOffset));
+
+ public CServerSideClient? GetClientBySlot(int slot)
+ {
+ if (Handle == nint.Zero)
+ return null;
+
+ if (slot < 0 || slot >= Slots.Count || Slots.m_Memory.m_pMemory == null)
+ return null;
+
+ var ptr = Slots.Element(slot);
+
+ if (ptr == nint.Zero)
+ return null;
+
+ return new CServerSideClient(ptr);
+ }
+ }
+
+ #endregion
+
+ #region CServerSideClient
+
+ public unsafe class CServerSideClient(nint ptr) : NativeObject(ptr)
+ {
+ private static readonly int m_nDeltaTick = GameData.GetOffset("CServerSideClient_m_nDeltaTick");
+
+ private ref T Field(int offset) where T : unmanaged
+ {
+ return ref Unsafe.AsRef((void*)(Handle + offset));
+ }
+
+ public int DeltaTick
+ {
+ get => Field(m_nDeltaTick);
+ set => Field(m_nDeltaTick) = value;
+ }
+
+ public void ForceFullUpdate() => DeltaTick = -1;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/jRandomSkills - SRC Files/src/utils/Config.cs b/jRandomSkills - SRC Files/src/utils/Config.cs
index 9104558..21e54b1 100644
--- a/jRandomSkills - SRC Files/src/utils/Config.cs
+++ b/jRandomSkills - SRC Files/src/utils/Config.cs
@@ -79,6 +79,7 @@ namespace src.utils
public bool SummaryAfterTheRound { get; set; }
public bool EnableBotSkills { get; set; }
public bool EnableBotKickDebug { get; set; }
+ public bool EnableFullForceUpdate { get; set; }
public bool DebugMode { get; set; }
public bool PerfMode { get; set; }
public string? AlternativeSkillButton { get; set; }
@@ -107,6 +108,7 @@ namespace src.utils
SummaryAfterTheRound = true;
EnableBotSkills = true;
EnableBotKickDebug = false;
+ EnableFullForceUpdate = false;
DebugMode = false;
PerfMode = false;
AlternativeSkillButton = null;
diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
index 32ffaee..a1de499 100644
--- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
+++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
@@ -333,6 +333,79 @@ namespace src.utils
return EntityManager.CreateTrackedTrigger(ownerPlayerIndex, name, radius, pos);
}
+ public static void ForceFullUpdate(CCSPlayerController player, List<(uint PlayerIndex, QAngle LastAngle)>? batchList = null, INetworkGameServer? networkGameServer = null)
+ {
+ if (!Config.LoadedConfig.EnableFullForceUpdate) return;
+ if (player == null || !player.IsValid) return;
+
+ var pawn = player.PlayerPawn?.Value;
+ if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
+
+ QAngle lastAngle = new(pawn.V_angle.X, pawn.V_angle.Y, pawn.V_angle.Z);
+
+ networkGameServer ??= new INetworkServerService().GetIGameServer();
+
+ var client = networkGameServer.GetClientBySlot(player.Slot);
+ if (client == null) return;
+
+ client.ForceFullUpdate();
+ // 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;
+
+ if (batchList != null)
+ {
+ batchList.Add((playerIndex, lastAngle));
+ return;
+ }
+
+ jRandomSkills.Instance.AddTickTimer(3, () =>
+ {
+ var target = Utilities.GetPlayerFromIndex((int)playerIndex);
+ if (target == null || !target.IsValid) return;
+
+ var targetPawn = target.PlayerPawn?.Value;
+ if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) return;
+
+ targetPawn.Look(lastAngle);
+ });
+ }
+
+ private static int lastForceFullUpdateAll = int.MinValue;
+
+ public static void ForceFullUpdateToAll()
+ {
+ if (!Config.LoadedConfig.EnableFullForceUpdate) return;
+
+ int tickCount = Server.TickCount;
+ if (tickCount == lastForceFullUpdateAll) return;
+
+ lastForceFullUpdateAll = tickCount;
+ var playersToRestore = new List<(uint PlayerIndex, QAngle LastAngle)>();
+
+ INetworkGameServer networkGameServer = new INetworkServerService().GetIGameServer();
+ foreach (var player in Utilities.GetPlayers())
+ ForceFullUpdate(player, playersToRestore, networkGameServer);
+
+ if (playersToRestore.Count <= 0) return;
+
+ jRandomSkills.Instance.AddTickTimer(3, () =>
+ {
+ foreach (var item in playersToRestore)
+ {
+ var target = Utilities.GetPlayerFromIndex((int)item.PlayerIndex);
+ if (target == null || !target.IsValid) continue;
+
+ var targetPawn = target.PlayerPawn?.Value;
+ if (targetPawn == null || !targetPawn.IsValid || targetPawn.AbsOrigin == null) continue;
+
+ targetPawn.Look(item.LastAngle);
+ }
+ });
+ }
+
public static bool SetHealth(CCSPlayerPawn? pawn, int newHealth, int? maxHealth = null)
{
if (pawn == null || !pawn.IsValid)
diff --git a/jRandomSkills - Server Files/gamedata/jRandomSkills.gamedata.json b/jRandomSkills - Server Files/gamedata/jRandomSkills.gamedata.json
index b4a5330..3e0bc0e 100644
--- a/jRandomSkills - Server Files/gamedata/jRandomSkills.gamedata.json
+++ b/jRandomSkills - Server Files/gamedata/jRandomSkills.gamedata.json
@@ -26,5 +26,23 @@
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B DA 48 8B F1 48 8D 55",
"linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8"
}
+ },
+ "CServerSideClient_m_nDeltaTick": {
+ "offsets": {
+ "windows": 348,
+ "linux": 348
+ }
+ },
+ "INetworkGameServer_Slots": {
+ "offsets": {
+ "windows": 584,
+ "linux": 584
+ }
+ },
+ "INetworkServerService_GetIGameServer": {
+ "offsets": {
+ "windows": 23,
+ "linux": 24
+ }
}
}
diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll
index a22e1b0..86a4447 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/configs/config.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json
index a343a83..03dbe24 100644
--- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json
+++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json
@@ -7,6 +7,7 @@
"SummaryAfterTheRound": true,
"EnableBotSkills": true,
"EnableBotKickDebug": false,
+ "EnableFullForceUpdate": false,
"DebugMode": false,
"PerfMode": false,
"AlternativeSkillButton": null,
diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll
index cfc8ef6..8e09384 100644
Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ