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 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hl2h.h | 1 + src/menu.cpp | 21 ++++++++++++++++----- 3 files changed, 73 insertions(+), 5 deletions(-) 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; diff --git a/src/hl2h.h b/src/hl2h.h index 50b5525..0986017 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -161,6 +161,7 @@ void find_modules(); void init_conmsg(); bool feature_active(int feature); bool esp_option(int opt); +bool bhop_autostrafe_enabled(); void *entitylist_get(int idx); int engine_get_local_player(); void draw_text_col(int x, int y, const char *s, int r, int g, int b); diff --git a/src/menu.cpp b/src/menu.cpp index 2caf0aa..c8dffc9 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -52,6 +52,7 @@ struct FeatureState { }; static FeatureState g_bhop{true, 0}; +static bool g_bhop_autostrafe = true; static FeatureState g_abh{true, 0}; static FeatureState g_esp{true, 0}; @@ -64,6 +65,7 @@ static bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true}; static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}, nullptr, nullptr, nullptr}; static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr}; +static MenuNode g_bhop_autostrafe_node{NODE_TOGGLE, "Uni-directional auto-strafe", false, 0, {}, &g_bhop_autostrafe, nullptr, nullptr}; static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; static MenuNode g_esp_feat{NODE_FEATURE, "ESP", true, 1, {}, nullptr, nullptr, nullptr}; @@ -83,6 +85,8 @@ bool g_menu_open = false; static bool g_bind_mode = false; static void build_tree() { g_bhop_feat.children[0] = &g_bhop_active; + g_bhop_feat.children[1] = &g_bhop_autostrafe_node; + g_bhop_feat.child_count = 2; g_abh_feat.children[0] = &g_abh_active; g_esp_feat.children[0] = &g_esp_active; for (int i = 0; i < ESP_OPT_COUNT; i++) @@ -151,7 +155,8 @@ static void config_save() { FILE *f = fopen(config_path().c_str(), "w"); if (!f) return; - fprintf(f, "[bhop]\nactive=%d\nbind=%d\n", g_bhop.active, g_bhop.bind); + fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, + g_bhop.bind, g_bhop_autostrafe); fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" "healthnum=%d\nname=%d\n", @@ -167,7 +172,7 @@ static void config_load() { return; char line[256]; FeatureState *cur = nullptr; - bool in_esp = false; + bool in_esp = false, in_bhop = false; while (fgets(line, sizeof(line), f)) { line[strcspn(line, "\r\n")] = 0; if (line[0] == '[') { @@ -180,12 +185,16 @@ static void config_load() { else cur = nullptr; in_esp = (cur == &g_esp); + in_bhop = (cur == &g_bhop); } else if (cur) { char *eq = strchr(line, '='); if (!eq) continue; *eq = 0; const char *key = line, *val = eq + 1; + if (in_bhop && strcmp(key, "autostrafe") == 0) { + g_bhop_autostrafe = atoi(val) != 0; + } if (in_esp) { if (strcmp(key, "neutral") == 0) g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0; @@ -319,6 +328,8 @@ bool esp_option(int opt) { return opt >= 0 && opt < ESP_OPT_COUNT && g_esp_opt[opt]; } +bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; } + static void menu_handle_input() { poll_keyboard(); @@ -557,10 +568,10 @@ void outer_draw() { draw_text(x, 10 + i * 20, line); // right-aligned state if (n->type == NODE_TOGGLE) { - draw_text(200, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]"); + draw_text(270, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]"); } else if (n->type == NODE_BIND) { if (g_bind_mode && i == g_selected) - draw_text(200, 10 + i * 20, "[press key]"); + draw_text(270, 10 + i * 20, "[press key]"); else { /* stable name for current bind */ static char bn[2][32]; @@ -570,7 +581,7 @@ void outer_draw() { if (n->bind_ptr == &feats[k]->bind) fi = k; snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); - draw_text(200, 10 + i * 20, bn[fi]); + draw_text(270, 10 + i * 20, bn[fi]); } } } -- cgit v1.2.3