From bb99c978c5f982e80a3007101fd61fe23635e242 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 13:19:00 +0100 Subject: Jumpbug: automatic duck+jump on damaging falls - Predict the landing speed from the current vertical velocity and a swept standing-hull ground trace; engage only when it exceeds PLAYER_MAX_SAFE_FALL_SPEED. - Press duck one command before the unduck window so FinishDuck's 36-unit origin raise places the feet ~37 units above the floor. - On the next command release duck and press jump: PlayerMove runs Duck() before CheckJumpButton, so FinishUnDuck sets ground, the jump executes, and CheckFalling never sees ground for the fall-damage check. - Measure the per-command fall from consecutive airborne origins instead of a fixed tick interval; clear IN_JUMP while waiting so the pogo latch is clear. - New Movement -> Jumpbug -> Active toggle (default on), config [jumpbug]. --- src/hl2h.cpp | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 3 deletions(-) (limited to 'src/hl2h.cpp') diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 4947b18..125d94e 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -166,7 +166,184 @@ static void apply_autostrafe(CUserCmd *cmd, const float *vel) { cmd->sidemove = (dx * rx + dy * ry) * 450.0f; } -static void do_move(int seq) { +// --------------------------------------------------------------------------- +// jumpbug +// --------------------------------------------------------------------------- +// +// Source jumpbug: while falling, hold duck; on the frame where unducking would +// leave the standing hull within CategorizePosition's 2-unit ground probe, +// release duck and press jump. PlayerMove calls Duck() before FullWalkMove's +// CheckJumpButton, so FinishUnDuck -> CategorizePosition sets the ground +// entity, the jump executes in the same frame, and CheckFalling never sees +// ground for the fall-damage test. +// +// In air, FinishDuck raises the feet by 36 units (the standing hull is 72 +// tall, the duck hull 36). We exploit that as a one-frame phase shift: press +// duck when the standing distance x to the floor is about `drop` units, so +// after this frame's fall the ducked feet sit ~37 units above the floor (the +// middle of the [36, 38] unduck window) and the next frame can jump. + +static constexpr float JB_SAFE_FALL = 526.5f; +static constexpr float JB_GRAVITY = 600.0f; +static constexpr float JB_DUCK_RAISE = 36.0f; + +static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window +static int jb_local = -1; +static float jb_prev_z = 0.0f; +static bool jb_prev_valid = false; +static bool jb_prev_ducked = false; +static float jb_drop = 0.0f; // last measured vertical fall per command +static int jb_logs = 0; +static bool jb_tracked = false; + +static bool game_running() { + if (!g_iface.engine) + return true; + using BoolFn = bool (*)(void *); + if (!vfunc(g_iface.engine, 26)(g_iface.engine)) + return false; + if (vfunc(g_iface.engine, 84)(g_iface.engine)) + return false; + return true; +} + +static void jumpbug_reset() { + jb_state = 0; + jb_prev_valid = false; + jb_tracked = false; +} + +static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, + const float *vel, float frametime) { + if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine || + g_net.origin < 0 || g_net.velocity < 0) { + jumpbug_reset(); + return false; + } + if (!game_running()) { + jumpbug_reset(); + return false; + } + + bool onground = (flags & FL_ONGROUND) != 0; + bool inwater = (flags & FL_INWATER) != 0; + bool ducked = (flags & FL_DUCKING) != 0; + uintptr_t e = (uintptr_t)ent; + int movetype = + g_net.movetype >= 0 ? *(int *)(e + g_net.movetype) : MOVETYPE_WALK; + + if (onground || inwater || movetype != MOVETYPE_WALK || + (g_net.health >= 0 && *(int *)(e + g_net.health) <= 0)) { + jumpbug_reset(); + return false; + } + + Vector origin{*(float *)(e + g_net.origin), + *(float *)(e + g_net.origin + 4), + *(float *)(e + g_net.origin + 8)}; + float vz = vel[2]; + + if (local != jb_local) { + jb_local = local; + jumpbug_reset(); + jb_drop = 0.0f; + jb_logs = 0; + } + + // Measure the previous command's fall. Both endpoints must be airborne and + // standing: a duck transition moves the origin by 36 units and would + // otherwise look like a huge fall. + if (jb_prev_valid && !jb_prev_ducked && !ducked) { + float d = jb_prev_z - origin.z; + if (d > 0.05f && d < 250.0f) + jb_drop = d; + } + jb_prev_valid = true; + jb_prev_z = origin.z; + jb_prev_ducked = ducked; + + if (vz > -10.0f) { // not falling + jb_state = 0; + jb_tracked = false; + return false; + } + + float x = ground_distance(origin, ent, 2048.0f); + if (x < 0.0f) { // no standable surface below + jb_state = 0; + jb_tracked = false; + return false; + } + float impact = sqrtf(vz * vz + 2.0f * JB_GRAVITY * x); + if (impact <= JB_SAFE_FALL + 0.5f) { + jb_tracked = false; + jb_state = 0; + return false; + } + if (!jb_tracked) { + jb_tracked = true; + logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact); + } + + if (jb_drop <= 0.05f) { + // No measured fall yet. Bias toward the default 15 ms Source tick when + // the input frametime is smaller: setting the duck up early leaves us + // ducked above the window, where we can wait; setting it up late would + // put us below the unduck window and lose the jump. + float dt = frametime; + if (!(dt > 0.0f && dt < 0.05f) || dt < 0.015f) + dt = 0.015f; + jb_drop = -vz * dt + 0.5f * JB_GRAVITY * dt * dt; + if (jb_drop < 0.05f) + jb_drop = 0.05f; + } + + int before = cmd->buttons; + bool trigger = false; + + if (ducked) { + if (x >= JB_DUCK_RAISE && x <= JB_DUCK_RAISE + 2.0f) { + // In the window: unduck (CategorizePosition sets ground) and jump in + // the same command. + cmd->buttons &= ~IN_DUCK; + cmd->buttons |= IN_JUMP; + trigger = true; + jb_state = 0; + jb_tracked = false; + if (jb_logs < 20) { + jb_logs++; + logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f", x, jb_drop, vz); + } + } else { + // Hold the crouch and wait for the window. Staying ducked is required + // for the unduck trick; releasing early would just add a phase shift + // and fight a user who is holding crouch. + cmd->buttons |= IN_DUCK; + jb_state = 1; + } + } else { + if (jb_state == 1) + jb_state = 0; // expected to be ducked but is not + if (x <= jb_drop + 2.0f) { + // Setup frame: raise the feet 36 units here so the next frame starts + // near the middle of the unduck window. + cmd->buttons |= IN_DUCK; + jb_state = 1; + if (jb_logs < 20) { + jb_logs++; + logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, jb_drop, + x + JB_DUCK_RAISE - jb_drop, vz); + } + } + } + + if (!trigger && (cmd->buttons & IN_JUMP)) + cmd->buttons &= ~IN_JUMP; // keep the pogo latch clear for the trigger + + return cmd->buttons != before; +} + +static void do_move(int seq, float frametime) { static bool g_echo_sent = false; if (!g_input || !g_net.ok()) return; @@ -206,6 +383,7 @@ static void do_move(int seq) { int flags = *(int *)(e + g_net.flags); bool onground = (flags & FL_ONGROUND) != 0; bool inwater = (flags & FL_INWATER) != 0; + const float *vel = (const float *)(e + g_net.velocity); bool bhop_on = feature_active(FEAT_BHOP); bool abh_on = feature_active(FEAT_ABH); @@ -217,7 +395,6 @@ static void do_move(int seq) { lastground = onground; return; } - const float *vel = (const float *)(e + g_net.velocity); float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); bool moving = speed > 0.1f; bool backmove = false; @@ -260,6 +437,9 @@ static void do_move(int seq) { } } + if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime)) + note_modified(cmd, flags, vel); + if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT)) aimbot_reset_lock(); if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM)) @@ -361,7 +541,7 @@ static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) { static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { bool ret = oCreateMove(self, seq, frametime, active); - do_move(seq); + do_move(seq, frametime); // Hook ISurface once: PaintTraverseEx (slot 114) fires every frame; keep // DrawPrintText (slot 22) hooked as a fallback text-draw trigger. -- cgit v1.2.3