diff --git a/README.md b/README.md index e22ea1a..982afdf 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/gamedata/inventory-simulator.json b/gamedata/inventory-simulator.json index 86d8ae0..67b17e4 100644 --- a/gamedata/inventory-simulator.json +++ b/gamedata/inventory-simulator.json @@ -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" } } } diff --git a/source/InventorySimulator/InventorySimulator.Entity.cs b/source/InventorySimulator/InventorySimulator.Entity.cs index 0c76fc6..674acca 100644 --- a/source/InventorySimulator/InventorySimulator.Entity.cs +++ b/source/InventorySimulator/InventorySimulator.Entity.cs @@ -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(); + return c4.IsPlantingViaUse; + } } diff --git a/source/InventorySimulator/InventorySimulator.Events.cs b/source/InventorySimulator/InventorySimulator.Events.cs index 512a825..6d53e72 100644 --- a/source/InventorySimulator/InventorySimulator.Events.cs +++ b/source/InventorySimulator/InventorySimulator.Events.cs @@ -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(); } diff --git a/source/InventorySimulator/InventorySimulator.Extensions.cs b/source/InventorySimulator/InventorySimulator.Extensions.cs index f08d2ce..5a1da7a 100644 --- a/source/InventorySimulator/InventorySimulator.Extensions.cs +++ b/source/InventorySimulator/InventorySimulator.Extensions.cs @@ -35,6 +35,9 @@ public static class Extensions public static readonly Func IsAbleToApplySprayFunc = IsAbleToApplySprayMemFunc.Invoke; + public static readonly MemoryFunctionWithReturn ProcessUsercmds = new( + GameData.GetSignature("CCSPlayerController_ProcessUsercmds")); + public static int ChangeSubclass(this CBasePlayerWeapon weapon, ushort itemDef) { return ChangeSubclassFunc(weapon.Handle, itemDef.ToString()); @@ -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; } } diff --git a/source/InventorySimulator/InventorySimulator.Give.cs b/source/InventorySimulator/InventorySimulator.Give.cs index 787e4cc..9173af3 100644 --- a/source/InventorySimulator/InventorySimulator.Give.cs +++ b/source/InventorySimulator/InventorySimulator.Give.cs @@ -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); @@ -255,31 +255,24 @@ public void SprayPlayerGraffiti(CCSPlayerController player) var cameraServices = pawn.CameraServices; var movementServices = pawn.MovementServices?.As(); 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("player_spray_decal"); + if (sprayDecal != null) { - var endPos = trace.Value.Item1; - var normalPos = trace.Value.Item2; - var sprayDecal = Utilities.CreateEntityByName("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"); } } } diff --git a/source/InventorySimulator/InventorySimulator.Hacks.cs b/source/InventorySimulator/InventorySimulator.Hacks.cs index 5132850..1f95967 100644 --- a/source/InventorySimulator/InventorySimulator.Hacks.cs +++ b/source/InventorySimulator/InventorySimulator.Hacks.cs @@ -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; @@ -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(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 { diff --git a/source/InventorySimulator/InventorySimulator.Hooks.cs b/source/InventorySimulator/InventorySimulator.Hooks.cs index a871b95..1d2d862 100644 --- a/source/InventorySimulator/InventorySimulator.Hooks.cs +++ b/source/InventorySimulator/InventorySimulator.Hooks.cs @@ -3,6 +3,7 @@ * 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; @@ -10,6 +11,30 @@ namespace InventorySimulator; public partial class InventorySimulator { + public HookResult OnProcessUsercmdsPost(DynamicHook hook) + { + if (!invsim_spray_on_use.Value) + return HookResult.Continue; + + var player = hook.GetParam(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(1); diff --git a/source/InventorySimulator/InventorySimulator.Player.cs b/source/InventorySimulator/InventorySimulator.Player.cs index cf4fad6..cafd7ba 100644 --- a/source/InventorySimulator/InventorySimulator.Player.cs +++ b/source/InventorySimulator/InventorySimulator.Player.cs @@ -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)) diff --git a/source/InventorySimulator/InventorySimulator.PlayerInventory.cs b/source/InventorySimulator/InventorySimulator.PlayerInventory.cs index 241d72d..5627bb2 100644 --- a/source/InventorySimulator/InventorySimulator.PlayerInventory.cs +++ b/source/InventorySimulator/InventorySimulator.PlayerInventory.cs @@ -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; diff --git a/source/InventorySimulator/InventorySimulator.State.cs b/source/InventorySimulator/InventorySimulator.State.cs index d0b3c5a..c847470 100644 --- a/source/InventorySimulator/InventorySimulator.State.cs +++ b/source/InventorySimulator/InventorySimulator.State.cs @@ -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; @@ -15,6 +16,7 @@ public partial class InventorySimulator { public readonly FakeConVar invsim_stattrak_ignore_bots = new("invsim_stattrak_ignore_bots", "Whether to ignore StatTrak increments for bot kills.", true); public readonly FakeConVar invsim_spraychanger_enabled = new("invsim_spraychanger_enabled", "Whether to change player vanilla spray if they have a graffiti equipped.", false); + public readonly FakeConVar invsim_spray_on_use = new("invsim_spray_on_use", "Whether to try to apply spray when player presses use.", false); public readonly FakeConVar invsim_ws_enabled = new("invsim_ws_enabled", "Whether players can refresh their inventory using !ws.", false); public readonly FakeConVar invsim_ws_print_full_url = new("invsim_ws_print_full_url", "Whether print full URL when the player uses !ws.", true); public readonly FakeConVar invsim_minmodels = new("invsim_minmodels", "Allows agents or use specific models for each team.", 0, flags: ConVarFlags.FCVAR_NONE, new RangeValidator(0, 2)); @@ -32,7 +34,8 @@ public partial class InventorySimulator public readonly ConcurrentDictionary PlayerSprayCooldownManager = []; public readonly ConcurrentDictionary PlayerOnTickInventoryManager = []; public readonly ConcurrentDictionary PlayerInventoryManager = []; - public readonly ConcurrentDictionary PlayerGiveNextSpawn = []; + public readonly ConcurrentDictionary PlayerUseCmdManager = []; + public readonly ConcurrentDictionary PlayerUseCmdBlockManager = []; public readonly PlayerInventory EmptyInventory = new(); @@ -40,5 +43,4 @@ public partial class InventorySimulator public static readonly ulong MinimumCustomItemID = 68719476736; public ulong NextItemId = MinimumCustomItemID; - public int NextFadeSeed = 3; } diff --git a/source/InventorySimulator/InventorySimulator.Utilities.cs b/source/InventorySimulator/InventorySimulator.Utilities.cs index 35d045b..dee0715 100644 --- a/source/InventorySimulator/InventorySimulator.Utilities.cs +++ b/source/InventorySimulator/InventorySimulator.Utilities.cs @@ -5,6 +5,8 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; +using System.Numerics; +using NativeVector = CounterStrikeSharp.API.Modules.Utils.Vector; namespace InventorySimulator; @@ -43,4 +45,6 @@ public static float ViewAsFloat(T value) where T : struct ? GameRulesProxy?.GameRules : null ); + + public static NativeVector Vector3toVector(Vector3 vec) => new(vec.X, vec.Y, vec.Z); } diff --git a/source/InventorySimulator/InventorySimulator.cs b/source/InventorySimulator/InventorySimulator.cs index f735256..faef0c9 100644 --- a/source/InventorySimulator/InventorySimulator.cs +++ b/source/InventorySimulator/InventorySimulator.cs @@ -24,6 +24,7 @@ public override void Load(bool hotReload) RegisterEventHandler(OnPlayerConnect); RegisterEventHandler(OnPlayerConnectFull); RegisterEventHandler(OnPlayerSpawn); + Extensions.ProcessUsercmds.Hook(OnProcessUsercmdsPost, HookMode.Post); VirtualFunctions.GiveNamedItemFunc.Hook(OnGiveNamedItemPost, HookMode.Post); RegisterEventHandler(OnPlayerDeathPre, HookMode.Pre); RegisterEventHandler(OnRoundMvpPre, HookMode.Pre);