Merge upstream v1.2.4.b3: native CSS traces, drop Ray-Trace

Upstream replaced RayTraceApi/RayTraceImpl with CounterStrikeSharp's own Trace API
(CSS 1.0.375), so RayTrace-MM and RayTrace-CSS-API are no longer needed. Also brings the
CS2 1.41.8.3 gamedata fixes and the No Recoil, Long Zeus, Long Knife, Fragile Bomb and
Tripwire fixes.

Kept from this fork: Panorama skill card and target menu (PanoramaManager), css_setoverride,
WASDMenuAPI removed, no tracked "Server Files" build output. Version 1.2.4.b3-panorama1.
This commit is contained in:
Astra 2026-09-25 12:20:39 +01:00
commit d6f1bbb24f
23 changed files with 524 additions and 146 deletions

21
.github/en.md vendored
View file

@ -424,6 +424,27 @@ This plugin uses content from the following projects:
## 📋 Changelog ## 📋 Changelog
<details>
<summary><b>v1.2.4.b3</b></summary>
- #### 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!**
</details>
<details> <details>
<summary><b>v1.2.4.b2</b></summary> <summary><b>v1.2.4.b2</b></summary>

21
.github/pl.md vendored
View file

@ -424,6 +424,27 @@ Plugin korzysta z zawartości następujących projektów:
## 📋 Lista Zmian ## 📋 Lista Zmian
<details>
<summary><b>v1.2.4.b3</b></summary>
- #### 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!**
</details>
<details> <details>
<summary><b>v1.2.4.b2</b></summary> <summary><b>v1.2.4.b2</b></summary>

View file

@ -441,6 +441,27 @@ Plugin korzysta z zawartości następujących projektów:
## 📋 Lista Zmian ## 📋 Lista Zmian
<details>
<summary><b>v1.2.4.b3</b></summary>
- #### 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!**
</details>
<details> <details>
<summary><b>v1.2.4.b2</b></summary> <summary><b>v1.2.4.b2</b></summary>

View file

@ -441,6 +441,27 @@ This plugin uses content from the following projects:
## 📋 Changelog ## 📋 Changelog
<details>
<summary><b>v1.2.4.b3</b></summary>
- #### 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!**
</details>
<details> <details>
<summary><b>v1.2.4.b2</b></summary> <summary><b>v1.2.4.b2</b></summary>

View file

@ -30,12 +30,6 @@
<PackageReference Include="PanoramaManager" Version="0.4.1" /> <PackageReference Include="PanoramaManager" Version="0.4.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="RayTraceApi">
<HintPath>libs/RayTraceApi.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

View file

@ -3,20 +3,20 @@
"signatures": { "signatures": {
"library": "server", "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", "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": { "HEGrenadeProjectile_CreateFunc": {
"signatures": { "signatures": {
"library": "server", "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" "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": { "CMolotovProjectile_CreateFunc": {
"signatures": { "signatures": {
"library": "server", "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" "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": { "SnapViewAngles": {
"signatures": { "signatures": {
"library": "server", "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" "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": { "INetworkGameServer_Slots": {
"offsets": { "offsets": {
"windows": 584, "windows": 616,
"linux": 584 "linux": 616
} }
}, },
"INetworkServerService_GetIGameServer": { "INetworkServerService_GetIGameServer": {

View file

@ -44,7 +44,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]"; public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; 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 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) public override void Load(bool hotReload)
{ {
@ -303,9 +303,6 @@ namespace src
{ "Newtonsoft Json", "./Newtonsoft.Json.dll" }, { "Newtonsoft Json", "./Newtonsoft.Json.dll" },
{ "MaxMind", "./MaxMind.Db.dll" }, { "MaxMind", "./MaxMind.Db.dll" },
{ "GeoLite2", "./packages/GeoLite2-Country.mmdb" }, { "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" } { "jRandomSkills gamedata", "./../../gamedata/jRandomSkills.gamedata.json" }
}; };

View file

@ -98,6 +98,7 @@ namespace src.player
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
Instance.SkillAction("Fortnite", "OnTakeDamage", args); Instance.SkillAction("Fortnite", "OnTakeDamage", args);
SkillUtils.ApplyNativeKill(entity, info);
return HookResult.Continue; return HookResult.Continue;
} }
} }

View file

@ -6,7 +6,6 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages; using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.player.skills; using src.player.skills;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -108,6 +107,7 @@ namespace src.player
TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre)); TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre));
TryUnhook(() => Instance.UnhookUserMessage(208, PlayerMakeSound)); TryUnhook(() => Instance.UnhookUserMessage(208, PlayerMakeSound));
TryUnhook(() => Instance.RemoveListener<CheckTransmit>(CheckTransmit)); TryUnhook(() => Instance.RemoveListener<CheckTransmit>(CheckTransmit));
TryUnhook(NoRecoil.RestoreSpread);
} }
private static void TryUnhook(Action unhook) private static void TryUnhook(Action unhook)
@ -507,6 +507,7 @@ namespace src.player
private static void OnTick() private static void OnTick()
{ {
ReportStall(); ReportStall();
NoRecoil.RestoreSpread();
long perfStart = PerfLog.Start(); long perfStart = PerfLog.Start();
lock (setLock) 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 eyePos = new(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z);
Vector endPos = eyePos + SkillUtils.GetForwardVector(pawn.EyeAngles) * 80; 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; ulong contents = 0;
var result = RayTrace.TraceShape(player, eyePos, endPos, mask, contents); var result = RayTrace.TraceShape(player, eyePos, endPos, mask, contents);

View file

@ -45,6 +45,7 @@ namespace src.player
private static void OnMapStart(string mapName) private static void OnMapStart(string mapName)
{ {
skills.NoRecoil.ForgetSpread();
PlayerManager.SetServerActive(true); PlayerManager.SetServerActive(true);
Instance.GameRules = null; Instance.GameRules = null;
Event.OnMapChange(); Event.OnMapChange();
@ -53,6 +54,8 @@ namespace src.player
private static void OnMapEnd() private static void OnMapEnd()
{ {
skills.NoRecoil.RestoreSpread();
skills.NoRecoil.ForgetSpread();
PerfLog.Info("===== MAP END (clean map change) ====="); PerfLog.Info("===== MAP END (clean map change) =====");
Debug.WriteToDebug("===== MAP END (clean map change) ====="); Debug.WriteToDebug("===== MAP END (clean map change) =====");
BotManager.Stop(); BotManager.Stop();

View file

@ -7,7 +7,6 @@ using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages; using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.player.skills; using src.player.skills;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -98,14 +97,14 @@ namespace src.player
{ {
lock (setLock) lock (setLock)
{ {
bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true; bool isWarmup = IsWarmupPeriod();
isTransmitRegistered = false; isTransmitRegistered = false;
setSkillRetries = 0; setSkillRetries = 0;
SkillUtils.ClearKillCredits(); SkillUtils.ClearKillCredits();
SkillUtils.ClearCurses(); SkillUtils.ClearCurses();
Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); 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); var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index);
if (skillPlayer == null) continue; if (skillPlayer == null) continue;
@ -115,18 +114,21 @@ namespace src.player
Instance.RemoveListener<CheckTransmit>(CheckTransmit); Instance.RemoveListener<CheckTransmit>(CheckTransmit);
int freezetime = SkillUtils.CvarValue("mp_freezetime", 0); 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(); setSkillTimer?.Kill();
if (isWarmup) if (isWarmup)
{ {
setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); 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; 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); 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; return HookResult.Continue;
} }
} }
@ -293,7 +295,22 @@ namespace src.player
private static void SetSkill() private static void SetSkill()
{ {
long perfStart = PerfLog.Start(); 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); PerfLog.End("SetSkill total", perfStart, 2.0);
} }
@ -472,10 +489,46 @@ namespace src.player
private static int CountConnectedPlayers() 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; } 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() private static void SetSkillCore()
{ {
setSkillTimer = null; setSkillTimer = null;
@ -484,7 +537,7 @@ namespace src.player
if (Instance == null) return; if (Instance == null) return;
// GameRules null = not ready; keep polling so skills land right after warmup ends. // 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) if (++gameRulesPolls % 10 == 0)
Debug.WriteToDebug($"SetSkill waiting: gameRules={(Instance.GameRules == null ? "null" : "warmup")}, poll {gameRulesPolls}.", DebugCategory.Skill); Debug.WriteToDebug($"SetSkill waiting: gameRules={(Instance.GameRules == null ? "null" : "warmup")}, poll {gameRulesPolls}.", DebugCategory.Skill);

View file

@ -4,7 +4,6 @@ using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers; using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Drawing; 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 eyePos = new(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z + playerPawn.ViewOffset.Z);
Vector endPos = eyePos + SkillUtils.GetForwardVector(playerPawn.V_angle) * 4096f; 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); var result = RayTrace.TraceShape(player, eyePos, endPos, mask);
if (result.HasValue && result.Value.HitWorld(out _)) 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; 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; 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); var result = RayTrace.TraceShape(player, cameraVector, endPos, mask);
if (result.HasValue && result.Value.HitWorld(out _)) if (result.HasValue && result.Value.HitWorld(out _))

View file

@ -29,10 +29,16 @@ namespace src.player.skills
} }
public static void BombPlanted(EventBombPlanted _) public static void BombPlanted(EventBombPlanted _)
{
plantedC4 = FindPlantedC4();
}
private static Vector? FindPlantedC4()
{ {
var plantedBomb = Utilities.FindAllEntitiesByDesignerName<CPlantedC4>("planted_c4").FirstOrDefault(); var plantedBomb = Utilities.FindAllEntitiesByDesignerName<CPlantedC4>("planted_c4").FirstOrDefault();
if (plantedBomb == null || !plantedBomb.IsValid || plantedBomb.AbsOrigin == null) return; if (plantedBomb == null || !plantedBomb.IsValid || plantedBomb.AbsOrigin == null) return null;
plantedC4 = new(plantedBomb.AbsOrigin.X, plantedBomb.AbsOrigin.Y, plantedBomb.AbsOrigin.Z); if (!plantedBomb.BombTicking || plantedBomb.BombDefused) return null;
return new(plantedBomb.AbsOrigin.X, plantedBomb.AbsOrigin.Y, plantedBomb.AbsOrigin.Z);
} }
private static void RemoveBomb() private static void RemoveBomb()
@ -49,14 +55,21 @@ namespace src.player.skills
if (lastTick == Server.TickCount) return; if (lastTick == Server.TickCount) return;
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid || plantedC4 == null) return; if (player == null || !player.IsValid) return;
var pos = new Vector(@event.X, @event.Y, @event.Z);
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) return; 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; return;
lastTick = Server.TickCount; 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"]); 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 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; public int MaxBombHealth { get; set; } = maxBombHealth;

View file

@ -15,6 +15,7 @@ namespace src.player.skills
private const string victimSound = "Player.DamageBody.Victim"; private const string victimSound = "Player.DamageBody.Victim";
private const string heavyHitSound = "Weapon_Knife.Hit.Heavy.Flesh"; private const string heavyHitSound = "Weapon_Knife.Hit.Heavy.Flesh";
private const string lightHitSound = "Weapon_Knife.Hit.Light.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 static bool hooked = false;
private const int actionCode = 503; private const int actionCode = 503;
@ -30,6 +31,7 @@ namespace src.player.skills
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(tracerParticle);
} }
public static void NewRound() public static void NewRound()
@ -113,6 +115,9 @@ namespace src.player.skills
if (result == null || !result.HasValue) if (result == null || !result.HasValue)
return; return;
if (result.Value.Distance() > 70)
SkillUtils.CreateTracer(player, tracerParticle, result.Value);
if (!result.Value.HitPlayer(out CCSPlayerController? target) || target == null) if (!result.Value.HitPlayer(out CCSPlayerController? target) || target == null)
return; return;

View file

@ -10,10 +10,12 @@ namespace src.player.skills
public class LongZeus : ISkill public class LongZeus : ISkill
{ {
private const Skills skillName = Skills.LongZeus; private const Skills skillName = Skills.LongZeus;
private const string shockParticle = "particles/blood_impact/impact_taser_bodyfx.vpcf";
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
Instance.AddToManifest(shockParticle);
} }
public unsafe static void WeaponFire(EventWeaponFire @event) public unsafe static void WeaponFire(EventWeaponFire @event)
@ -40,7 +42,12 @@ namespace src.player.skills
if (target.Handle == player.Handle) return; if (target.Handle == player.Handle) return;
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire") && player.Team == target.Team) return; if (!SkillsInfo.GetValue<bool>(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) public static void EnableSkill(CCSPlayerController player)

View file

@ -17,6 +17,27 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, byte> holders = []; private static readonly ConcurrentDictionary<uint, byte> holders = [];
private static readonly ConcurrentDictionary<uint, string> originalNoSpreadValues = []; private static readonly ConcurrentDictionary<uint, string> 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<nint, SpreadPatch> spreadPatches = [];
private static readonly List<SpreadPatch> activePatches = [];
private static readonly Dictionary<nint, CCSWeaponBaseVData> wantedVData = [];
private static readonly HashSet<nint> blockedVData = [];
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
@ -83,6 +104,106 @@ namespace src.player.skills
catch { } 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<CCSWeaponBase>().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) public static void WeaponFire(EventWeaponFire @event)
{ {
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);

View file

@ -1,8 +1,7 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils; using jRandomSkills.src.utils;
using RayTraceAPI;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Drawing; using System.Drawing;
@ -22,7 +21,7 @@ namespace src.player.skills
private static readonly List<Flight> flights = []; private static readonly List<Flight> flights = [];
private static readonly object flightLock = new(); private static readonly object flightLock = new();
private static readonly Color tracerColor = Color.FromArgb(170, 255, 226, 140); 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() public static void LoadSkill()
{ {

View file

@ -1,7 +1,6 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using RayTraceAPI;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Drawing; using System.Drawing;
@ -104,19 +103,7 @@ namespace src.player.skills
private static void DestroyWire(uint beamIndex) private static void DestroyWire(uint beamIndex)
{ {
var beam = Utilities.GetEntityFromIndex<CBeam>((int)beamIndex); EntityManager.DestroyBeam(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);
} }
public static void UseSkill(CCSPlayerController player) 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 rightDir = SkillUtils.GetForwardVector(new QAngle(0, yaw - 90, 0));
Vector leftDir = 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 rightHit = RayTrace.TraceShape(player, origin, origin + rightDir * maxDistance, wallMask, 0);
var leftHit = RayTrace.TraceShape(player, origin, origin + leftDir * maxDistance, wallMask, 0); var leftHit = RayTrace.TraceShape(player, origin, origin + leftDir * maxDistance, wallMask, 0);

View file

@ -1,30 +1,55 @@
using RayTraceAPI; using CounterStrikeSharp.API.Modules.Utils;
using System.Numerics; using System.Numerics;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace jRandomSkills.src.utils 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 StartPosX;
public float StartPosY = startPos.Y; public float StartPosY;
public float StartPosZ = startPos.Z; public float StartPosZ;
public float EndPosX = result.EndPosX; public float EndPosX;
public float EndPosY = result.EndPosY; public float EndPosY;
public float EndPosZ = result.EndPosZ; public float EndPosZ;
public nint HitEntity = result.HitEntity; public nint HitEntity;
public float Fraction = result.Fraction; public float Fraction;
public int AllSolid = result.AllSolid; public int AllSolid;
public float NormalX = result.NormalX; public float NormalX;
public float NormalY = result.NormalY; public float NormalY;
public float NormalZ = result.NormalZ; public float NormalZ;
public ulong InteractsWith = mask; public ulong InteractsWith;
public ulong InteractsExclude = contents; public ulong InteractsExclude;
public bool DrawBeam = drawBeam; 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 StartPos => new(StartPosX, StartPosY, StartPosZ);
public readonly Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ); public readonly Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ);

View file

@ -1,10 +1,12 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.player; using src.player;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Drawing; using System.Drawing;
using System.Runtime.InteropServices;
using static src.jRandomSkills; using static src.jRandomSkills;
namespace src.utils 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<CParticleSystem>("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<CParticleSystem>("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) public static CDynamicProp? CreateTrackedDynamicProp(uint playerIndex, string designerName = "prop_dynamic", string? trackAs = null)
{ {
try try
@ -295,7 +358,7 @@ namespace src.utils
public static bool SuppressKills = false; 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); bool wasTracked = trackedEntities.TryRemove(entityIndex, out var removed);
@ -310,7 +373,8 @@ namespace src.utils
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex); var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex);
if (entity != null && entity.IsValid) 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. // Detach first so no follower is left on a freed parent.
entity.AcceptInput("ClearParent"); entity.AcceptInput("ClearParent");
entity.AddEntityIOEvent("Kill", entity, delay: delay); entity.AddEntityIOEvent("Kill", entity, delay: delay);
@ -346,7 +410,7 @@ namespace src.utils
LogEntityError($"DestroyBeam {entityIndex}: {ex.Message}"); LogEntityError($"DestroyBeam {entityIndex}: {ex.Message}");
} }
bool killed = DestroyEntity(entityIndex); bool killed = DestroyEntity(entityIndex, hideFromTransmit: false);
if (!SuppressKills) if (!SuppressKills)
Server.NextFrame(() => Server.NextFrame(() =>
@ -403,7 +467,7 @@ namespace src.utils
DestroyAllTracked(); DestroyAllTracked();
} }
private static void ScheduleAutoDestroy(uint entityIndex, float? seconds) public static void ScheduleAutoDestroy(uint entityIndex, float? seconds)
{ {
if (seconds is not > 0) return; if (seconds is not > 0) return;
Instance?.AddTimer(seconds.Value, () => DestroyEntity(entityIndex), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); Instance?.AddTimer(seconds.Value, () => DestroyEntity(entityIndex), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);

View file

@ -1,45 +1,43 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils; using jRandomSkills.src.utils;
using RayTraceAPI;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
using TraceOptions = RayTraceAPI.TraceOptions;
using TraceResult = RayTraceAPI.TraceResult;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace src.utils namespace src.utils
{ {
public static class RayTrace public static class RayTrace
{ {
private static PluginCapability<CRayTraceInterface> RayTraceInterface { get; } = new("raytrace:craytraceinterface");
private static bool missingModuleLogged; private static bool traceFailureLogged;
private static CRayTraceInterface? GetInterface() public static bool IsAvailable => true;
private static bool TryTraceHull(Vector startPos, Vector endPos, Vector mins, Vector maxs, CBaseEntity? ignore, ulong mask, ulong contents, out TraceResult result)
{ {
try try
{ {
var rayTrace = RayTraceInterface.Get(); result = Trace.TraceHullShape(startPos, endPos, mins, maxs, ignore, new TraceOptions
if (rayTrace != null) return rayTrace; {
InteractsWith = (Contents)mask,
InteractsExclude = (Contents)contents,
});
return true;
} }
catch { } catch (Exception ex)
if (!missingModuleLogged)
{ {
missingModuleLogged = true; result = default;
Server.PrintToConsole("[jRandomSkills] RayTrace module not found - skills that need it do nothing: " + if (!traceFailureLogged)
"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"); 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) public static CustomTraceResult? TraceShape(CCSPlayerController player, Vector startPos, Vector endPos, ulong? mask = null, ulong? contents = null)
{ {
if (player == null || !player.IsValid) return null; if (player == null || !player.IsValid) return null;
@ -53,54 +51,39 @@ namespace src.utils
playerPawn.CBodyComponent?.SceneNode == null) playerPawn.CBodyComponent?.SceneNode == null)
return null; return null;
var rayTrace = GetInterface();
if (rayTrace == null)
return null;
if (mask == null) if (mask == null)
{ {
try try
{ {
if (playerPawn.Collision?.CollisionAttribute != null) if (playerPawn.Collision?.CollisionAttribute != null)
{ {
mask = playerPawn.Collision.CollisionAttribute.InteractsWith | (ulong)InteractionLayers.Hitboxes; mask = playerPawn.Collision.CollisionAttribute.InteractsWith | (ulong)Contents.Hitbox;
mask &= ~(ulong)InteractionLayers.PlayerClip; mask &= ~(ulong)Contents.PlayerClip;
} }
else else
mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Hitboxes); mask = (ulong)(Contents.Solid | Contents.Hitbox);
} }
catch catch
{ {
mask = (ulong)(InteractionLayers.Solid | InteractionLayers.Hitboxes); mask = (ulong)(Contents.Solid | Contents.Hitbox);
} }
} }
contents ??= 0; contents ??= 0;
bool drawBeam = Config.LoadedConfig.TraceRayBeam; 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 mins = new(-0.5f, -0.5f, -0.5f);
Vector maxs = new(0.5f, 0.5f, 0.5f); Vector maxs = new(0.5f, 0.5f, 0.5f);
TraceResult result = default; if (!TryTraceHull(startPos, endPos, mins, maxs, playerPawn, (ulong)mask, (ulong)contents, out var result))
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}");
return null; 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) public static CustomTraceResult? EyeTrace(CCSPlayerController player)
@ -136,10 +119,6 @@ namespace src.utils
playerPawn.CBodyComponent?.SceneNode == null) playerPawn.CBodyComponent?.SceneNode == null)
return null; return null;
var rayTrace = GetInterface();
if (rayTrace == null)
return null;
Vector safeMins = mins ?? playerPawn.Collision.Mins; Vector safeMins = mins ?? playerPawn.Collision.Mins;
Vector safeMaxs = maxs ?? playerPawn.Collision.Maxs; Vector safeMaxs = maxs ?? playerPawn.Collision.Maxs;
@ -148,26 +127,8 @@ namespace src.utils
bool drawBeam = Config.LoadedConfig.TraceRayBeam; bool drawBeam = Config.LoadedConfig.TraceRayBeam;
TraceResult result = default; if (!TryTraceHull(startPos, endPos, safeMins, safeMaxs, playerPawn, safeMask, safeContents, out var result))
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)
{
return null; return null;
}
if (drawBeam) if (drawBeam)
{ {
@ -246,6 +207,9 @@ namespace src.utils
beam.EndPos.Z = end.Z; beam.EndPos.Z = end.Z;
beam.DispatchSpawn(); beam.DispatchSpawn();
EntityManager.RegisterEntity(beam.Index, int.MaxValue, "beam");
EntityManager.ScheduleAutoDestroy(beam.Index, 10);
} }
public static float Distance(this CustomTraceResult result) public static float Distance(this CustomTraceResult result)
@ -260,6 +224,12 @@ namespace src.utils
public static bool HitEntityByDesignerName<T>(this CustomTraceResult result, out T? entity, string designerName, DesignerNameMatchType matchType = DesignerNameMatchType.Equals) where T : CEntityInstance public static bool HitEntityByDesignerName<T>(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); T? val = (T?)Activator.CreateInstance(typeof(T), result.HitEntity);
if ((object?)val != null && matchType switch if ((object?)val != null && matchType switch
{ {
@ -279,6 +249,12 @@ namespace src.utils
public static bool HitEntity(this CustomTraceResult result, out CBaseEntity? entity) public static bool HitEntity(this CustomTraceResult result, out CBaseEntity? entity)
{ {
if (result.HitEntity == IntPtr.Zero)
{
entity = null;
return false;
}
CEntityInstance entityInstance = new(result.HitEntity); CEntityInstance entityInstance = new(result.HitEntity);
if (string.IsNullOrEmpty(entityInstance.DesignerName)) if (string.IsNullOrEmpty(entityInstance.DesignerName))
{ {

View file

@ -480,9 +480,48 @@ namespace src.utils
return true; 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() public static void ClearKillCredits()
{ {
pendingKillCredits.Clear(); pendingKillCredits.Clear();
pendingNativeKills.Clear();
}
private static readonly ConcurrentDictionary<uint, (uint AttackerHandle, uint InflictorHandle, DamageTypes_t DamageType, int ExpiryTick)> 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<string> bulletWeapons = new(StringComparer.Ordinal) private static readonly HashSet<string> bulletWeapons = new(StringComparer.Ordinal)