summaryrefslogtreecommitdiffstats
path: root/src/hl2h.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 13:48:27 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 13:48:27 +0100
commit471f022f449a18766fedb1ff0a4204a933b95b9c (patch)
tree499fcd29f1c9f7933aade3f8ffad88b3ef10f377 /src/hl2h.cpp
parentscript: change the map for tests (diff)
downloadhl2h-471f022f449a18766fedb1ff0a4204a933b95b9c.tar.gz
hl2h-471f022f449a18766fedb1ff0a4204a933b95b9c.zip
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.
Diffstat (limited to '')
-rw-r--r--src/hl2h.cpp82
1 files changed, 71 insertions, 11 deletions
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) {
186static constexpr float JB_SAFE_FALL = 526.5f; 186static constexpr float JB_SAFE_FALL = 526.5f;
187static constexpr float JB_GRAVITY = 600.0f; 187static constexpr float JB_GRAVITY = 600.0f;
188static constexpr float JB_DUCK_RAISE = 36.0f; 188static constexpr float JB_DUCK_RAISE = 36.0f;
189static constexpr float JB_TARGET_DIST = 37.0f; // middle of the unduck window
190static constexpr float JB_TICK = 0.015f; // Source interval_per_tick
189 191
190static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window 192static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window
191static int jb_local = -1; 193static int jb_local = -1;
@@ -213,6 +215,30 @@ static void jumpbug_reset() {
213 jb_tracked = false; 215 jb_tracked = false;
214} 216}
215 217
218// Choose a vertical velocity so this command's movement ends with the feet
219// `target` units above the floor. The engine does StartGravity (half a tick),
220// moves by velocity*dt, then FinishGravity; displacement is
221// (vz - 0.5*g*dt)*dt. dist is the distance before the command's movement.
222static bool jb_force_next_distance(int local, void *ent, const float *vel,
223 float dist, float target) {
224 if (g_net.absvelocity < 0 || g_net.velocity < 0)
225 return false;
226 float dz = target - dist;
227 float vz = dz / JB_TICK + 0.5f * JB_GRAVITY * JB_TICK;
228 if (vz < -2000.0f)
229 vz = -2000.0f;
230 if (vz > 2000.0f)
231 vz = 2000.0f;
232 if (!server_set_velocity_z(local, vz))
233 return false;
234 // Keep the local prediction in step with the authoritative server entity:
235 // C_BaseEntity::GetAbsVelocity() reads the non-networked m_vecAbsVelocity,
236 // while the networked m_vecVelocity is what the rest of the client sees.
237 *(float *)((uintptr_t)ent + g_net.absvelocity + 8) = vz;
238 ((float *)vel)[2] = vz;
239 return true;
240}
241
216static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, 242static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
217 const float *vel, float frametime) { 243 const float *vel, float frametime) {
218 if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine || 244 if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine ||
@@ -285,6 +311,10 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
285 logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact); 311 logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact);
286 } 312 }
287 313
314 bool assist = jumpbug_assist_enabled();
315 if (assist)
316 resolve_server_netvars();
317
288 if (jb_drop <= 0.05f) { 318 if (jb_drop <= 0.05f) {
289 // No measured fall yet. Bias toward the default 15 ms Source tick when 319 // 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 320 // 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,
310 trigger = true; 340 trigger = true;
311 jb_state = 0; 341 jb_state = 0;
312 jb_tracked = false; 342 jb_tracked = false;
313 if (jb_logs < 20) { 343 if (jb_logs < 30) {
314 jb_logs++; 344 jb_logs++;
315 logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f", x, jb_drop, vz); 345 logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f assist=%d", x, jb_drop,
346 vz, (int)assist);
316 } 347 }
317 } else { 348 } else {
318 // Hold the crouch and wait for the window. Staying ducked is required 349 if (assist) {
319 // for the unduck trick; releasing early would just add a phase shift 350 float next_x = x - jb_drop;
320 // and fight a user who is holding crouch. 351 if (next_x <= 38.0f && next_x < 36.0f) {
352 // The natural next command would skip the 2-unit window. Force the
353 // post-command distance to 37 so the next command can unduck+jump.
354 if (jb_force_next_distance(local, ent, vel, x, JB_TARGET_DIST)) {
355 if (jb_logs < 30) {
356 jb_logs++;
357 logf("jumpbug: assist duck x=%.2f next=%.2f -> vz=%.1f", x,
358 next_x, vel[2]);
359 }
360 } else {
361 static bool warned = false;
362 if (!warned) {
363 warned = true;
364 logf("jumpbug: assist unavailable (server velocity failed)");
365 }
366 }
367 }
368 }
369 // Hold the crouch and wait for the window (or for the assisted
370 // position on the next command).
321 cmd->buttons |= IN_DUCK; 371 cmd->buttons |= IN_DUCK;
322 jb_state = 1; 372 jb_state = 1;
323 } 373 }
324 } else { 374 } else {
325 if (jb_state == 1) 375 if (jb_state == 1)
326 jb_state = 0; // expected to be ducked but is not 376 jb_state = 0; // expected to be ducked but is not
327 if (x <= jb_drop + 2.0f) { 377 float setup_limit = jb_drop + (assist ? 4.0f : 2.0f);
328 // Setup frame: raise the feet 36 units here so the next frame starts 378 if (x <= setup_limit) {
329 // near the middle of the unduck window. 379 // Setup frame: raise the feet 36 units here so the next command starts
380 // near the middle of the unduck window. With assist enabled, also pin
381 // the post-command distance to exactly 37.
382 bool forced = false;
383 if (assist)
384 forced = jb_force_next_distance(local, ent, vel, x + JB_DUCK_RAISE,
385 JB_TARGET_DIST);
330 cmd->buttons |= IN_DUCK; 386 cmd->buttons |= IN_DUCK;
331 jb_state = 1; 387 jb_state = 1;
332 if (jb_logs < 20) { 388 if (jb_logs < 30) {
333 jb_logs++; 389 jb_logs++;
334 logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, jb_drop, 390 if (assist)
335 x + JB_DUCK_RAISE - jb_drop, vz); 391 logf("jumpbug: setup assist x=%.2f drop=%.2f vz=%.1f ok=%d", x,
392 jb_drop, vel[2], (int)forced);
393 else
394 logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x,
395 jb_drop, x + JB_DUCK_RAISE - jb_drop, vz);
336 } 396 }
337 } 397 }
338 } 398 }