v1.2.4.b3

#### Compatibility
* **CounterStrikeSharp v1.0.375** - after the CS2 update, RayTraceImpl stopped working. All traces now use CounterStrikeSharp's native `Trace` API, so **RayTrace-CSS-API and RayTrace-MM are no longer required**. `RayTraceApi.dll` was removed from the project and the Ray-Trace step was removed from the README install guide.
* **Gamedata** - CS2 1.41.8.3 broke the Windows `HEGrenadeProjectile_CreateFunc`, `CMolotovProjectile_CreateFunc` and `SnapViewAngles` signatures, and the Linux `SmokeGrenadeProjectile_CreateFunc` signature. `INetworkGameServer_Slots` moved from 584 to 616 on both platforms; the old value read a garbage size and pointer without throwing. All signatures now match exactly one function on both platforms, and the offsets were confirmed at runtime.

#### Skills
* **No Recoil** - the client received `weapon_accuracy_nospread`, but the server still applied spread, so the crosshair looked straight while bullets spread. The weapon's spread and inaccuracy values are now also zeroed on the server for that tick. Weapon data is shared per weapon type, so a weapon type is only patched while no other living player holds the same type; the values are restored on the next tick, at map end and on unload.
* **Long Zeus** - kills went through suicide, so they did not count as Zeus kills and the ragdoll was marked `world`. The suicide damage is now rewritten to the attacker's Zeus with shock damage, so the engine credits the kill natively (killfeed, kill sound, scoreboard, ragdoll). The victim's body also gets the electric shock effect, which the engine only plays on real Taser hits.
* **Long Knife** - long-range slashes now draw a tracer to where they hit.
* **Fragile Bomb** - the bomb position was only stored by the `bomb_planted` event, which is sent only to skills active at that moment, so receiving the skill after the plant never detected hits. The planted bomb is now looked up when needed. Hits were also measured from the impact point, so shots passing through the bomb and landing behind it were missed; the bullet's path is now checked instead.
* **Tripwire** - wires stayed visible after the skill changed. Beams were hidden from clients in the same tick they were cleared, so the "hide" update never reached the client. Beams are no longer hidden from clients before removal.

#### Reliability
* **Skill distribution** - an exception thrown during skill assignment aborted the round's distribution silently. It is now caught, logged and retried. Spectators without a pawn no longer count as connected players, which had caused unnecessary retries.
This commit is contained in:
ByDexter 2026-09-25 03:34:10 +03:00
parent 20dafd2a7c
commit 71ef1acbe9
24 changed files with 446 additions and 159 deletions

View file

@ -223,14 +223,9 @@ Buying a server on pukawka? Use my [referral code](https://pukawka.pl/pp,juzlus.
Game csgo Game csgo
``` ```
3. Install **CounterStrikeSharp**. 3. Install **CounterStrikeSharp**.
- Download [CounterStrikeSharp-With-Runtime](https://github.com/roflmuffin/CounterStrikeSharp/releases). - Download [CounterStrikeSharp-With-Runtime](https://github.com/roflmuffin/CounterStrikeSharp/releases) (v1.0.375 or newer).
- Extract it to the `C2Server/game/csgo/` folder. - Extract it to the `C2Server/game/csgo/` folder.
4. Install **Ray-Trace** 4. Install **jRandomSkills**
- Download [RayTrace-CSS-API](https://github.com/FUNPLAY-pro-CS2/Ray-Trace/releases)
- Extract folder `conterstrikesharp` to the `CS2Server/game/csgo/addons/` folder.
- Download [RayTrace-MM](https://github.com/FUNPLAY-pro-CS2/Ray-Trace/releases)
- Extract it to the `CS2Server/game/csgo/addons/` folder.
5. Install **jRandomSkills**
- Download [jRandomSkills](https://github.com/Juzlus/jRandomSkills/releases) - Download [jRandomSkills](https://github.com/Juzlus/jRandomSkills/releases)
## </> Server Commands ## </> Server Commands

View file

@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.373" /> <PackageReference Include="CounterStrikeSharp.API" Version="1.0.375" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -19,18 +19,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.373" /> <PackageReference Include="CounterStrikeSharp.API" Version="1.0.375" />
<PackageReference Include="MaxMind.Db" Version="5.1.0"> <PackageReference Include="MaxMind.Db" Version="5.1.0">
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="RayTraceApi">
<HintPath>libs/RayTraceApi.dll</HintPath>
</Reference>
</ItemGroup>
<!-- Relative path to the WASDMenuAPI source code --> <!-- Relative path to the WASDMenuAPI source code -->
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WASDMenuAPI - SRC Files\WASDMenuAPI.csproj" /> <ProjectReference Include="..\WASDMenuAPI - SRC Files\WASDMenuAPI.csproj" />

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

@ -31,7 +31,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"; public override string ModuleVersion => "1.2.4.b3";
public override void Load(bool hotReload) public override void Load(bool hotReload)
{ {
@ -280,9 +280,6 @@ namespace src
{ "WASDMenuAPI", "./WASDMenuAPI.dll" }, { "WASDMenuAPI", "./WASDMenuAPI.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 = RayTrace.WorldOnlyMask | (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

@ -42,6 +42,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();
@ -50,6 +51,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 = RayTrace.WorldOnlyMask;
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 = RayTrace.WorldOnlyMask;
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(() =>

View file

@ -1,45 +1,44 @@
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"); public const ulong WorldOnlyMask = (ulong)(Contents.Solid | Contents.Window | Contents.PassBullets);
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 +52,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 +120,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 +128,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)
{ {
@ -260,6 +222,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 +247,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

@ -482,9 +482,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)

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": {