Skip to content

Commit

Permalink
feat: add invsim_spray_on_use ConVar
Browse files Browse the repository at this point in the history
  • Loading branch information
ianlucas committed Oct 22, 2024
1 parent 5f8863e commit 20975e9
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 100 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ Submit a PR or open an issue if you happen to know a workaround for them.
* **Type:** `bool`
* **Default:** `false`

#### `invsim_spray_on_use` ConVar

* Whether to try to apply spray when player presses use.
* **Type:** `bool`
* **Default:** `false`

#### `invsim_spray_cooldown` ConVar

* Cooldown in seconds between player sprays.
Expand Down
19 changes: 6 additions & 13 deletions gamedata/inventory-simulator.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,18 @@
"linux": "55 48 89 E5 41 56 49 89 F6 41 55 41 89 D5 41 54 49 89 FC 48 83 EC"
}
},
"Trace": {
"signatures": {
"library": "server",
"windows": "4C 8B DC 49 89 5B ? 49 89 6B ? 49 89 73 ? 57 41 56 41 57 48 81 EC ? ? ? ? 0F 57 C0",
"linux": "48 B8 ? ? ? ? ? ? ? ? 55 48 89 E5 41 57 41 56 49 89 D6 41 55"
}
},
"GameTraceManager": {
"CCSPlayerPawn_IsAbleToApplySpray": {
"signatures": {
"library": "server",
"windows": "48 8B 0D ? ? ? ? 48 8D 45 ? 48 89 44 24 ? 4C 8D 44 24 ? C7 44 24 ? ? ? ? ? 48 8D 54 24 ? 4C 8B CB",
"linux": "48 8D 05 ? ? ? ? F3 0F 58 8D ? ? ? ? 31 FF"
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 4C 89 74 24 ? 55 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 49 8B F1",
"linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 49 89 D5 41 54 48 8D 95"
}
},
"CCSPlayerPawn_IsAbleToApplySpray": {
"CCSPlayerController_ProcessUsercmds": {
"signatures": {
"library": "server",
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 4C 89 74 24 ? 55 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 49 8B F1",
"linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 49 89 D5 41 54 48 8D 95"
"windows": "48 8B C4 44 88 48 20 44 89 40 18 48 89 50 10 53",
"linux": "55 48 89 E5 41 57 41 56 41 89 D6 41 55 41 54 49 89 FC 53 48 83 EC 38"
}
}
}
13 changes: 13 additions & 0 deletions source/InventorySimulator/InventorySimulator.Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,17 @@ public bool IsPlayerPawnValid(CCSPlayerController player)
{
return player.PlayerPawn != null && player.PlayerPawn.Value != null && player.PlayerPawn.IsValid;
}

public bool IsPlayerUseCmdBusy(CCSPlayerController player)
{
if (player.PlayerPawn.Value?.IsBuyMenuOpen == true)
return true;
if (player.PlayerPawn.Value?.IsDefusing == true)
return true;
var weapon = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;
if (weapon?.DesignerName != "weapon_c4")
return false;
var c4 = weapon.As<CC4>();
return c4.IsPlantingViaUse;
}
}
1 change: 1 addition & 0 deletions source/InventorySimulator/InventorySimulator.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo
var player = @event.Userid;
if (player != null && IsPlayerHumanAndValid(player))
{
ClearPlayerUseCmd(player.SteamID);
RemovePlayerInventory(player.SteamID);
ClearInventoryManager();
}
Expand Down
7 changes: 5 additions & 2 deletions source/InventorySimulator/InventorySimulator.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public static class Extensions

public static readonly Func<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr> IsAbleToApplySprayFunc = IsAbleToApplySprayMemFunc.Invoke;

public static readonly MemoryFunctionWithReturn<IntPtr, IntPtr, int, bool, float> ProcessUsercmds = new(
GameData.GetSignature("CCSPlayerController_ProcessUsercmds"));

public static int ChangeSubclass(this CBasePlayerWeapon weapon, ushort itemDef)
{
return ChangeSubclassFunc(weapon.Handle, itemDef.ToString());
Expand All @@ -50,8 +53,8 @@ public static int SetBodygroup(this CCSPlayerPawn pawn, string group, int value)
return SetBodygroupFunc(pawn.Handle, group, value);
}

public static bool IsAbleToApplySpray(this CCSPlayerPawn pawn)
public static bool IsAbleToApplySpray(this CCSPlayerPawn pawn, IntPtr ptr = 0)
{
return IsAbleToApplySprayFunc(pawn.Handle, 0, 0, 0) == IntPtr.Zero;
return IsAbleToApplySprayFunc(pawn.Handle, ptr, 0, 0) == IntPtr.Zero;
}
}
39 changes: 16 additions & 23 deletions source/InventorySimulator/InventorySimulator.Give.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public void GivePlayerGraffiti(CCSPlayerController player, CPlayerSprayDecal spr
}
}

public void SprayPlayerGraffiti(CCSPlayerController player)
public unsafe void SprayPlayerGraffiti(CCSPlayerController player)
{
if (!IsPlayerHumanAndValid(player)) return;
var inventory = GetPlayerInventory(player);
Expand All @@ -255,31 +255,24 @@ public void SprayPlayerGraffiti(CCSPlayerController player)
var cameraServices = pawn.CameraServices;
var movementServices = pawn.MovementServices?.As<CCSPlayer_MovementServices>();
if (absOrigin == null || cameraServices == null || movementServices == null) return;
if (!pawn.IsAbleToApplySpray()) return;
var trace = stackalloc GameTrace[1];
if (!pawn.IsAbleToApplySpray((IntPtr)trace) || (IntPtr)trace == IntPtr.Zero) return;
player.ExecuteClientCommand("play sounds/items/spraycan_shake");
PlayerSprayCooldownManager[player.SteamID] = Now();
var trace = GameTraceManager.Trace(
new Vector(absOrigin.X, absOrigin.Y, absOrigin.Z + cameraServices.OldPlayerViewOffsetZ),
pawn.EyeAngles,
false,
true);
if (trace != null)
var endPos = Vector3toVector(trace->EndPos);
var normalPos = Vector3toVector(trace->Normal);
var sprayDecal = Utilities.CreateEntityByName<CPlayerSprayDecal>("player_spray_decal");
if (sprayDecal != null)
{
var endPos = trace.Value.Item1;
var normalPos = trace.Value.Item2;
var sprayDecal = Utilities.CreateEntityByName<CPlayerSprayDecal>("player_spray_decal");
if (sprayDecal != null)
{
sprayDecal.EndPos.Add(endPos);
sprayDecal.Start.Add(endPos);
sprayDecal.Left.Add(movementServices.Left);
sprayDecal.Normal.Add(normalPos);
sprayDecal.AccountID = (uint)player.SteamID;
sprayDecal.Player = item.Def;
sprayDecal.TintID = item.Tint;
sprayDecal.DispatchSpawn();
player.ExecuteClientCommand("play sounds/items/spraycan_spray");
}
sprayDecal.EndPos.Add(endPos);
sprayDecal.Start.Add(endPos);
sprayDecal.Left.Add(movementServices.Left);
sprayDecal.Normal.Add(normalPos);
sprayDecal.AccountID = (uint)player.SteamID;
sprayDecal.Player = item.Def;
sprayDecal.TintID = item.Tint;
sprayDecal.DispatchSpawn();
player.ExecuteClientCommand("play sounds/items/spraycan_spray");
}
}
}
60 changes: 1 addition & 59 deletions source/InventorySimulator/InventorySimulator.Hacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using NativeVector = CounterStrikeSharp.API.Modules.Utils.Vector;
using System.Numerics;
using System.Runtime.InteropServices;

Expand All @@ -27,64 +26,7 @@ public static class HackExtensions
}
}

// This is a hack by Nuko, adapted from UgurhanK/BaseBuilder.
public static class GameTraceManager
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate bool TraceFuncShape(
IntPtr GameTraceManager,
IntPtr vecStart,
IntPtr vecEnd,
IntPtr skip,
ulong mask,
byte a6,
GameTrace* pGameTrace);

private static readonly IntPtr TracePtr = NativeAPI.FindSignature(Addresses.ServerPath, GameData.GetSignature("Trace"));
private static readonly TraceFuncShape TraceFunc = Marshal.GetDelegateForFunctionPointer<TraceFuncShape>(TracePtr);
private static readonly IntPtr GameTraceManagerPtr = NativeAPI.FindSignature(Addresses.ServerPath, GameData.GetSignature("GameTraceManager"));
private static readonly IntPtr GameTraceManagerAddress = Address.GetAbsoluteAddress(GameTraceManagerPtr, 3, 7);

public static NativeVector Vector3toVector(Vector3 vec) => new(vec.X, vec.Y, vec.Z);

public static unsafe (NativeVector, NativeVector)? Trace(
NativeVector origin,
QAngle viewangles,
bool drawResult = false,
bool fromPlayer = false)
{
var forward = new NativeVector();
NativeAPI.AngleVectors(viewangles.Handle, forward.Handle, 0, 0);
var reach = 8192;
var endOrigin = new NativeVector(origin.X + forward.X * reach, origin.Y + forward.Y * reach, origin.Z + forward.Z * reach);
var distance = 50;
if (fromPlayer)
{
origin.X += forward.X * distance;
origin.Y += forward.Y * distance;
origin.Z += forward.Z * distance;
}
var trace = stackalloc GameTrace[1];
var result = TraceFunc(*(IntPtr*)GameTraceManagerAddress, origin.Handle, endOrigin.Handle, 0, 0x1C1003, 4, trace);
if (result)
{
return (
Vector3toVector(trace->EndPos),
Vector3toVector(trace->Normal));
}
return null;
}
}

public static class Address
{
static unsafe public IntPtr GetAbsoluteAddress(IntPtr addr, IntPtr offset, IntPtr size)
{
int code = *(int*)(addr + offset);
return addr + code + size;
}
}

// This is a hack by Nuko.
[StructLayout(LayoutKind.Explicit, Size = 0x44)]
public unsafe struct TraceHitboxData
{
Expand Down
25 changes: 25 additions & 0 deletions source/InventorySimulator/InventorySimulator.Hooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,38 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;

namespace InventorySimulator;

public partial class InventorySimulator
{
public HookResult OnProcessUsercmdsPost(DynamicHook hook)
{
if (!invsim_spray_on_use.Value)
return HookResult.Continue;

var player = hook.GetParam<CCSPlayerController>(0);
if ((player.Buttons & PlayerButtons.Use) != 0 && player.PlayerPawn.Value?.IsAbleToApplySpray() == true)
{
if (IsPlayerUseCmdBusy(player))
PlayerUseCmdBlockManager[player.SteamID] = true;
if (PlayerUseCmdManager.TryGetValue(player.SteamID, out var timer))
timer.Kill();
PlayerUseCmdManager[player.SteamID] = AddTimer(0.1f, () =>
{
if (PlayerUseCmdBlockManager.ContainsKey(player.SteamID))
PlayerUseCmdBlockManager.Remove(player.SteamID, out var _);
else if (player.IsValid && !IsPlayerUseCmdBusy(player))
player.ExecuteClientCommandFromServer("css_spray");
});
}

return HookResult.Continue;
}

public HookResult OnGiveNamedItemPost(DynamicHook hook)
{
var className = hook.GetParam<string>(1);
Expand Down
6 changes: 6 additions & 0 deletions source/InventorySimulator/InventorySimulator.Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ public void RemovePlayerInventory(ulong steamId)
}
}

public void ClearPlayerUseCmd(ulong steamId)
{
PlayerUseCmdManager.Remove(steamId, out var _);
PlayerUseCmdBlockManager.Remove(steamId, out var _);
}

public PlayerInventory GetPlayerInventory(CCSPlayerController player)
{
if (PlayerInventoryManager.TryGetValue(player.SteamID, out var inventory))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using System.Text.Json.Serialization;

Expand Down
6 changes: 4 additions & 2 deletions source/InventorySimulator/InventorySimulator.State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
using System.Collections.Concurrent;

namespace InventorySimulator;
Expand All @@ -15,6 +16,7 @@ public partial class InventorySimulator
{
public readonly FakeConVar<bool> invsim_stattrak_ignore_bots = new("invsim_stattrak_ignore_bots", "Whether to ignore StatTrak increments for bot kills.", true);
public readonly FakeConVar<bool> invsim_spraychanger_enabled = new("invsim_spraychanger_enabled", "Whether to change player vanilla spray if they have a graffiti equipped.", false);
public readonly FakeConVar<bool> invsim_spray_on_use = new("invsim_spray_on_use", "Whether to try to apply spray when player presses use.", false);
public readonly FakeConVar<bool> invsim_ws_enabled = new("invsim_ws_enabled", "Whether players can refresh their inventory using !ws.", false);
public readonly FakeConVar<bool> invsim_ws_print_full_url = new("invsim_ws_print_full_url", "Whether print full URL when the player uses !ws.", true);
public readonly FakeConVar<int> invsim_minmodels = new("invsim_minmodels", "Allows agents or use specific models for each team.", 0, flags: ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 2));
Expand All @@ -32,13 +34,13 @@ public partial class InventorySimulator
public readonly ConcurrentDictionary<ulong, long> PlayerSprayCooldownManager = [];
public readonly ConcurrentDictionary<ulong, (CCSPlayerController?, PlayerInventory)> PlayerOnTickInventoryManager = [];
public readonly ConcurrentDictionary<ulong, PlayerInventory> PlayerInventoryManager = [];
public readonly ConcurrentDictionary<ulong, bool> PlayerGiveNextSpawn = [];
public readonly ConcurrentDictionary<ulong, Timer> PlayerUseCmdManager = [];
public readonly ConcurrentDictionary<ulong, bool> PlayerUseCmdBlockManager = [];

public readonly PlayerInventory EmptyInventory = new();

public static readonly string InventoryFileDir = "csgo/addons/counterstrikesharp/configs/plugins/InventorySimulator";
public static readonly ulong MinimumCustomItemID = 68719476736;

public ulong NextItemId = MinimumCustomItemID;
public int NextFadeSeed = 3;
}
4 changes: 4 additions & 0 deletions source/InventorySimulator/InventorySimulator.Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using System.Numerics;
using NativeVector = CounterStrikeSharp.API.Modules.Utils.Vector;

namespace InventorySimulator;

Expand Down Expand Up @@ -43,4 +45,6 @@ public static float ViewAsFloat<T>(T value) where T : struct
? GameRulesProxy?.GameRules
: null
);

public static NativeVector Vector3toVector(Vector3 vec) => new(vec.X, vec.Y, vec.Z);
}
1 change: 1 addition & 0 deletions source/InventorySimulator/InventorySimulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public override void Load(bool hotReload)
RegisterEventHandler<EventPlayerConnect>(OnPlayerConnect);
RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull);
RegisterEventHandler<EventPlayerSpawn>(OnPlayerSpawn);
Extensions.ProcessUsercmds.Hook(OnProcessUsercmdsPost, HookMode.Post);
VirtualFunctions.GiveNamedItemFunc.Hook(OnGiveNamedItemPost, HookMode.Post);
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeathPre, HookMode.Pre);
RegisterEventHandler<EventRoundMvp>(OnRoundMvpPre, HookMode.Pre);
Expand Down

0 comments on commit 20975e9

Please sign in to comment.