summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 13:38:57 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 13:38:57 +0100
commite56d5ae8a472bde580de5704f59bbc0f74a6824f (patch)
tree41569c24cf3f98eca711646d896d25c064ec2911 /src
parentNo spread: zero pellet offsets inside FireBullets (diff)
downloadhl2h-e56d5ae8a472bde580de5704f59bbc0f74a6824f.tar.gz
hl2h-e56d5ae8a472bde580de5704f59bbc0f74a6824f.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/hl2h.cpp27
1 files changed, 22 insertions, 5 deletions
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;
482static uintptr_t g_srv_base = 0; 482static uintptr_t g_srv_base = 0;
483static uintptr_t g_local_server_player = 0; 483static uintptr_t g_local_server_player = 0;
484static bool g_seed_hook_installed = false; 484static bool g_seed_hook_installed = false;
485// Pellet-spread window armed by the player's FireBullets RandomSeed call.
486static volatile int g_spread_rand_left = 0;
485 487
486static void hkdSrvRandomSeed(int seed) { 488static void hkdSrvRandomSeed(int seed) {
487 uintptr_t ret = (uintptr_t)__builtin_return_address(0); 489 uintptr_t ret = (uintptr_t)__builtin_return_address(0);
@@ -502,6 +504,7 @@ static void hkdSrvRandomSeed(int seed) {
502 504
503 if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && 505 if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) &&
504 spread_no_spread_enabled()) { 506 spread_no_spread_enabled()) {
507 g_spread_rand_left = 8;
505 int pellets = g_spread_pellets; 508 int pellets = g_spread_pellets;
506 if (pellets < 1) 509 if (pellets < 1)
507 pellets = 1; 510 pellets = 1;
@@ -590,19 +593,33 @@ static void install_seed_hook() {
590 593
591static float (*g_orig_uniform_float)(void *, float, float) = nullptr; 594static float (*g_orig_uniform_float)(void *, float, float) = nullptr;
592static bool g_uniform_hook_installed = false; 595static bool g_uniform_hook_installed = false;
596// g_spread_rand_left is set for a few draws right after the player's
597// FireBullets RandomSeed. The ApplySpread frame is not always the player's
598// (the compiler may outline it), so this window is the reliable signal.
593 599
594static float hkdUniformRandomFloat(void *self, float mn, float mx) { 600static float hkdUniformRandomFloat(void *self, float mn, float mx) {
595 if (g_local_server_player && feature_active(FEAT_SPREAD) && 601 bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) &&
596 spread_no_spread_enabled()) { 602 spread_no_spread_enabled();
603 bool hit = false;
604 void *who = nullptr;
605 if (enabled) {
597 void *frame = nullptr; 606 void *frame = nullptr;
598 __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); 607 __asm__ volatile("movl %%ebp, %0" : "=r"(frame));
599 if (frame) { 608 if (frame) {
600 frame = *(void **)frame; // caller's ebp (CBaseEntity::FireBullets) 609 frame = *(void **)frame; // caller's ebp
601 void *who = frame ? *(void **)((char *)frame + 8) : nullptr; 610 who = frame ? *(void **)((char *)frame + 8) : nullptr;
602 if (who && (uintptr_t)who == g_local_server_player) 611 if (who && (uintptr_t)who == g_local_server_player)
603 return 0.0f; 612 hit = true;
613 }
614 if (!hit && g_spread_rand_left > 0) {
615 // The next few draws after the player's FireBullets RandomSeed are the
616 // pellet spread, regardless of which function emits them.
617 hit = true;
618 g_spread_rand_left--;
604 } 619 }
605 } 620 }
621 if (hit)
622 return 0.0f;
606 return g_orig_uniform_float ? g_orig_uniform_float(self, mn, mx) : mn; 623 return g_orig_uniform_float ? g_orig_uniform_float(self, mn, mx) : mn;
607} 624}
608 625