From 6618d19660c42f4a6ae48cdaec23324e6e79def1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 12:50:11 +0100 Subject: Combat: fix silent-aim spread seed and lead projectiles - Silent aim and triggerbot set IN_ATTACK after CInput::CreateMove, so the minimal-spread seed was never applied to their shots. Re-apply spread_control_apply after setting the attack button, then update the CRC. - Crossbow bolts and RPG rockets are not hitscan. Lead the selected hitbox by target velocity * travel time; the crossbow bolt also gets its 0.05 gravity drop (30 u/s^2). Apply the same correction to the smooth aimbot. - Document both paths in CONTEXT. --- CONTEXT.md | 7 ++++++- src/esp.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hl2h.cpp | 12 ++++++++++-- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 0ecf80c..a9e34d8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -176,7 +176,10 @@ vocabulary / decisions. - Silent aim: exact target angle written to `cmd->viewangles`, `IN_ATTACK`, then the rendered view is restored immediately **and** at `FRAME_RENDER_START`; `forwardmove`/`sidemove` are rewritten to preserve the - world-space movement wish vector. + world-space movement wish vector. Because `IN_ATTACK` is set after + `CInput::CreateMove`, the minimal-spread seed is applied again in `do_move` + (same for triggerbot). Crossbow bolts and RPG rockets are led by target + velocity and travel time; the crossbow also gets its 0.05 gravity drop. - ABH: on the landing/jump tick, face opposite the horizontal velocity with zero forward/side movement; HL2's jump speed-bound subtraction then accelerates along the velocity. Camera restored at `FRAME_RENDER_START`. @@ -212,6 +215,8 @@ vocabulary / decisions. `RandomSeed`/`RandomFloat`, the 8-bit seed whose `CShotManipulator::ApplySpread` offset is smallest. It sets `cmd->random_seed` on attack commands; `note_modified` repairs the CRC. + Silent aim and triggerbot set `IN_ATTACK` after the CInput hook, so they + re-apply the best seed in `do_move`. - Shotguns use consecutive seeds per pellet. The primary's first pellet is `FIRE_BULLETS_FIRST_SHOT_ACCURATE`, so the table excludes it; the secondary uses all 12. Separate best starting seeds are precomputed for 1, 8 and 12 diff --git a/src/esp.cpp b/src/esp.cpp index 4d2345d..06bafaf 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -669,6 +669,61 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, return false; } +static bool active_weapon_contains(void *local_ent, const char *needle) { + if (!local_ent || g_net.activeweapon < 0 || !needle) + return false; + uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); + if (h == 0xFFFFFFFFu) + return false; + int idx = (int)(h & 0xFFFF); + if (idx <= 0 || idx > 4096) + return false; + void *wep = entitylist_get(idx); + void *net = entity_networkable(idx); + const char *cls = net ? entity_class_name(net, nullptr) : nullptr; + const char *model = wep ? entity_model_name(wep) : nullptr; + return (cls && strstr(cls, needle)) || (model && strstr(model, needle)); +} + +// Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the +// projectile travel time; the crossbow bolt also has gravity 0.05 (30 u/s^2). +static bool projectile_lead_point(int target_idx, const Vector &point, + Vector &out) { + int local = engine_get_local_player(); + if (local <= 0) + return false; + void *local_ent = entitylist_get(local); + if (!local_ent) + return false; + float speed = 0.0f, gravity = 0.0f; + if (active_weapon_contains(local_ent, "crossbow") || + active_weapon_contains(local_ent, "Crossbow")) { + speed = 2500.0f; + gravity = 30.0f; + } else if (active_weapon_contains(local_ent, "rocket_launcher") || + active_weapon_contains(local_ent, "w_rpg") || + active_weapon_contains(local_ent, "RPG")) { + speed = 1500.0f; + gravity = 0.0f; + } else { + return false; + } + void *target = entitylist_get(target_idx); + if (!target || g_net.velocity < 0) + return false; + const float *v = (const float *)((uintptr_t)target + g_net.velocity); + float dx = point.x - g_cam_eye.x; + float dy = point.y - g_cam_eye.y; + float dz = point.z - g_cam_eye.z; + float dist = sqrtf(dx * dx + dy * dy + dz * dz); + float t = dist / speed; + out = point; + out.x += v[0] * t; + out.y += v[1] * t; + out.z += v[2] * t + 0.5f * gravity * t * t; + return true; +} + bool aimbot_apply(CUserCmd *cmd) { unsigned bone_mask = aimbot_bone_mask(); if (!bone_mask) @@ -684,6 +739,9 @@ bool aimbot_apply(CUserCmd *cmd) { if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov, "aimbot", lock, target, point, fov)) return false; + Vector lead; + if (projectile_lead_point(target, point, lead)) + point = lead; QAngle desired = angle_to(g_cam_eye, point); static int want_logs = 0; if (want_logs < 10) { @@ -759,6 +817,9 @@ bool silent_aim_apply(CUserCmd *cmd) { if (!select_aim_target(view, bone_mask, true, max_fov, "silentaim", lock, target, point, fov)) return false; + Vector lead; + if (projectile_lead_point(target, point, lead)) + point = lead; QAngle desired = angle_to(g_cam_eye, point); float np = desired.x; if (np > 89.0f) np = 89.0f; diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 256b9ab..4f5673f 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -689,13 +689,21 @@ static void do_move(int seq, float frametime) { } if (feature_active(FEAT_TRIGGERBOT)) { - if (triggerbot_apply(cmd)) + if (triggerbot_apply(cmd)) { + // Triggerbot sets IN_ATTACK after CInput's spread hook, so the best + // seed has to be applied here as well. + spread_control_apply(cmd, ent); note_modified(cmd, flags, vel); + } } if (feature_active(FEAT_SILENTAIM)) { - if (silent_aim_apply(cmd)) + if (silent_aim_apply(cmd)) { + // Silent aim also sets IN_ATTACK late; without this the shot keeps + // the random CInput seed instead of the minimal-spread seed. + spread_control_apply(cmd, ent); note_modified(cmd, flags, vel); + } } } } -- cgit v1.2.3