From e56d5ae8a472bde580de5704f59bbc0f74a6824f Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 13:38:57 +0100 Subject: No spread: detect pellet draws from the FireBullets seed call The ApplySpread random draws are not guaranteed to run in the player's frame (the compiler can outline them), so the frame-based shooter check missed and the shotgun kept its seed-minimised spread. The player's FireBullets RandomSeed call now arms a short 8-draw window; the next RandomFloat draws return 0. Keep the frame check as the other path. Remove the diagnostics used to find this. --- src/hl2h.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src/hl2h.cpp') diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 02bf584..8ab1a3e 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -482,6 +482,8 @@ static void (*g_real_random_seed)(int) = nullptr; static uintptr_t g_srv_base = 0; static uintptr_t g_local_server_player = 0; static bool g_seed_hook_installed = false; +// Pellet-spread window armed by the player's FireBullets RandomSeed call. +static volatile int g_spread_rand_left = 0; static void hkdSrvRandomSeed(int seed) { uintptr_t ret = (uintptr_t)__builtin_return_address(0); @@ -502,6 +504,7 @@ static void hkdSrvRandomSeed(int seed) { if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && spread_no_spread_enabled()) { + g_spread_rand_left = 8; int pellets = g_spread_pellets; if (pellets < 1) pellets = 1; @@ -590,19 +593,33 @@ static void install_seed_hook() { static float (*g_orig_uniform_float)(void *, float, float) = nullptr; static bool g_uniform_hook_installed = false; +// g_spread_rand_left is set for a few draws right after the player's +// FireBullets RandomSeed. The ApplySpread frame is not always the player's +// (the compiler may outline it), so this window is the reliable signal. static float hkdUniformRandomFloat(void *self, float mn, float mx) { - if (g_local_server_player && feature_active(FEAT_SPREAD) && - spread_no_spread_enabled()) { + bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) && + spread_no_spread_enabled(); + bool hit = false; + void *who = nullptr; + if (enabled) { void *frame = nullptr; __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); if (frame) { - frame = *(void **)frame; // caller's ebp (CBaseEntity::FireBullets) - void *who = frame ? *(void **)((char *)frame + 8) : nullptr; + frame = *(void **)frame; // caller's ebp + who = frame ? *(void **)((char *)frame + 8) : nullptr; if (who && (uintptr_t)who == g_local_server_player) - return 0.0f; + hit = true; + } + if (!hit && g_spread_rand_left > 0) { + // The next few draws after the player's FireBullets RandomSeed are the + // pellet spread, regardless of which function emits them. + hit = true; + g_spread_rand_left--; } } + if (hit) + return 0.0f; return g_orig_uniform_float ? g_orig_uniform_float(self, mn, mx) : mn; } -- cgit v1.2.3