From 471f022f449a18766fedb1ff0a4204a933b95b9c Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 13:48:27 +0100 Subject: Jumpbug: optional velocity-timed assist The unduck window is only 2 units, while a 625-ups fall moves ~9.3 units per 66.7 Hz movement command, so pure jumpbug is ~20% reliable at that speed. - Add Jumpbug -> Assist timing (default off). When the natural next command would skip [36,38], pin the post-command ducked distance to 37 by writing the vertical velocity for that command. Pure jumpbug remains available with the option off. - Resolve the client local m_vecAbsVelocity (0x11C) via the client datamap and the server m_vecAbsVelocity (0x228) via the server datamap. - Write both the authoritative server velocity (what SetupMove reads through GetAbsVelocity) and the local prediction fields, so the next CreateMove sees the assisted origin on both sides. - Do not write the server m_vecVelocity directly: the datamap scan finds two same-named entries (0x1C and 0x29C); SetAbsVelocity syncs it after movement. --- src/hl2h.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 11 deletions(-) (limited to 'src/hl2h.cpp') diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 125d94e..4ba30bc 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -186,6 +186,8 @@ static void apply_autostrafe(CUserCmd *cmd, const float *vel) { static constexpr float JB_SAFE_FALL = 526.5f; static constexpr float JB_GRAVITY = 600.0f; static constexpr float JB_DUCK_RAISE = 36.0f; +static constexpr float JB_TARGET_DIST = 37.0f; // middle of the unduck window +static constexpr float JB_TICK = 0.015f; // Source interval_per_tick static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window static int jb_local = -1; @@ -213,6 +215,30 @@ static void jumpbug_reset() { jb_tracked = false; } +// Choose a vertical velocity so this command's movement ends with the feet +// `target` units above the floor. The engine does StartGravity (half a tick), +// moves by velocity*dt, then FinishGravity; displacement is +// (vz - 0.5*g*dt)*dt. dist is the distance before the command's movement. +static bool jb_force_next_distance(int local, void *ent, const float *vel, + float dist, float target) { + if (g_net.absvelocity < 0 || g_net.velocity < 0) + return false; + float dz = target - dist; + float vz = dz / JB_TICK + 0.5f * JB_GRAVITY * JB_TICK; + if (vz < -2000.0f) + vz = -2000.0f; + if (vz > 2000.0f) + vz = 2000.0f; + if (!server_set_velocity_z(local, vz)) + return false; + // Keep the local prediction in step with the authoritative server entity: + // C_BaseEntity::GetAbsVelocity() reads the non-networked m_vecAbsVelocity, + // while the networked m_vecVelocity is what the rest of the client sees. + *(float *)((uintptr_t)ent + g_net.absvelocity + 8) = vz; + ((float *)vel)[2] = vz; + return true; +} + 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 || @@ -285,6 +311,10 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact); } + bool assist = jumpbug_assist_enabled(); + if (assist) + resolve_server_netvars(); + 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 @@ -310,29 +340,59 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, trigger = true; jb_state = 0; jb_tracked = false; - if (jb_logs < 20) { + if (jb_logs < 30) { jb_logs++; - logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f", x, jb_drop, vz); + logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f assist=%d", x, jb_drop, + vz, (int)assist); } } 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. + if (assist) { + float next_x = x - jb_drop; + if (next_x <= 38.0f && next_x < 36.0f) { + // The natural next command would skip the 2-unit window. Force the + // post-command distance to 37 so the next command can unduck+jump. + if (jb_force_next_distance(local, ent, vel, x, JB_TARGET_DIST)) { + if (jb_logs < 30) { + jb_logs++; + logf("jumpbug: assist duck x=%.2f next=%.2f -> vz=%.1f", x, + next_x, vel[2]); + } + } else { + static bool warned = false; + if (!warned) { + warned = true; + logf("jumpbug: assist unavailable (server velocity failed)"); + } + } + } + } + // Hold the crouch and wait for the window (or for the assisted + // position on the next command). 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. + float setup_limit = jb_drop + (assist ? 4.0f : 2.0f); + if (x <= setup_limit) { + // Setup frame: raise the feet 36 units here so the next command starts + // near the middle of the unduck window. With assist enabled, also pin + // the post-command distance to exactly 37. + bool forced = false; + if (assist) + forced = jb_force_next_distance(local, ent, vel, x + JB_DUCK_RAISE, + JB_TARGET_DIST); cmd->buttons |= IN_DUCK; jb_state = 1; - if (jb_logs < 20) { + if (jb_logs < 30) { jb_logs++; - logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, jb_drop, - x + JB_DUCK_RAISE - jb_drop, vz); + if (assist) + logf("jumpbug: setup assist x=%.2f drop=%.2f vz=%.1f ok=%d", x, + jb_drop, vel[2], (int)forced); + else + logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, + jb_drop, x + JB_DUCK_RAISE - jb_drop, vz); } } } -- cgit v1.2.3