summaryrefslogtreecommitdiffstats
path: root/src/hl2h.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 13:19:00 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 13:22:27 +0100
commitbb99c978c5f982e80a3007101fd61fe23635e242 (patch)
tree749acafe3d2c4fcffeb2060c7a4a92c49aa69407 /src/hl2h.cpp
parentSpeed graph: show current speed at the top-right (diff)
downloadhl2h-bb99c978c5f982e80a3007101fd61fe23635e242.tar.gz
hl2h-bb99c978c5f982e80a3007101fd61fe23635e242.zip
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].
Diffstat (limited to '')
-rw-r--r--src/hl2h.cpp186
1 files changed, 183 insertions, 3 deletions
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) {
166 cmd->sidemove = (dx * rx + dy * ry) * 450.0f; 166 cmd->sidemove = (dx * rx + dy * ry) * 450.0f;
167} 167}
168 168
169static void do_move(int seq) { 169// ---------------------------------------------------------------------------
170// jumpbug
171// ---------------------------------------------------------------------------
172//
173// Source jumpbug: while falling, hold duck; on the frame where unducking would
174// leave the standing hull within CategorizePosition's 2-unit ground probe,
175// release duck and press jump. PlayerMove calls Duck() before FullWalkMove's
176// CheckJumpButton, so FinishUnDuck -> CategorizePosition sets the ground
177// entity, the jump executes in the same frame, and CheckFalling never sees
178// ground for the fall-damage test.
179//
180// In air, FinishDuck raises the feet by 36 units (the standing hull is 72
181// tall, the duck hull 36). We exploit that as a one-frame phase shift: press
182// duck when the standing distance x to the floor is about `drop` units, so
183// after this frame's fall the ducked feet sit ~37 units above the floor (the
184// middle of the [36, 38] unduck window) and the next frame can jump.
185
186static constexpr float JB_SAFE_FALL = 526.5f;
187static constexpr float JB_GRAVITY = 600.0f;
188static constexpr float JB_DUCK_RAISE = 36.0f;
189
190static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window
191static int jb_local = -1;
192static float jb_prev_z = 0.0f;
193static bool jb_prev_valid = false;
194static bool jb_prev_ducked = false;
195static float jb_drop = 0.0f; // last measured vertical fall per command
196static int jb_logs = 0;
197static bool jb_tracked = false;
198
199static bool game_running() {
200 if (!g_iface.engine)
201 return true;
202 using BoolFn = bool (*)(void *);
203 if (!vfunc<BoolFn>(g_iface.engine, 26)(g_iface.engine))
204 return false;
205 if (vfunc<BoolFn>(g_iface.engine, 84)(g_iface.engine))
206 return false;
207 return true;
208}
209
210static void jumpbug_reset() {
211 jb_state = 0;
212 jb_prev_valid = false;
213 jb_tracked = false;
214}
215
216static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
217 const float *vel, float frametime) {
218 if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine ||
219 g_net.origin < 0 || g_net.velocity < 0) {
220 jumpbug_reset();
221 return false;
222 }
223 if (!game_running()) {
224 jumpbug_reset();
225 return false;
226 }
227
228 bool onground = (flags & FL_ONGROUND) != 0;
229 bool inwater = (flags & FL_INWATER) != 0;
230 bool ducked = (flags & FL_DUCKING) != 0;
231 uintptr_t e = (uintptr_t)ent;
232 int movetype =
233 g_net.movetype >= 0 ? *(int *)(e + g_net.movetype) : MOVETYPE_WALK;
234
235 if (onground || inwater || movetype != MOVETYPE_WALK ||
236 (g_net.health >= 0 && *(int *)(e + g_net.health) <= 0)) {
237 jumpbug_reset();
238 return false;
239 }
240
241 Vector origin{*(float *)(e + g_net.origin),
242 *(float *)(e + g_net.origin + 4),
243 *(float *)(e + g_net.origin + 8)};
244 float vz = vel[2];
245
246 if (local != jb_local) {
247 jb_local = local;
248 jumpbug_reset();
249 jb_drop = 0.0f;
250 jb_logs = 0;
251 }
252
253 // Measure the previous command's fall. Both endpoints must be airborne and
254 // standing: a duck transition moves the origin by 36 units and would
255 // otherwise look like a huge fall.
256 if (jb_prev_valid && !jb_prev_ducked && !ducked) {
257 float d = jb_prev_z - origin.z;
258 if (d > 0.05f && d < 250.0f)
259 jb_drop = d;
260 }
261 jb_prev_valid = true;
262 jb_prev_z = origin.z;
263 jb_prev_ducked = ducked;
264
265 if (vz > -10.0f) { // not falling
266 jb_state = 0;
267 jb_tracked = false;
268 return false;
269 }
270
271 float x = ground_distance(origin, ent, 2048.0f);
272 if (x < 0.0f) { // no standable surface below
273 jb_state = 0;
274 jb_tracked = false;
275 return false;
276 }
277 float impact = sqrtf(vz * vz + 2.0f * JB_GRAVITY * x);
278 if (impact <= JB_SAFE_FALL + 0.5f) {
279 jb_tracked = false;
280 jb_state = 0;
281 return false;
282 }
283 if (!jb_tracked) {
284 jb_tracked = true;
285 logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact);
286 }
287
288 if (jb_drop <= 0.05f) {
289 // No measured fall yet. Bias toward the default 15 ms Source tick when
290 // the input frametime is smaller: setting the duck up early leaves us
291 // ducked above the window, where we can wait; setting it up late would
292 // put us below the unduck window and lose the jump.
293 float dt = frametime;
294 if (!(dt > 0.0f && dt < 0.05f) || dt < 0.015f)
295 dt = 0.015f;
296 jb_drop = -vz * dt + 0.5f * JB_GRAVITY * dt * dt;
297 if (jb_drop < 0.05f)
298 jb_drop = 0.05f;
299 }
300
301 int before = cmd->buttons;
302 bool trigger = false;
303
304 if (ducked) {
305 if (x >= JB_DUCK_RAISE && x <= JB_DUCK_RAISE + 2.0f) {
306 // In the window: unduck (CategorizePosition sets ground) and jump in
307 // the same command.
308 cmd->buttons &= ~IN_DUCK;
309 cmd->buttons |= IN_JUMP;
310 trigger = true;
311 jb_state = 0;
312 jb_tracked = false;
313 if (jb_logs < 20) {
314 jb_logs++;
315 logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f", x, jb_drop, vz);
316 }
317 } else {
318 // Hold the crouch and wait for the window. Staying ducked is required
319 // for the unduck trick; releasing early would just add a phase shift
320 // and fight a user who is holding crouch.
321 cmd->buttons |= IN_DUCK;
322 jb_state = 1;
323 }
324 } else {
325 if (jb_state == 1)
326 jb_state = 0; // expected to be ducked but is not
327 if (x <= jb_drop + 2.0f) {
328 // Setup frame: raise the feet 36 units here so the next frame starts
329 // near the middle of the unduck window.
330 cmd->buttons |= IN_DUCK;
331 jb_state = 1;
332 if (jb_logs < 20) {
333 jb_logs++;
334 logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, jb_drop,
335 x + JB_DUCK_RAISE - jb_drop, vz);
336 }
337 }
338 }
339
340 if (!trigger && (cmd->buttons & IN_JUMP))
341 cmd->buttons &= ~IN_JUMP; // keep the pogo latch clear for the trigger
342
343 return cmd->buttons != before;
344}
345
346static void do_move(int seq, float frametime) {
170 static bool g_echo_sent = false; 347 static bool g_echo_sent = false;
171 if (!g_input || !g_net.ok()) 348 if (!g_input || !g_net.ok())
172 return; 349 return;
@@ -206,6 +383,7 @@ static void do_move(int seq) {
206 int flags = *(int *)(e + g_net.flags); 383 int flags = *(int *)(e + g_net.flags);
207 bool onground = (flags & FL_ONGROUND) != 0; 384 bool onground = (flags & FL_ONGROUND) != 0;
208 bool inwater = (flags & FL_INWATER) != 0; 385 bool inwater = (flags & FL_INWATER) != 0;
386 const float *vel = (const float *)(e + g_net.velocity);
209 387
210 bool bhop_on = feature_active(FEAT_BHOP); 388 bool bhop_on = feature_active(FEAT_BHOP);
211 bool abh_on = feature_active(FEAT_ABH); 389 bool abh_on = feature_active(FEAT_ABH);
@@ -217,7 +395,6 @@ static void do_move(int seq) {
217 lastground = onground; 395 lastground = onground;
218 return; 396 return;
219 } 397 }
220 const float *vel = (const float *)(e + g_net.velocity);
221 float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); 398 float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]);
222 bool moving = speed > 0.1f; 399 bool moving = speed > 0.1f;
223 bool backmove = false; 400 bool backmove = false;
@@ -260,6 +437,9 @@ static void do_move(int seq) {
260 } 437 }
261 } 438 }
262 439
440 if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime))
441 note_modified(cmd, flags, vel);
442
263 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT)) 443 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT))
264 aimbot_reset_lock(); 444 aimbot_reset_lock();
265 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM)) 445 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM))
@@ -361,7 +541,7 @@ static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) {
361 541
362static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { 542static bool hkdCreateMove(void *self, int seq, float frametime, bool active) {
363 bool ret = oCreateMove(self, seq, frametime, active); 543 bool ret = oCreateMove(self, seq, frametime, active);
364 do_move(seq); 544 do_move(seq, frametime);
365 545
366 // Hook ISurface once: PaintTraverseEx (slot 114) fires every frame; keep 546 // Hook ISurface once: PaintTraverseEx (slot 114) fires every frame; keep
367 // DrawPrintText (slot 22) hooked as a fallback text-draw trigger. 547 // DrawPrintText (slot 22) hooked as a fallback text-draw trigger.