From 164bdcbf3e8ed2b7377351b619150586012e1007 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 15:56:03 +0100 Subject: Bhop: uni-directional auto-strafe for all movement directions - derive the desired air direction from the raw W/S/A/D input (view-relative) - pick the velocity perpendicular that rotates toward that target, so S/A/D work instead of only forward and A/D no longer spin in circles - no keys: accelerate along the crosshair - menu label now "Uni-directional auto-strafe"; state column moved to x=270 --- src/hl2h.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'src/hl2h.cpp') diff --git a/src/hl2h.cpp b/src/hl2h.cpp index a451a18..b666585 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -114,6 +114,60 @@ void *entitylist_get(int idx) { return vfunc(g_iface.entitylist, 3)(g_iface.entitylist, idx); } +// Source air-strafe. The movement code caps the *wishspeed* used for the +// acceleration delta to GetAirSpeedCap() (30) but scales accelspeed by the +// full wishspeed, then clamps to the delta. Putting wishdir perpendicular to +// the horizontal velocity makes the delta maximal every tick. We express that +// world-space direction in the current view basis so the camera is untouched. +// +// The perpendicular is chosen so the velocity turns toward the direction the +// player is actually asking for (W/S/A/D relative to the view), which keeps +// the feature useful in every direction instead of only forward. +static void apply_autostrafe(CUserCmd *cmd, const float *vel) { + float vx = vel[0], vy = vel[1]; + float speed = sqrtf(vx * vx + vy * vy); + if (speed < 1.0f) + return; + + float yaw = cmd->viewangles[1] * PI_F / 180.0f; + float cy = cosf(yaw), sy = sinf(yaw); + float fx = cy, fy = sy; // view forward on the XY plane + float rx = sy, ry = -cy; // view right on the XY plane + + // Intended ground-plane direction from the raw movement keys (before we + // overwrite them). No keys held -> keep accelerating along current travel. + float tx, ty; + if (fabsf(cmd->forwardmove) > 0.01f || fabsf(cmd->sidemove) > 0.01f) { + tx = fx * cmd->forwardmove + rx * cmd->sidemove; + ty = fy * cmd->forwardmove + ry * cmd->sidemove; + float tl = sqrtf(tx * tx + ty * ty); + tx /= tl; + ty /= tl; + } else { + // No movement keys held: accelerate along the crosshair (a velocity- + // relative target would keep rotating with the velocity and circle). + tx = fx; + ty = fy; + } + + // +90 degrees from horizontal velocity ("left" in Source's XY convention); + // the other perpendicular is its negation. Pick the side that rotates the + // velocity toward the intended direction. + float px = -vy / speed, py = vx / speed; + float cross = vx * ty - vy * tx; // > 0 => target is left of velocity + static int last_side = -1; + int side = (cross >= 0.0f) ? -1 : 1; + if (fabsf(cross) < 1e-6f) // exactly aligned/opposite: keep previous side + side = last_side; + last_side = side; + + float dx = (side < 0) ? px : -px; + float dy = (side < 0) ? py : -py; + // 450 saturates the usercmd move clamps and the engine's air-accel cap. + cmd->forwardmove = (dx * fx + dy * fy) * 450.0f; + cmd->sidemove = (dx * rx + dy * ry) * 450.0f; +} + static void do_move(int seq) { static bool g_echo_sent = false; if (!g_input || !g_net.ok()) @@ -177,6 +231,8 @@ static void do_move(int seq) { if (!onground) { lastground = false; cmd->buttons &= ~IN_JUMP; + if (!inwater && bhop_autostrafe_enabled()) + apply_autostrafe(cmd, vel); note_modified(cmd, flags, vel); } else if (!lastground) { lastground = true; -- cgit v1.2.3