summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-26 15:56:03 +0100
committersalmonik <salmonik@tuta.io>2026-10-26 15:56:03 +0100
commit164bdcbf3e8ed2b7377351b619150586012e1007 (patch)
tree63660fc6f33c0e77e81f73a5d544418307e1b7e5 /src
parentESP: menu toggles for neutral/healthbar/healthnum/name (diff)
downloadhl2h-164bdcbf3e8ed2b7377351b619150586012e1007.tar.gz
hl2h-164bdcbf3e8ed2b7377351b619150586012e1007.zip
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
Diffstat (limited to '')
-rw-r--r--src/hl2h.cpp56
-rw-r--r--src/hl2h.h1
-rw-r--r--src/menu.cpp21
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) {
114 return vfunc<Fn>(g_iface.entitylist, 3)(g_iface.entitylist, idx); 114 return vfunc<Fn>(g_iface.entitylist, 3)(g_iface.entitylist, idx);
115} 115}
116 116
117// Source air-strafe. The movement code caps the *wishspeed* used for the
118// acceleration delta to GetAirSpeedCap() (30) but scales accelspeed by the
119// full wishspeed, then clamps to the delta. Putting wishdir perpendicular to
120// the horizontal velocity makes the delta maximal every tick. We express that
121// world-space direction in the current view basis so the camera is untouched.
122//
123// The perpendicular is chosen so the velocity turns toward the direction the
124// player is actually asking for (W/S/A/D relative to the view), which keeps
125// the feature useful in every direction instead of only forward.
126static void apply_autostrafe(CUserCmd *cmd, const float *vel) {
127 float vx = vel[0], vy = vel[1];
128 float speed = sqrtf(vx * vx + vy * vy);
129 if (speed < 1.0f)
130 return;
131
132 float yaw = cmd->viewangles[1] * PI_F / 180.0f;
133 float cy = cosf(yaw), sy = sinf(yaw);
134 float fx = cy, fy = sy; // view forward on the XY plane
135 float rx = sy, ry = -cy; // view right on the XY plane
136
137 // Intended ground-plane direction from the raw movement keys (before we
138 // overwrite them). No keys held -> keep accelerating along current travel.
139 float tx, ty;
140 if (fabsf(cmd->forwardmove) > 0.01f || fabsf(cmd->sidemove) > 0.01f) {
141 tx = fx * cmd->forwardmove + rx * cmd->sidemove;
142 ty = fy * cmd->forwardmove + ry * cmd->sidemove;
143 float tl = sqrtf(tx * tx + ty * ty);
144 tx /= tl;
145 ty /= tl;
146 } else {
147 // No movement keys held: accelerate along the crosshair (a velocity-
148 // relative target would keep rotating with the velocity and circle).
149 tx = fx;
150 ty = fy;
151 }
152
153 // +90 degrees from horizontal velocity ("left" in Source's XY convention);
154 // the other perpendicular is its negation. Pick the side that rotates the
155 // velocity toward the intended direction.
156 float px = -vy / speed, py = vx / speed;
157 float cross = vx * ty - vy * tx; // > 0 => target is left of velocity
158 static int last_side = -1;
159 int side = (cross >= 0.0f) ? -1 : 1;
160 if (fabsf(cross) < 1e-6f) // exactly aligned/opposite: keep previous side
161 side = last_side;
162 last_side = side;
163
164 float dx = (side < 0) ? px : -px;
165 float dy = (side < 0) ? py : -py;
166 // 450 saturates the usercmd move clamps and the engine's air-accel cap.
167 cmd->forwardmove = (dx * fx + dy * fy) * 450.0f;
168 cmd->sidemove = (dx * rx + dy * ry) * 450.0f;
169}
170
117static void do_move(int seq) { 171static void do_move(int seq) {
118 static bool g_echo_sent = false; 172 static bool g_echo_sent = false;
119 if (!g_input || !g_net.ok()) 173 if (!g_input || !g_net.ok())
@@ -177,6 +231,8 @@ static void do_move(int seq) {
177 if (!onground) { 231 if (!onground) {
178 lastground = false; 232 lastground = false;
179 cmd->buttons &= ~IN_JUMP; 233 cmd->buttons &= ~IN_JUMP;
234 if (!inwater && bhop_autostrafe_enabled())
235 apply_autostrafe(cmd, vel);
180 note_modified(cmd, flags, vel); 236 note_modified(cmd, flags, vel);
181 } else if (!lastground) { 237 } else if (!lastground) {
182 lastground = true; 238 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();
161void init_conmsg(); 161void init_conmsg();
162bool feature_active(int feature); 162bool feature_active(int feature);
163bool esp_option(int opt); 163bool esp_option(int opt);
164bool bhop_autostrafe_enabled();
164void *entitylist_get(int idx); 165void *entitylist_get(int idx);
165int engine_get_local_player(); 166int engine_get_local_player();
166void draw_text_col(int x, int y, const char *s, int r, int g, int b); 167void 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 {
52}; 52};
53 53
54static FeatureState g_bhop{true, 0}; 54static FeatureState g_bhop{true, 0};
55static bool g_bhop_autostrafe = true;
55 56
56static FeatureState g_abh{true, 0}; 57static FeatureState g_abh{true, 0};
57static FeatureState g_esp{true, 0}; 58static FeatureState g_esp{true, 0};
@@ -64,6 +65,7 @@ static bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true};
64static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}, nullptr, nullptr, nullptr}; 65static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}, nullptr, nullptr, nullptr};
65static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}, nullptr, nullptr, nullptr}; 66static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}, nullptr, nullptr, nullptr};
66static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr}; 67static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr};
68static MenuNode g_bhop_autostrafe_node{NODE_TOGGLE, "Uni-directional auto-strafe", false, 0, {}, &g_bhop_autostrafe, nullptr, nullptr};
67static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}, nullptr, nullptr, nullptr}; 69static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}, nullptr, nullptr, nullptr};
68static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; 70static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr};
69static MenuNode g_esp_feat{NODE_FEATURE, "ESP", true, 1, {}, nullptr, nullptr, nullptr}; 71static MenuNode g_esp_feat{NODE_FEATURE, "ESP", true, 1, {}, nullptr, nullptr, nullptr};
@@ -83,6 +85,8 @@ bool g_menu_open = false;
83static bool g_bind_mode = false; 85static bool g_bind_mode = false;
84static void build_tree() { 86static void build_tree() {
85 g_bhop_feat.children[0] = &g_bhop_active; 87 g_bhop_feat.children[0] = &g_bhop_active;
88 g_bhop_feat.children[1] = &g_bhop_autostrafe_node;
89 g_bhop_feat.child_count = 2;
86 g_abh_feat.children[0] = &g_abh_active; 90 g_abh_feat.children[0] = &g_abh_active;
87 g_esp_feat.children[0] = &g_esp_active; 91 g_esp_feat.children[0] = &g_esp_active;
88 for (int i = 0; i < ESP_OPT_COUNT; i++) 92 for (int i = 0; i < ESP_OPT_COUNT; i++)
@@ -151,7 +155,8 @@ static void config_save() {
151 FILE *f = fopen(config_path().c_str(), "w"); 155 FILE *f = fopen(config_path().c_str(), "w");
152 if (!f) 156 if (!f)
153 return; 157 return;
154 fprintf(f, "[bhop]\nactive=%d\nbind=%d\n", g_bhop.active, g_bhop.bind); 158 fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active,
159 g_bhop.bind, g_bhop_autostrafe);
155 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); 160 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind);
156 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" 161 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n"
157 "healthnum=%d\nname=%d\n", 162 "healthnum=%d\nname=%d\n",
@@ -167,7 +172,7 @@ static void config_load() {
167 return; 172 return;
168 char line[256]; 173 char line[256];
169 FeatureState *cur = nullptr; 174 FeatureState *cur = nullptr;
170 bool in_esp = false; 175 bool in_esp = false, in_bhop = false;
171 while (fgets(line, sizeof(line), f)) { 176 while (fgets(line, sizeof(line), f)) {
172 line[strcspn(line, "\r\n")] = 0; 177 line[strcspn(line, "\r\n")] = 0;
173 if (line[0] == '[') { 178 if (line[0] == '[') {
@@ -180,12 +185,16 @@ static void config_load() {
180 else 185 else
181 cur = nullptr; 186 cur = nullptr;
182 in_esp = (cur == &g_esp); 187 in_esp = (cur == &g_esp);
188 in_bhop = (cur == &g_bhop);
183 } else if (cur) { 189 } else if (cur) {
184 char *eq = strchr(line, '='); 190 char *eq = strchr(line, '=');
185 if (!eq) 191 if (!eq)
186 continue; 192 continue;
187 *eq = 0; 193 *eq = 0;
188 const char *key = line, *val = eq + 1; 194 const char *key = line, *val = eq + 1;
195 if (in_bhop && strcmp(key, "autostrafe") == 0) {
196 g_bhop_autostrafe = atoi(val) != 0;
197 }
189 if (in_esp) { 198 if (in_esp) {
190 if (strcmp(key, "neutral") == 0) 199 if (strcmp(key, "neutral") == 0)
191 g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0; 200 g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0;
@@ -319,6 +328,8 @@ bool esp_option(int opt) {
319 return opt >= 0 && opt < ESP_OPT_COUNT && g_esp_opt[opt]; 328 return opt >= 0 && opt < ESP_OPT_COUNT && g_esp_opt[opt];
320} 329}
321 330
331bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; }
332
322static void menu_handle_input() { 333static void menu_handle_input() {
323 poll_keyboard(); 334 poll_keyboard();
324 335
@@ -557,10 +568,10 @@ void outer_draw() {
557 draw_text(x, 10 + i * 20, line); 568 draw_text(x, 10 + i * 20, line);
558 // right-aligned state 569 // right-aligned state
559 if (n->type == NODE_TOGGLE) { 570 if (n->type == NODE_TOGGLE) {
560 draw_text(200, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]"); 571 draw_text(270, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]");
561 } else if (n->type == NODE_BIND) { 572 } else if (n->type == NODE_BIND) {
562 if (g_bind_mode && i == g_selected) 573 if (g_bind_mode && i == g_selected)
563 draw_text(200, 10 + i * 20, "[press key]"); 574 draw_text(270, 10 + i * 20, "[press key]");
564 else { 575 else {
565 /* stable name for current bind */ 576 /* stable name for current bind */
566 static char bn[2][32]; 577 static char bn[2][32];
@@ -570,7 +581,7 @@ void outer_draw() {
570 if (n->bind_ptr == &feats[k]->bind) 581 if (n->bind_ptr == &feats[k]->bind)
571 fi = k; 582 fi = k;
572 snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); 583 snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr));
573 draw_text(200, 10 + i * 20, bn[fi]); 584 draw_text(270, 10 + i * 20, bn[fi]);
574 } 585 }
575 } 586 }
576 } 587 }