diff --git a/.github/en.md b/.github/en.md
index 2befcd0..0d0dd0f 100644
--- a/.github/en.md
+++ b/.github/en.md
@@ -424,6 +424,27 @@ This plugin uses content from the following projects:
## 馃搵 Changelog
+
+v1.2.4.b3
+
+- #### General
+ - Updated compatibility with **CounterStrikeSharp v1.0.375**.
+ - **Removed RayTrace-CSS-API / RayTrace-MM** (ray tracing is now built into CounterStrikeSharp).
+ - **Updated gamedata** signatures and offsets for Windows/Linux.
+ - Skill distribution failures are now logged and retried.
+ - Spectators without a pawn are excluded from connected-player checks.
+
+- #### Skill Fixes
+ - **No Recoil** - Spread and inaccuracy are now disabled server-side for accurate shots.
+ - **Long Zeus** - Kills now correctly count as Zeus kills, including killfeed, sound and ragdoll effects.
+ - **Long Knife** - Long-range hits now show a tracer.
+ - **Fragile Bomb** - Planted bombs are detected reliably and bullet paths are checked correctly.
+ - **Tripwire** - Beams are now removed correctly when the skill changes.
+
+**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#61](https://github.com/Juzlus/jRandomSkills/pull/61). Thanks to ByDexterTR!**
+
+
+
v1.2.4.b2
diff --git a/.github/pl.md b/.github/pl.md
index 971c7c0..aa26a1d 100644
--- a/.github/pl.md
+++ b/.github/pl.md
@@ -424,6 +424,27 @@ Plugin korzysta z zawarto艣ci nast臋puj膮cych projekt贸w:
## 馃搵 Lista Zmian
+
+v1.2.4.b3
+
+- #### Og贸lne
+ - Zaktualizowano kompatybilno艣膰 z **CounterStrikeSharp v1.0.375**.
+ - **Usuni臋to RayTrace-CSS-API / RayTrace-MM** (ray tracing jest teraz wbudowany w CounterStrikeSharp).
+ - **Zaktualizowano gamedata** - sygnatury i offsety dla Windows/Linux.
+ - Obserwatorzy bez pawna nie s膮 ju偶 uwzgl臋dniani podczas sprawdzania liczby po艂膮czonych graczy.
+ - B艂臋dy podczas przydzielania umiej臋tno艣ci s膮 teraz obserwowane i ponawiane.
+
+- #### Poprawki mocy
+ - **Skupienie** - Rozrzut i niecelno艣膰 s膮 teraz wy艂膮czane r贸wnie偶 po stronie serwera, zapewniaj膮c celniejsze strza艂y.
+ - **D艂ugi Zeus** - Zab贸jstwa s膮 teraz poprawnie zaliczane jako zab贸jstwa z Zeusa, wraz z killfeedem, d藕wi臋kiem i efektami ragdolla.
+ - **D艂ugi N贸偶** - Trafienia z du偶ego dystansu pokazuj膮 teraz 艣lad.
+ - **Krucha Bomba** - Pod艂o偶one bomby s膮 teraz wykrywane poprawnie, a trajektoria pocisku jest prawid艂owo sprawdzana.
+ - **"Linka z Drutu** - Wi膮zki s膮 teraz poprawnie usuwane po zmianie umiej臋tno艣ci.
+
+**Pe艂na aktualizacja zosta艂a przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#61](https://github.com/Juzlus/jRandomSkills/pull/61). Dzi臋kujemy ByDexterTR!**
+
+
+
v1.2.4.b2
@@ -1992,4 +2013,4 @@ Plugin korzysta z zawarto艣ci nast臋puj膮cych projekt贸w:
## 馃摑 Kontakt
-Je艣li masz jakie艣 pytanie, napisz na juzlus.biznes@gmail.com lub [jRandomSkills Discord](https://discord.gg/9H8EZYBpPF).
\ No newline at end of file
+Je艣li masz jakie艣 pytanie, napisz na juzlus.biznes@gmail.com lub [jRandomSkills Discord](https://discord.gg/9H8EZYBpPF).
diff --git a/README-PL.md b/README-PL.md
index ae42a84..8899f1c 100644
--- a/README-PL.md
+++ b/README-PL.md
@@ -441,6 +441,27 @@ Plugin korzysta z zawarto艣ci nast臋puj膮cych projekt贸w:
## 馃搵 Lista Zmian
+
+v1.2.4.b3
+
+- #### Og贸lne
+ - Zaktualizowano kompatybilno艣膰 z **CounterStrikeSharp v1.0.375**.
+ - **Usuni臋to RayTrace-CSS-API / RayTrace-MM** (ray tracing jest teraz wbudowany w CounterStrikeSharp).
+ - **Zaktualizowano gamedata** - sygnatury i offsety dla Windows/Linux.
+ - Obserwatorzy bez pawna nie s膮 ju偶 uwzgl臋dniani podczas sprawdzania liczby po艂膮czonych graczy.
+ - B艂臋dy podczas przydzielania umiej臋tno艣ci s膮 teraz obserwowane i ponawiane.
+
+- #### Poprawki mocy
+ - **Skupienie** - Rozrzut i niecelno艣膰 s膮 teraz wy艂膮czane r贸wnie偶 po stronie serwera, zapewniaj膮c celniejsze strza艂y.
+ - **D艂ugi Zeus** - Zab贸jstwa s膮 teraz poprawnie zaliczane jako zab贸jstwa z Zeusa, wraz z killfeedem, d藕wi臋kiem i efektami ragdolla.
+ - **D艂ugi N贸偶** - Trafienia z du偶ego dystansu pokazuj膮 teraz 艣lad.
+ - **Krucha Bomba** - Pod艂o偶one bomby s膮 teraz wykrywane poprawnie, a trajektoria pocisku jest prawid艂owo sprawdzana.
+ - **"Linka z Drutu** - Wi膮zki s膮 teraz poprawnie usuwane po zmianie umiej臋tno艣ci.
+
+**Pe艂na aktualizacja zosta艂a przygotowana przez [@ByDexterTR](https://github.com/ByDexterTR) w ramach pull requesta [#58](https://github.com/Juzlus/jRandomSkills/pull/58). Dzi臋kujemy ByDexterTR!**
+
+
+
v1.2.4.b2
diff --git a/README.md b/README.md
index 308088d..1c23bd9 100644
--- a/README.md
+++ b/README.md
@@ -441,6 +441,27 @@ This plugin uses content from the following projects:
## 馃搵 Changelog
+
+v1.2.4.b3
+
+- #### General
+ - Updated compatibility with **CounterStrikeSharp v1.0.375**.
+ - **Removed RayTrace-CSS-API / RayTrace-MM** (ray tracing is now built into CounterStrikeSharp).
+ - **Updated gamedata** signatures and offsets for Windows/Linux.
+ - Skill distribution failures are now logged and retried.
+ - Spectators without a pawn are excluded from connected-player checks.
+
+- #### Skill Fixes
+ - **No Recoil** - Spread and inaccuracy are now disabled server-side for accurate shots.
+ - **Long Zeus** - Kills now correctly count as Zeus kills, including killfeed, sound and ragdoll effects.
+ - **Long Knife** - Long-range hits now show a tracer.
+ - **Fragile Bomb** - Planted bombs are detected reliably and bullet paths are checked correctly.
+ - **Tripwire** - Beams are now removed correctly when the skill changes.
+
+**Full update contributed by [@ByDexterTR](https://github.com/ByDexterTR) in pull request [#61](https://github.com/Juzlus/jRandomSkills/pull/61). Thanks to ByDexterTR!**
+
+
+
v1.2.4.b2
diff --git a/jRandomSkills - SRC Files/jRandomSkills.csproj b/jRandomSkills - SRC Files/jRandomSkills.csproj
index 17a8785..b553bd8 100644
--- a/jRandomSkills - SRC Files/jRandomSkills.csproj
+++ b/jRandomSkills - SRC Files/jRandomSkills.csproj
@@ -21,7 +21,7 @@
-
+
@@ -30,12 +30,6 @@
-
-
- libs/RayTraceApi.dll
-
-
-
True
diff --git a/jRandomSkills - SRC Files/libs/RayTraceApi.dll b/jRandomSkills - SRC Files/libs/RayTraceApi.dll
deleted file mode 100644
index 584aea0..0000000
Binary files a/jRandomSkills - SRC Files/libs/RayTraceApi.dll and /dev/null differ
diff --git a/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json b/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
index bd817d6..0c2c147 100644
--- a/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
+++ b/jRandomSkills - SRC Files/src/gamedata/jRandomSkills.gamedata.json
@@ -3,20 +3,20 @@
"signatures": {
"library": "server",
"windows": "48 8B C4 48 89 58 ? 48 89 68 ? 48 89 70 ? 57 41 56 41 57 48 81 EC ? ? ? ? 48 8B B4 24 ? ? ? ? 4D 8B F8",
- "linux": "55 4C 89 C1 48 89 E5 41 57 49 89 FF 41 56 45 89 CE 41 55 4D 89 C5 41 54 53 48 83 EC ? 48 89 55 ? 48 89 F2 48 89 FE"
+ "linux": "55 4C 89 C1 48 89 E5 41 57 49 89 FF 41 56 45 89 CE 41 55 4D 89 C5 41 54 53 48 81 EC ? ? ? ? 48 89 95 ? ? ? ? 48 89 F2 48 89 FE 48 8D 3D"
}
},
"HEGrenadeProjectile_CreateFunc": {
"signatures": {
"library": "server",
- "windows": "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC ? 48 8B 6C 24 ? 49 8B F8 4C 8B C2 0F 29 74 24 ? 48 8B D1 48 8B D9 48 8D 0D ? ? ? ? 4C 8B CD E8 ? ? ? ? F3 0F 10 0D ? ? ? ? 48 8B C8 48 8B F0 E8 ? ? ? ? 48 8B D7 48 8B CE",
+ "windows": "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC ? 48 8B AC 24 ? ? ? ? 49 8B F8 4C 8B C2 0F 29 74 24 ? 48 8B D1 0F 29 7C 24 ? 48 8B D9 4C 8B CD 48 8D 0D ? ? ? ? E8",
"linux": "55 4C 89 C1 48 89 E5 41 57 49 89 FF 41 56 49 89 D6 48 89 F2 48 89 FE 41 55 48 8D 3D ? ? ? ? 4D 89 C5 41 54 45 89 CC 53"
}
},
"CMolotovProjectile_CreateFunc": {
"signatures": {
"library": "server",
- "windows": "48 8B C4 48 89 58 10 4C 89 40 18 48 89 48 08",
+ "windows": "48 8B C4 48 89 58 08 48 89 70 10 48 89 78 20 4C 89 40 18 55 41 54 41 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 45 33 FF",
"linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 41 55 41 54 49 89 FC 53 48 81 EC ? ? ? ? 4C 8D 35"
}
},
@@ -30,7 +30,7 @@
"SnapViewAngles": {
"signatures": {
"library": "server",
- "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",
+ "windows": "48 89 7C 24 ? 55 48 8B EC 48 81 EC ? ? ? ? 48 8B F9 48 8D 4D ? E8 ? ? ? ? F2 0F 10 87 ? ? ? ? 48 8B CF",
"linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8"
}
},
@@ -42,8 +42,8 @@
},
"INetworkGameServer_Slots": {
"offsets": {
- "windows": 584,
- "linux": 584
+ "windows": 616,
+ "linux": 616
}
},
"INetworkServerService_GetIGameServer": {
diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs
index 80ed317..e3ad2b8 100644
--- a/jRandomSkills - SRC Files/src/jRandomSkills.cs
+++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs
@@ -44,7 +44,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
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.4.b2-panorama1";
+ public override string ModuleVersion => "1.2.4.b3-panorama1";
public override void Load(bool hotReload)
{
@@ -303,9 +303,6 @@ namespace src
{ "Newtonsoft Json", "./Newtonsoft.Json.dll" },
{ "MaxMind", "./MaxMind.Db.dll" },
{ "GeoLite2", "./packages/GeoLite2-Country.mmdb" },
- { "RayTraceApi", "./../../shared/RayTraceApi/RayTraceApi.dll" },
- { "RayTraceImpl", "./../../plugins/RayTraceImpl/RayTraceImpl.dll" },
- { "RayTrace MetaMod", "./../../../metamod/RayTrace.vdf" },
{ "jRandomSkills gamedata", "./../../gamedata/jRandomSkills.gamedata.json" }
};
diff --git a/jRandomSkills - SRC Files/src/player/EntityEvents.cs b/jRandomSkills - SRC Files/src/player/EntityEvents.cs
index c98cc41..17c54e9 100644
--- a/jRandomSkills - SRC Files/src/player/EntityEvents.cs
+++ b/jRandomSkills - SRC Files/src/player/EntityEvents.cs
@@ -98,6 +98,7 @@ namespace src.player
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
Instance.SkillAction("Fortnite", "OnTakeDamage", args);
+ SkillUtils.ApplyNativeKill(entity, info);
return HookResult.Continue;
}
}
diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
index 8214e42..804d2f9 100644
--- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
+++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs
@@ -6,7 +6,6 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
-using RayTraceAPI;
using src.player.skills;
using src.utils;
using System.Collections.Concurrent;
@@ -108,6 +107,7 @@ namespace src.player
TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre));
TryUnhook(() => Instance.UnhookUserMessage(208, PlayerMakeSound));
TryUnhook(() => Instance.RemoveListener(CheckTransmit));
+ TryUnhook(NoRecoil.RestoreSpread);
}
private static void TryUnhook(Action unhook)
@@ -507,6 +507,7 @@ namespace src.player
private static void OnTick()
{
ReportStall();
+ NoRecoil.RestoreSpread();
long perfStart = PerfLog.Start();
lock (setLock)
@@ -794,7 +795,7 @@ namespace src.player
Vector eyePos = new(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z);
Vector endPos = eyePos + SkillUtils.GetForwardVector(pawn.EyeAngles) * 80;
- ulong mask = (ulong)(InteractionLayers.MASK_WORLD_ONLY | InteractionLayers.Player | InteractionLayers.NPC);
+ ulong mask = (ulong)Masks.SolidBrushOnly | (ulong)(Contents.Player | Contents.Npc);
ulong contents = 0;
var result = RayTrace.TraceShape(player, eyePos, endPos, mask, contents);
diff --git a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
index 437dec4..e185820 100644
--- a/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
+++ b/jRandomSkills - SRC Files/src/player/PlayerOnTick.cs
@@ -45,6 +45,7 @@ namespace src.player
private static void OnMapStart(string mapName)
{
+ skills.NoRecoil.ForgetSpread();
PlayerManager.SetServerActive(true);
Instance.GameRules = null;
Event.OnMapChange();
@@ -53,6 +54,8 @@ namespace src.player
private static void OnMapEnd()
{
+ skills.NoRecoil.RestoreSpread();
+ skills.NoRecoil.ForgetSpread();
PerfLog.Info("===== MAP END (clean map change) =====");
Debug.WriteToDebug("===== MAP END (clean map change) =====");
BotManager.Stop();
diff --git a/jRandomSkills - SRC Files/src/player/RoundEvents.cs b/jRandomSkills - SRC Files/src/player/RoundEvents.cs
index c5c7769..7fa6628 100644
--- a/jRandomSkills - SRC Files/src/player/RoundEvents.cs
+++ b/jRandomSkills - SRC Files/src/player/RoundEvents.cs
@@ -7,7 +7,6 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
-using RayTraceAPI;
using src.player.skills;
using src.utils;
using System.Collections.Concurrent;
@@ -98,14 +97,14 @@ namespace src.player
{
lock (setLock)
{
- bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true;
+ bool isWarmup = IsWarmupPeriod();
isTransmitRegistered = false;
setSkillRetries = 0;
SkillUtils.ClearKillCredits();
SkillUtils.ClearCurses();
Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
- foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist))
+ foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && InPlayingTeam(p)))
{
var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) continue;
@@ -115,18 +114,21 @@ namespace src.player
Instance.RemoveListener(CheckTransmit);
int freezetime = SkillUtils.CvarValue("mp_freezetime", 0);
- freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0));
+ int introDelay = IsTeamIntroPeriod() ? 7 : 0;
+ freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + introDelay);
setSkillTimer?.Kill();
if (isWarmup)
{
setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+ Debug.WriteToDebug("RoundStart: warmup, SetSkill scheduled in 1.00s.", DebugCategory.Round);
return HookResult.Continue;
}
- float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f;
+ float timeToDraw = introDelay + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f;
setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+ Debug.WriteToDebug($"RoundStart: SetSkill scheduled in {timeToDraw:F2}s (freezetime={freezetime}, intro={introDelay}, timer={(setSkillTimer == null ? "null" : "ok")}).", DebugCategory.Round);
return HookResult.Continue;
}
}
@@ -293,7 +295,22 @@ namespace src.player
private static void SetSkill()
{
long perfStart = PerfLog.Start();
- SetSkillCore();
+
+ try
+ {
+ SetSkillCore();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteToDebug($"SetSkill threw: {ex}", DebugCategory.Skill);
+
+ if (setSkillRetries < MaxSetSkillRetries)
+ {
+ setSkillRetries++;
+ setSkillTimer = Instance?.AddTimer(.5f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+ }
+ }
+
PerfLog.End("SetSkill total", perfStart, 2.0);
}
@@ -472,10 +489,46 @@ namespace src.player
private static int CountConnectedPlayers()
{
- try { return Utilities.GetPlayers().Count(p => p != null && p.IsValid && !p.IsHLTV); }
+ try
+ {
+ return Utilities.GetPlayers().Count(p =>
+ {
+ if (p == null || !p.IsValid || p.IsHLTV) return false;
+ var pawn = p.PlayerPawn?.Value;
+ return pawn != null && pawn.IsValid;
+ });
+ }
catch { return 0; }
}
+ private static bool InPlayingTeam(CCSPlayerController player)
+ {
+ try { return player.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; }
+ catch { return false; }
+ }
+
+ private static bool IsWarmupPeriod()
+ {
+ try
+ {
+ var gameRules = Instance?.GameRules;
+ if (gameRules == null || gameRules.Handle == IntPtr.Zero) return true;
+ return gameRules.WarmupPeriod;
+ }
+ catch { return true; }
+ }
+
+ private static bool IsTeamIntroPeriod()
+ {
+ try
+ {
+ var gameRules = Instance?.GameRules;
+ if (gameRules == null || gameRules.Handle == IntPtr.Zero) return false;
+ return gameRules.TeamIntroPeriod;
+ }
+ catch { return false; }
+ }
+
private static void SetSkillCore()
{
setSkillTimer = null;
@@ -484,7 +537,7 @@ namespace src.player
if (Instance == null) return;
// GameRules null = not ready; keep polling so skills land right after warmup ends.
- if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true)
+ if (IsWarmupPeriod())
{
if (++gameRulesPolls % 10 == 0)
Debug.WriteToDebug($"SetSkill waiting: gameRules={(Instance.GameRules == null ? "null" : "warmup")}, poll {gameRulesPolls}.", DebugCategory.Skill);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Cypher.cs b/jRandomSkills - SRC Files/src/player/skills/Cypher.cs
index 152f113..df75b2b 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Cypher.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Cypher.cs
@@ -4,7 +4,6 @@ using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
-using RayTraceAPI;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
@@ -341,7 +340,7 @@ namespace src.player.skills
Vector eyePos = new(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z + playerPawn.ViewOffset.Z);
Vector endPos = eyePos + SkillUtils.GetForwardVector(playerPawn.V_angle) * 4096f;
- ulong mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Window | InteractionLayers.PassBullets);
+ ulong mask = (ulong)(Contents.Solid | Contents.Window | Contents.PassBullets);
var result = RayTrace.TraceShape(player, eyePos, endPos, mask);
if (result.HasValue && result.Value.HitWorld(out _))
@@ -370,7 +369,7 @@ namespace src.player.skills
Vector endPos = cameraVector + SkillUtils.GetForwardVector(new QAngle(0, playerPawn.V_angle.Y + 180, 0)) * -5;
cameraVector += SkillUtils.GetForwardVector(new QAngle(0, playerPawn.V_angle.Y + 180, 0)) * 5;
- ulong mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Window | InteractionLayers.PassBullets);
+ ulong mask = (ulong)(Contents.Solid | Contents.Window | Contents.PassBullets);
var result = RayTrace.TraceShape(player, cameraVector, endPos, mask);
if (result.HasValue && result.Value.HitWorld(out _))
diff --git a/jRandomSkills - SRC Files/src/player/skills/FragileBomb.cs b/jRandomSkills - SRC Files/src/player/skills/FragileBomb.cs
index a46fa4e..a0661ab 100644
--- a/jRandomSkills - SRC Files/src/player/skills/FragileBomb.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/FragileBomb.cs
@@ -29,10 +29,16 @@ namespace src.player.skills
}
public static void BombPlanted(EventBombPlanted _)
+ {
+ plantedC4 = FindPlantedC4();
+ }
+
+ private static Vector? FindPlantedC4()
{
var plantedBomb = Utilities.FindAllEntitiesByDesignerName("planted_c4").FirstOrDefault();
- if (plantedBomb == null || !plantedBomb.IsValid || plantedBomb.AbsOrigin == null) return;
- plantedC4 = new(plantedBomb.AbsOrigin.X, plantedBomb.AbsOrigin.Y, plantedBomb.AbsOrigin.Z);
+ if (plantedBomb == null || !plantedBomb.IsValid || plantedBomb.AbsOrigin == null) return null;
+ if (!plantedBomb.BombTicking || plantedBomb.BombDefused) return null;
+ return new(plantedBomb.AbsOrigin.X, plantedBomb.AbsOrigin.Y, plantedBomb.AbsOrigin.Z);
}
private static void RemoveBomb()
@@ -49,14 +55,21 @@ namespace src.player.skills
if (lastTick == Server.TickCount) return;
var player = PlayerManager.GetPlayerEvent(@event.Userid);
- if (player == null || !player.IsValid || plantedC4 == null) return;
-
- var pos = new Vector(@event.X, @event.Y, @event.Z);
+ if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) return;
- if (SkillUtils.Distance(plantedC4, pos) >= 8)
+ plantedC4 ??= FindPlantedC4();
+ if (plantedC4 == null) return;
+
+ var pawn = player.PlayerPawn.Value;
+ if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
+
+ var eyePos = new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z);
+ var impactPos = new Vector(@event.X, @event.Y, @event.Z);
+
+ if (DistanceToSegment(plantedC4, eyePos, impactPos) >= 8)
return;
lastTick = Server.TickCount;
@@ -71,6 +84,16 @@ namespace src.player.skills
Localization.PrintTranslationToChatAll($" {ChatColors.Gold}{{0}}: {ChatColors.Red}{bombHealth}{ChatColors.Gold}/{ChatColors.Green}{maxBombHealth}", ["fragilebomb_bomb_health"]);
}
+ private static float DistanceToSegment(Vector point, Vector start, Vector end)
+ {
+ float lineX = end.X - start.X, lineY = end.Y - start.Y, lineZ = end.Z - start.Z;
+ float lengthSquared = lineX * lineX + lineY * lineY + lineZ * lineZ;
+ float t = lengthSquared <= .0001f ? 0f : Math.Clamp(((point.X - start.X) * lineX + (point.Y - start.Y) * lineY + (point.Z - start.Z) * lineZ) / lengthSquared, 0f, 1f);
+
+ float dx = start.X + lineX * t - point.X, dy = start.Y + lineY * t - point.Y, dz = start.Z + lineZ * t - point.Z;
+ return MathF.Sqrt(dx * dx + dy * dy + dz * dz);
+ }
+
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5d00ff", CsTeam onlyTeam = CsTeam.CounterTerrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 1, Rarity rarity = Rarity.Common, int maxBombHealth = 1000) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int MaxBombHealth { get; set; } = maxBombHealth;
diff --git a/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs b/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs
index a15c9df..049314b 100644
--- a/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/LongKnife.cs
@@ -15,6 +15,7 @@ namespace src.player.skills
private const string victimSound = "Player.DamageBody.Victim";
private const string heavyHitSound = "Weapon_Knife.Hit.Heavy.Flesh";
private const string lightHitSound = "Weapon_Knife.Hit.Light.Flesh";
+ private const string tracerParticle = "particles/weapons/cs_weapon_fx/weapon_tracers_rifle_wisp.vpcf";
private static bool hooked = false;
private const int actionCode = 503;
@@ -30,6 +31,7 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color"));
+ Instance.AddToManifest(tracerParticle);
}
public static void NewRound()
@@ -113,6 +115,9 @@ namespace src.player.skills
if (result == null || !result.HasValue)
return;
+ if (result.Value.Distance() > 70)
+ SkillUtils.CreateTracer(player, tracerParticle, result.Value);
+
if (!result.Value.HitPlayer(out CCSPlayerController? target) || target == null)
return;
diff --git a/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs b/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs
index 60f1efb..56f5dca 100644
--- a/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/LongZeus.cs
@@ -10,10 +10,12 @@ namespace src.player.skills
public class LongZeus : ISkill
{
private const Skills skillName = Skills.LongZeus;
+ private const string shockParticle = "particles/blood_impact/impact_taser_bodyfx.vpcf";
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color"));
+ Instance.AddToManifest(shockParticle);
}
public unsafe static void WeaponFire(EventWeaponFire @event)
@@ -40,7 +42,12 @@ namespace src.player.skills
if (target.Handle == player.Handle) return;
if (!SkillsInfo.GetValue(skillName, "friendlyFire") && player.Team == target.Team) return;
- SkillUtils.TakeHealth(target.PlayerPawn.Value, 9999, player, KillfeedIcons.Taser);
+ var targetPawn = target.PlayerPawn.Value;
+ if (targetPawn == null || !targetPawn.IsValid) return;
+
+ EntityManager.CreateEntityParticle(player.Index, shockParticle, targetPawn);
+ SkillUtils.RegisterNativeKill(targetPawn, pawn, activeWeapon, DamageTypes_t.DMG_SHOCK);
+ SkillUtils.TakeHealth(targetPawn, 9999);
}
public static void EnableSkill(CCSPlayerController player)
diff --git a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs
index f36f62c..ac5e5c1 100644
--- a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs
@@ -17,6 +17,27 @@ namespace src.player.skills
private static readonly ConcurrentDictionary holders = [];
private static readonly ConcurrentDictionary originalNoSpreadValues = [];
+ private sealed class SpreadPatch(CCSWeaponBaseVData vdata)
+ {
+ public readonly CCSWeaponBaseVData VData = vdata;
+ public readonly CFiringModeFloat[] Fields =
+ [
+ vdata.Spread, vdata.InaccuracyCrouch, vdata.InaccuracyStand, vdata.InaccuracyJump,
+ vdata.InaccuracyLand, vdata.InaccuracyLadder, vdata.InaccuracyFire, vdata.InaccuracyMove
+ ];
+ public readonly float[] Values = new float[16];
+ public int Count;
+ public float JumpInitial;
+ public float JumpApex;
+ public float Reload;
+ public bool Active;
+ }
+
+ private static readonly Dictionary spreadPatches = [];
+ private static readonly List activePatches = [];
+ private static readonly Dictionary wantedVData = [];
+ private static readonly HashSet blockedVData = [];
+
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color"));
@@ -83,6 +104,106 @@ namespace src.player.skills
catch { }
}
+ public static void OnTick()
+ {
+ if (holders.IsEmpty) return;
+
+ wantedVData.Clear();
+ blockedVData.Clear();
+
+ foreach (var player in PlayerManager.GetTickPlayers())
+ {
+ if (player == null || !player.IsValid || !player.PawnIsAlive) continue;
+
+ var vdata = GetActiveWeaponVData(player);
+ if (vdata == null) continue;
+
+ bool isHolder = holders.ContainsKey(player.Index) && PlayerManager.GetPlayerByIndex(player.Index)?.Skill == skillName;
+ if (isHolder)
+ wantedVData[vdata.Handle] = vdata;
+ else
+ blockedVData.Add(vdata.Handle);
+ }
+
+ foreach (var (handle, vdata) in wantedVData)
+ {
+ if (!blockedVData.Contains(handle))
+ PatchSpread(vdata);
+ }
+ }
+
+ private static CCSWeaponBaseVData? GetActiveWeaponVData(CCSPlayerController player)
+ {
+ var weapon = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;
+ if (weapon == null || !weapon.IsValid) return null;
+
+ var vdata = weapon.As().VData;
+ return vdata == null || vdata.Handle == IntPtr.Zero ? null : vdata;
+ }
+
+ private static void PatchSpread(CCSWeaponBaseVData vdata)
+ {
+ if (!spreadPatches.TryGetValue(vdata.Handle, out var patch))
+ {
+ patch = new SpreadPatch(vdata);
+ spreadPatches[vdata.Handle] = patch;
+ }
+
+ if (patch.Active) return;
+
+ int index = 0;
+ foreach (var field in patch.Fields)
+ {
+ var values = field.Values;
+ for (int i = 0; i < values.Length && index < patch.Values.Length; i++)
+ {
+ patch.Values[index++] = values[i];
+ values[i] = 0f;
+ }
+ }
+
+ patch.Count = index;
+ patch.JumpInitial = vdata.InaccuracyJumpInitial;
+ patch.JumpApex = vdata.InaccuracyJumpApex;
+ patch.Reload = vdata.InaccuracyReload;
+
+ vdata.InaccuracyJumpInitial = 0f;
+ vdata.InaccuracyJumpApex = 0f;
+ vdata.InaccuracyReload = 0f;
+
+ patch.Active = true;
+ activePatches.Add(patch);
+ }
+
+ public static void RestoreSpread()
+ {
+ if (activePatches.Count == 0) return;
+
+ foreach (var patch in activePatches)
+ {
+ int index = 0;
+ foreach (var field in patch.Fields)
+ {
+ var values = field.Values;
+ for (int i = 0; i < values.Length && index < patch.Count; i++)
+ values[i] = patch.Values[index++];
+ }
+
+ patch.VData.InaccuracyJumpInitial = patch.JumpInitial;
+ patch.VData.InaccuracyJumpApex = patch.JumpApex;
+ patch.VData.InaccuracyReload = patch.Reload;
+ patch.Active = false;
+ }
+
+ activePatches.Clear();
+ }
+
+ public static void ForgetSpread()
+ {
+ activePatches.Clear();
+ spreadPatches.Clear();
+ }
+
public static void WeaponFire(EventWeaponFire @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
diff --git a/jRandomSkills - SRC Files/src/player/skills/Ricochet.cs b/jRandomSkills - SRC Files/src/player/skills/Ricochet.cs
index 1982f5f..b4ca260 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Ricochet.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Ricochet.cs
@@ -1,8 +1,7 @@
-using CounterStrikeSharp.API;
+锘縰sing CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
-using RayTraceAPI;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
@@ -22,7 +21,7 @@ namespace src.player.skills
private static readonly List flights = [];
private static readonly object flightLock = new();
private static readonly Color tracerColor = Color.FromArgb(170, 255, 226, 140);
- private static readonly ulong worldOnlyMask = (ulong)InteractionLayers.MASK_WORLD_ONLY;
+ private static readonly ulong worldOnlyMask = (ulong)Masks.SolidBrushOnly;
public static void LoadSkill()
{
diff --git a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs
index 7fc5f34..d94b2cc 100644
--- a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs
+++ b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs
@@ -1,7 +1,6 @@
-using CounterStrikeSharp.API;
+锘縰sing CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
-using RayTraceAPI;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
@@ -104,19 +103,7 @@ namespace src.player.skills
private static void DestroyWire(uint beamIndex)
{
- var beam = Utilities.GetEntityFromIndex((int)beamIndex);
- if (beam != null && beam.IsValid)
- {
- beam.Width = 0;
- beam.EndWidth = 0;
- beam.Render = Color.FromArgb(0, 0, 0, 0);
-
- Utilities.SetStateChanged(beam, "CBeam", "m_fWidth");
- Utilities.SetStateChanged(beam, "CBeam", "m_fEndWidth");
- Utilities.SetStateChanged(beam, "CBaseModelEntity", "m_clrRender");
- }
-
- EntityManager.DestroyEntity(beamIndex);
+ EntityManager.DestroyBeam(beamIndex);
}
public static void UseSkill(CCSPlayerController player)
@@ -175,7 +162,7 @@ namespace src.player.skills
Vector rightDir = SkillUtils.GetForwardVector(new QAngle(0, yaw - 90, 0));
Vector leftDir = SkillUtils.GetForwardVector(new QAngle(0, yaw + 90, 0));
- ulong wallMask = (ulong)InteractionLayers.MASK_WORLD_ONLY;
+ ulong wallMask = (ulong)Masks.SolidBrushOnly;
var rightHit = RayTrace.TraceShape(player, origin, origin + rightDir * maxDistance, wallMask, 0);
var leftHit = RayTrace.TraceShape(player, origin, origin + leftDir * maxDistance, wallMask, 0);
diff --git a/jRandomSkills - SRC Files/src/utils/CustomTraceResult.cs b/jRandomSkills - SRC Files/src/utils/CustomTraceResult.cs
index 85ea933..9ffd2b7 100644
--- a/jRandomSkills - SRC Files/src/utils/CustomTraceResult.cs
+++ b/jRandomSkills - SRC Files/src/utils/CustomTraceResult.cs
@@ -1,30 +1,55 @@
-锘縰sing RayTraceAPI;
+锘縰sing CounterStrikeSharp.API.Modules.Utils;
using System.Numerics;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace jRandomSkills.src.utils
{
- public struct CustomTraceResult(TraceResult result, Vector startPos, ulong mask, ulong contents, bool drawBeam)
+ public struct CustomTraceResult
{
- public float StartPosX = startPos.X;
- public float StartPosY = startPos.Y;
- public float StartPosZ = startPos.Z;
+ public float StartPosX;
+ public float StartPosY;
+ public float StartPosZ;
- public float EndPosX = result.EndPosX;
- public float EndPosY = result.EndPosY;
- public float EndPosZ = result.EndPosZ;
+ public float EndPosX;
+ public float EndPosY;
+ public float EndPosZ;
- public nint HitEntity = result.HitEntity;
- public float Fraction = result.Fraction;
- public int AllSolid = result.AllSolid;
+ public nint HitEntity;
+ public float Fraction;
+ public int AllSolid;
- public float NormalX = result.NormalX;
- public float NormalY = result.NormalY;
- public float NormalZ = result.NormalZ;
+ public float NormalX;
+ public float NormalY;
+ public float NormalZ;
- public ulong InteractsWith = mask;
- public ulong InteractsExclude = contents;
- public bool DrawBeam = drawBeam;
+ public ulong InteractsWith;
+ public ulong InteractsExclude;
+ public bool DrawBeam;
+
+ public CustomTraceResult(TraceResult result, Vector startPos, ulong mask, ulong contents, bool drawBeam)
+ {
+ StartPosX = startPos.X;
+ StartPosY = startPos.Y;
+ StartPosZ = startPos.Z;
+
+ var end = result.EndPos;
+ EndPosX = end.X;
+ EndPosY = end.Y;
+ EndPosZ = end.Z;
+
+ var normal = result.Normal;
+ NormalX = normal.X;
+ NormalY = normal.Y;
+ NormalZ = normal.Z;
+
+ HitEntity = result.HitEntity().Handle;
+ Fraction = result.Fraction;
+ AllSolid = result.IsAllSolid ? 1 : 0;
+
+ InteractsWith = mask;
+ InteractsExclude = contents;
+ DrawBeam = drawBeam;
+ }
public readonly Vector3 StartPos => new(StartPosX, StartPosY, StartPosZ);
public readonly Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ);
diff --git a/jRandomSkills - SRC Files/src/utils/EntityManager.cs b/jRandomSkills - SRC Files/src/utils/EntityManager.cs
index ec57874..72ffa84 100644
--- a/jRandomSkills - SRC Files/src/utils/EntityManager.cs
+++ b/jRandomSkills - SRC Files/src/utils/EntityManager.cs
@@ -1,10 +1,12 @@
锘縰sing CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
+using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using src.player;
using System.Collections.Concurrent;
using System.Drawing;
+using System.Runtime.InteropServices;
using static src.jRandomSkills;
namespace src.utils
@@ -136,6 +138,67 @@ namespace src.utils
}
}
+ public static CParticleSystem? CreateTracer(uint playerIndex, string particleName, Vector start, Vector end, float lifetime = 1f)
+ {
+ try
+ {
+ if (OverBudget()) return null;
+ var particle = Utilities.CreateEntityByName("info_particle_system");
+ if (particle == null || !particle.IsValid) return null;
+
+ particle.EffectName = particleName;
+ particle.StartActive = true;
+ particle.Teleport(start);
+
+ var controlPoint = new Vector(particle.Handle + Schema.GetSchemaOffset("CParticleSystem", "m_vServerControlPoints"));
+ controlPoint.X = end.X;
+ controlPoint.Y = end.Y;
+ controlPoint.Z = end.Z;
+ particle.ServerControlPointAssignments[0] = 1;
+
+ particle.DispatchSpawn();
+
+ RegisterEntity(particle.Index, playerIndex, "tracer");
+ ScheduleAutoDestroy(particle.Index, lifetime);
+ return particle;
+ }
+ catch (Exception ex)
+ {
+ LogEntityError($"CreateTracer: {ex.Message}");
+ return null;
+ }
+ }
+
+ public static CParticleSystem? CreateEntityParticle(uint playerIndex, string particleName, CBaseEntity target, float lifetime = 5f)
+ {
+ try
+ {
+ if (OverBudget() || target.AbsOrigin == null) return null;
+ var particle = Utilities.CreateEntityByName("info_particle_system");
+ if (particle == null || !particle.IsValid) return null;
+
+ particle.EffectName = particleName;
+ particle.StartActive = true;
+ particle.Teleport(target.AbsOrigin);
+
+ nint controlPointEnts = particle.Handle + Schema.GetSchemaOffset("CParticleSystem", "m_hControlPointEnts");
+ Marshal.WriteInt32(controlPointEnts, (int)target.EntityHandle.Raw);
+ Marshal.WriteInt32(controlPointEnts + 4, (int)target.EntityHandle.Raw);
+
+ particle.DispatchSpawn();
+ particle.AcceptInput("SetParent", target, particle, "!activator");
+
+ RegisterEntity(particle.Index, playerIndex, "particle_system");
+ ScheduleAutoDestroy(particle.Index, lifetime);
+ return particle;
+ }
+ catch (Exception ex)
+ {
+ LogEntityError($"CreateEntityParticle: {ex.Message}");
+ return null;
+ }
+ }
+
public static CDynamicProp? CreateTrackedDynamicProp(uint playerIndex, string designerName = "prop_dynamic", string? trackAs = null)
{
try
@@ -295,7 +358,7 @@ namespace src.utils
public static bool SuppressKills = false;
- public static bool DestroyEntity(uint entityIndex, float delay = 0.1f)
+ public static bool DestroyEntity(uint entityIndex, float delay = 0.1f, bool hideFromTransmit = true)
{
bool wasTracked = trackedEntities.TryRemove(entityIndex, out var removed);
@@ -310,7 +373,8 @@ namespace src.utils
var entity = Utilities.GetEntityFromIndex((int)entityIndex);
if (entity != null && entity.IsValid)
{
- recentlyDestroyed[entityIndex] = new DyingEntity(DateTime.UtcNow.AddSeconds(delay + 0.5), entity.EntityHandle.Raw);
+ if (hideFromTransmit)
+ recentlyDestroyed[entityIndex] = new DyingEntity(DateTime.UtcNow.AddSeconds(delay + 0.5), entity.EntityHandle.Raw);
// Detach first so no follower is left on a freed parent.
entity.AcceptInput("ClearParent");
entity.AddEntityIOEvent("Kill", entity, delay: delay);
@@ -346,7 +410,7 @@ namespace src.utils
LogEntityError($"DestroyBeam {entityIndex}: {ex.Message}");
}
- bool killed = DestroyEntity(entityIndex);
+ bool killed = DestroyEntity(entityIndex, hideFromTransmit: false);
if (!SuppressKills)
Server.NextFrame(() =>
@@ -403,7 +467,7 @@ namespace src.utils
DestroyAllTracked();
}
- private static void ScheduleAutoDestroy(uint entityIndex, float? seconds)
+ public static void ScheduleAutoDestroy(uint entityIndex, float? seconds)
{
if (seconds is not > 0) return;
Instance?.AddTimer(seconds.Value, () => DestroyEntity(entityIndex), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
diff --git a/jRandomSkills - SRC Files/src/utils/RayTrace.cs b/jRandomSkills - SRC Files/src/utils/RayTrace.cs
index 92f314f..3066094 100644
--- a/jRandomSkills - SRC Files/src/utils/RayTrace.cs
+++ b/jRandomSkills - SRC Files/src/utils/RayTrace.cs
@@ -1,45 +1,43 @@
锘縰sing CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
-using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
-using RayTraceAPI;
using System.Drawing;
using System.Numerics;
-using TraceOptions = RayTraceAPI.TraceOptions;
-using TraceResult = RayTraceAPI.TraceResult;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace src.utils
{
public static class RayTrace
{
- private static PluginCapability RayTraceInterface { get; } = new("raytrace:craytraceinterface");
- private static bool missingModuleLogged;
+ private static bool traceFailureLogged;
+
+ public static bool IsAvailable => true;
- private static CRayTraceInterface? GetInterface()
+ private static bool TryTraceHull(Vector startPos, Vector endPos, Vector mins, Vector maxs, CBaseEntity? ignore, ulong mask, ulong contents, out TraceResult result)
{
try
{
- var rayTrace = RayTraceInterface.Get();
- if (rayTrace != null) return rayTrace;
+ result = Trace.TraceHullShape(startPos, endPos, mins, maxs, ignore, new TraceOptions
+ {
+ InteractsWith = (Contents)mask,
+ InteractsExclude = (Contents)contents,
+ });
+ return true;
}
- catch { }
-
- if (!missingModuleLogged)
+ catch (Exception ex)
{
- missingModuleLogged = true;
- Server.PrintToConsole("[jRandomSkills] RayTrace module not found - skills that need it do nothing: " +
- "LongZeus, LongKnife, Iana, Cypher, Noclip, Shade (and the skill-use button's aim check). " +
- "Install RayTrace-CSS-API and RayTrace-MM: https://github.com/FUNPLAY-pro-CS2/Ray-Trace/releases");
+ result = default;
+ if (!traceFailureLogged)
+ {
+ traceFailureLogged = true;
+ Server.PrintToConsole($"[jRandomSkills] Native trace failed: {ex.Message}");
+ }
+ return false;
}
-
- return null;
}
- public static bool IsAvailable => GetInterface() != null;
-
public static CustomTraceResult? TraceShape(CCSPlayerController player, Vector startPos, Vector endPos, ulong? mask = null, ulong? contents = null)
{
if (player == null || !player.IsValid) return null;
@@ -53,54 +51,39 @@ namespace src.utils
playerPawn.CBodyComponent?.SceneNode == null)
return null;
- var rayTrace = GetInterface();
- if (rayTrace == null)
- return null;
-
if (mask == null)
{
try
{
if (playerPawn.Collision?.CollisionAttribute != null)
{
- mask = playerPawn.Collision.CollisionAttribute.InteractsWith | (ulong)InteractionLayers.Hitboxes;
- mask &= ~(ulong)InteractionLayers.PlayerClip;
+ mask = playerPawn.Collision.CollisionAttribute.InteractsWith | (ulong)Contents.Hitbox;
+ mask &= ~(ulong)Contents.PlayerClip;
}
else
- mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Hitboxes);
+ mask = (ulong)(Contents.Solid | Contents.Hitbox);
}
catch
{
- mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Hitboxes);
+ mask = (ulong)(Contents.Solid | Contents.Hitbox);
}
}
contents ??= 0;
bool drawBeam = Config.LoadedConfig.TraceRayBeam;
- TraceOptions options = new()
- {
- InteractsWith = (ulong)mask,
- InteractsExclude = (ulong)contents,
- DrawBeam = drawBeam == true ? 1 : 0,
- };
-
Vector mins = new(-0.5f, -0.5f, -0.5f);
Vector maxs = new(0.5f, 0.5f, 0.5f);
- TraceResult result = default;
-
- try
- {
- rayTrace.TraceHullShape(startPos, endPos, mins, maxs, playerPawn, options, out result);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"[jRandomSkills] A memory error was caught during RayTrace: {ex.Message}");
+ if (!TryTraceHull(startPos, endPos, mins, maxs, playerPawn, (ulong)mask, (ulong)contents, out var result))
return null;
- }
- return new CustomTraceResult(result, startPos, (ulong)mask, (ulong)contents, drawBeam);
+ var traced = new CustomTraceResult(result, startPos, (ulong)mask, (ulong)contents, drawBeam);
+
+ if (drawBeam)
+ CreateBeamLine(startPos, new Vector(traced.EndPosX, traced.EndPosY, traced.EndPosZ), traced.DidHit ? Color.Red : Color.Green);
+
+ return traced;
}
public static CustomTraceResult? EyeTrace(CCSPlayerController player)
@@ -136,10 +119,6 @@ namespace src.utils
playerPawn.CBodyComponent?.SceneNode == null)
return null;
- var rayTrace = GetInterface();
- if (rayTrace == null)
- return null;
-
Vector safeMins = mins ?? playerPawn.Collision.Mins;
Vector safeMaxs = maxs ?? playerPawn.Collision.Maxs;
@@ -148,26 +127,8 @@ namespace src.utils
bool drawBeam = Config.LoadedConfig.TraceRayBeam;
- TraceResult result = default;
- TraceOptions options = new()
- {
- InteractsWith = safeMask,
- InteractsExclude = safeContents,
- DrawBeam = drawBeam == true ? 1 : 0,
- };
-
- CEntityInstance? entityToIgnore = (playerPawn.LifeState == (byte)LifeState_t.LIFE_ALIVE)
- ? playerPawn
- : null;
-
- try
- {
- rayTrace.TraceHullShape(startPos, endPos, safeMins, safeMaxs, entityToIgnore, options, out result);
- }
- catch (Exception)
- {
+ if (!TryTraceHull(startPos, endPos, safeMins, safeMaxs, playerPawn, safeMask, safeContents, out var result))
return null;
- }
if (drawBeam)
{
@@ -246,6 +207,9 @@ namespace src.utils
beam.EndPos.Z = end.Z;
beam.DispatchSpawn();
+
+ EntityManager.RegisterEntity(beam.Index, int.MaxValue, "beam");
+ EntityManager.ScheduleAutoDestroy(beam.Index, 10);
}
public static float Distance(this CustomTraceResult result)
@@ -260,6 +224,12 @@ namespace src.utils
public static bool HitEntityByDesignerName(this CustomTraceResult result, out T? entity, string designerName, DesignerNameMatchType matchType = DesignerNameMatchType.Equals) where T : CEntityInstance
{
+ if (result.HitEntity == IntPtr.Zero)
+ {
+ entity = null;
+ return false;
+ }
+
T? val = (T?)Activator.CreateInstance(typeof(T), result.HitEntity);
if ((object?)val != null && matchType switch
{
@@ -279,6 +249,12 @@ namespace src.utils
public static bool HitEntity(this CustomTraceResult result, out CBaseEntity? entity)
{
+ if (result.HitEntity == IntPtr.Zero)
+ {
+ entity = null;
+ return false;
+ }
+
CEntityInstance entityInstance = new(result.HitEntity);
if (string.IsNullOrEmpty(entityInstance.DesignerName))
{
diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
index 18eebd5..bd2fe5d 100644
--- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
+++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs
@@ -480,9 +480,48 @@ namespace src.utils
return true;
}
+ public static void CreateTracer(CCSPlayerController player, string particleName, CustomTraceResult result)
+ {
+ var pawn = player.PlayerPawn.Value;
+ if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
+
+ Vector forward = GetForwardVector(pawn.EyeAngles);
+ Vector start = new(pawn.AbsOrigin.X + forward.X * 16, pawn.AbsOrigin.Y + forward.Y * 16, pawn.AbsOrigin.Z + pawn.ViewOffset.Z - 6);
+ Vector end = new(result.EndPos.X, result.EndPos.Y, result.EndPos.Z);
+
+ EntityManager.CreateTracer(player.Index, particleName, start, end);
+ }
+
public static void ClearKillCredits()
{
pendingKillCredits.Clear();
+ pendingNativeKills.Clear();
+ }
+
+ private static readonly ConcurrentDictionary pendingNativeKills = [];
+
+ public static void RegisterNativeKill(CCSPlayerPawn victimPawn, CBaseEntity attacker, CBaseEntity inflictor, DamageTypes_t damageType)
+ {
+ pendingNativeKills[victimPawn.Index] = (attacker.EntityHandle.Raw, inflictor.EntityHandle.Raw, damageType, Server.TickCount + 64);
+ }
+
+ public static void ApplyNativeKill(CEntityInstance victim, CTakeDamageInfo info)
+ {
+ if (pendingNativeKills.IsEmpty) return;
+ if (!pendingNativeKills.TryGetValue(victim.Index, out var kill)) return;
+
+ if (kill.ExpiryTick < Server.TickCount)
+ {
+ pendingNativeKills.TryRemove(victim.Index, out _);
+ return;
+ }
+
+ if (info.Attacker.Value?.Handle != victim.Handle) return;
+
+ pendingNativeKills.TryRemove(victim.Index, out _);
+ info.Attacker.Raw = kill.AttackerHandle;
+ info.Inflictor.Raw = kill.InflictorHandle;
+ info.BitsDamageType = kill.DamageType;
}
private static readonly HashSet bulletWeapons = new(StringComparer.Ordinal)
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000..c02fb80
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,189 @@
+#!/usr/bin/env bash
+# Build jRandomSkills and its Panorama layouts, package them as one tarball and publish it as a
+# Forgejo release.
+#
+# ./release.sh e.g. ./release.sh v1.2.4.b2-panorama1
+#
+# Commit first: the plugin is rebuilt here from the committed source, so the release always matches
+# the tag. The tag is created on HEAD and pushed to origin if it doesn't exist yet. Re-running with
+# the same tag replaces that release's attachment.
+#
+# Asset: jRandomSkills-.tar.gz, laid out like the server's game/csgo/ - extract it there:
+# tar -xzf jRandomSkills-.tar.gz -C /srv/cs2/game/csgo
+#
+# addons/counterstrikesharp/plugins/jRandomSkills/ plugin, deps, languages/, packages/
+# addons/counterstrikesharp/gamedata/ jRandomSkills.gamedata.json
+# panorama/{layout,styles}/custom_game/ compiled skill_hud / jrs_menu (.vxml_c/.vcss_c)
+#
+# No configs/: the plugin writes config.json and skillsInfo.json itself when they are missing, so an
+# upgrade never touches the server's config. The Panorama files are compiled from
+# "jRandomSkills - SRC Files/panorama" with the CS2 client's resourcecompiler.exe (WSL interop).
+#
+# Players get the layouts from the Workshop, not from this tarball: the sources are compiled into
+# the client's jrandomskills_hud Workshop Tools addon (Workshop item 3807837213, mounted for every
+# player by the server's MultiAddonManager, mm_extra_addons). The panorama/ files in the tarball
+# only land on the server. When the compiled layouts changed, the script says so at the end:
+# re-upload the addon from Workshop Tools. That step stays manual.
+set -euo pipefail
+
+FORGEJO_URL="https://git.zio.sh"
+OWNER="cs2"
+REPO="jRandomSkills"
+# Placeholder - replace, or set FORGEJO_TOKEN in the environment instead. Needs write:repository.
+# Only uploading needs it; downloading from the public repo doesn't. Don't commit a real token.
+FORGEJO_TOKEN="${FORGEJO_TOKEN:-CHANGE_ME_FORGEJO_TOKEN}"
+
+# CS2 client used to compile the Panorama layouts, and the Workshop Tools addon compiled into.
+CS2_CLIENT_DIR="${CS2_CLIENT_DIR:-/mnt/e/SteamLibrary/steamapps/common/Counter-Strike Global Offensive}"
+CS2_ADDON="${CS2_ADDON:-jrandomskills_hud}"
+WORKSHOP_ID="3807837213"
+PANORAMA_FILES=(
+ layout/custom_game/skill_hud.xml
+ layout/custom_game/jrs_menu.xml
+ styles/custom_game/skill_hud.css
+ styles/custom_game/jrs_menu.css
+)
+
+# Only these ship from the build output. bin/ also holds CounterStrikeSharp.API.dll, RayTraceApi.dll
+# and framework DLLs, which the server already provides and which must not be duplicated per plugin.
+PLUGIN_BINARIES=(
+ jRandomSkills.dll
+ jRandomSkills.pdb
+ MaxMind.Db.dll
+ Newtonsoft.Json.dll
+ PanoramaManager.dll
+)
+
+TAG="${1:-}"
+if [[ -z "$TAG" ]]; then
+ echo "usage: $0 (e.g. v1.2.4.b2-panorama1)" >&2
+ exit 1
+fi
+if [[ "$FORGEJO_TOKEN" == "CHANGE_ME_FORGEJO_TOKEN" ]]; then
+ echo "Set FORGEJO_TOKEN (edit release.sh or export it) first." >&2
+ exit 1
+fi
+
+cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
+
+SRC="jRandomSkills - SRC Files"
+
+if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
+ echo "Tracked files have uncommitted changes; commit them so the tag matches the release." >&2
+ exit 1
+fi
+
+# --- Build -----------------------------------------------------------------------
+BIN="$SRC/bin/Release/net10.0"
+rm -rf "$SRC/bin" "$SRC/obj"
+podman run --rm -v "$(pwd)":/src:Z -w "/src/$SRC" \
+ mcr.microsoft.com/dotnet/sdk:10.0 dotnet build -c Release
+
+# --- Stage (layout of game/csgo/) --------------------------------------------------
+OUT="release-out"
+STAGE="$OUT/stage"
+rm -rf "$OUT"
+PLUGIN_DIR="$STAGE/addons/counterstrikesharp/plugins/jRandomSkills"
+mkdir -p "$PLUGIN_DIR/languages" "$PLUGIN_DIR/packages" "$STAGE/addons/counterstrikesharp/gamedata"
+
+for file in "${PLUGIN_BINARIES[@]}"; do
+ cp "$BIN/$file" "$PLUGIN_DIR/"
+done
+cp "$SRC"/src/lang/*.json "$PLUGIN_DIR/languages/"
+cp -r "$SRC"/packages/. "$PLUGIN_DIR/packages/"
+cp "$SRC"/src/gamedata/*.json "$STAGE/addons/counterstrikesharp/gamedata/"
+
+# --- Panorama: compile sources with the CS2 client ---------------------------------
+RC_DIR="$CS2_CLIENT_DIR/game/bin/win64"
+[[ -x "$RC_DIR/resourcecompiler.exe" ]] || { echo "resourcecompiler.exe not found in $RC_DIR (set CS2_CLIENT_DIR)." >&2; exit 1; }
+CONTENT_ADDON="$CS2_CLIENT_DIR/content/csgo_addons/$CS2_ADDON/panorama"
+GAME_ADDON="$CS2_CLIENT_DIR/game/csgo_addons/$CS2_ADDON/panorama"
+WIN_CLIENT_DIR="$(wslpath -w "$CS2_CLIENT_DIR")"
+
+# Checksums of the addon's compiled layouts, to tell whether the Workshop item needs a re-upload.
+compiled_sums() {
+ local file compiled
+ for file in "${PANORAMA_FILES[@]}"; do
+ case "$file" in
+ *.xml) compiled="${file%.xml}.vxml_c" ;;
+ *.css) compiled="${file%.css}.vcss_c" ;;
+ esac
+ if [[ -f "$GAME_ADDON/$compiled" ]]; then
+ sha256sum "$GAME_ADDON/$compiled" | cut -d' ' -f1
+ else
+ echo "missing $compiled"
+ fi
+ done
+}
+sums_before="$(compiled_sums)"
+
+for file in "${PANORAMA_FILES[@]}"; do
+ mkdir -p "$CONTENT_ADDON/$(dirname "$file")" "$STAGE/panorama/$(dirname "$file")"
+ cp -f "$SRC/panorama/$file" "$CONTENT_ADDON/$file"
+
+ log="$(cd "$RC_DIR" && ./resourcecompiler.exe -game "$WIN_CLIENT_DIR\\game\\csgo" -f \
+ -i "$WIN_CLIENT_DIR\\content\\csgo_addons\\$CS2_ADDON\\panorama\\${file//\//\\}" 2>&1 | tr -d '\r')"
+ if ! grep -q 'OK: 1 compiled, 0 failed' <<<"$log"; then
+ echo "$log" >&2
+ echo "Failed to compile $file" >&2
+ exit 1
+ fi
+
+ case "$file" in
+ *.xml) compiled="${file%.xml}.vxml_c" ;;
+ *.css) compiled="${file%.css}.vcss_c" ;;
+ esac
+ cp -f "$GAME_ADDON/$compiled" "$STAGE/panorama/$compiled"
+ echo "Compiled $compiled"
+done
+
+# --- Package -------------------------------------------------------------------------
+TARBALL="jRandomSkills-${TAG}.tar.gz"
+tar -czf "$OUT/$TARBALL" -C "$STAGE" addons panorama
+rm -rf "$STAGE"
+
+echo "Packaged $OUT/$TARBALL:"
+tar -tzf "$OUT/$TARBALL" | grep -v '/$'
+
+# --- Tag -------------------------------------------------------------------------
+if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
+ git tag -a "$TAG" -m "$TAG"
+fi
+git push origin "refs/tags/$TAG"
+
+# --- Release ---------------------------------------------------------------------
+API="$FORGEJO_URL/api/v1/repos/$OWNER/$REPO"
+AUTH=(-H "Authorization: token $FORGEJO_TOKEN")
+
+release_json="$(curl -fsS "${AUTH[@]}" "$API/releases/tags/$TAG" 2>/dev/null || true)"
+if [[ -z "$release_json" ]]; then
+ body="$(git log -1 --format=%B "$TAG")"
+ release_json="$(jq -n --arg tag "$TAG" --arg body "$body" \
+ '{tag_name: $tag, name: $tag, body: $body, draft: false, prerelease: false}' |
+ curl -fsS "${AUTH[@]}" -H "Content-Type: application/json" -X POST --data @- "$API/releases")"
+ echo "Created release $TAG"
+else
+ echo "Release $TAG already exists, replacing its attachment"
+fi
+release_id="$(jq -r '.id' <<<"$release_json")"
+
+existing_id="$(jq -r --arg n "$TARBALL" '.assets[]? | select(.name == $n) | .id' <<<"$release_json")"
+if [[ -n "$existing_id" ]]; then
+ curl -fsS "${AUTH[@]}" -X DELETE "$API/releases/$release_id/assets/$existing_id" >/dev/null
+fi
+curl -fsS "${AUTH[@]}" -X POST -F "attachment=@$OUT/$TARBALL" \
+ "$API/releases/$release_id/assets?name=$TARBALL" >/dev/null
+echo "Uploaded $TARBALL"
+
+echo
+echo "Download: $FORGEJO_URL/$OWNER/$REPO/releases/download/$TAG/$TARBALL"
+echo "Install: tar -xzf $TARBALL -C /srv/cs2/game/csgo"
+
+echo
+if [[ "$(compiled_sums)" != "$sums_before" ]]; then
+ echo "Workshop: the compiled Panorama layouts CHANGED. Players won't see the change until the"
+ echo " '$CS2_ADDON' addon is re-uploaded from Workshop Tools to item $WORKSHOP_ID:"
+ echo " https://steamcommunity.com/sharedfiles/filedetails/?id=$WORKSHOP_ID"
+else
+ echo "Workshop: compiled Panorama layouts unchanged; no Workshop update needed."
+fi