summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTEXT.md53
-rw-r--r--docs/adr/0001-custom-isurface-gui.md6
-rw-r--r--src/gui.cpp25
-rw-r--r--src/hl2h.h3
-rw-r--r--src/menu.cpp359
5 files changed, 35 insertions, 411 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 0cb05c5..5e0e4b2 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -94,52 +94,11 @@ Relevant vtable slots (this build):
94 94
95## Menu 95## Menu
96 96
97Preview key: `F1` (the final GUI will replace this tree). Starts fully collapsed on first open. `F1` toggles; `Up`/`Down` navigate; 97There is a single menu: the custom GUI, toggled with `Insert`. The old
98`Enter` expands/toggles; `Left`/`Right` edit values; `Escape` closes. 98text-tree menu was removed after the GUI reached parity. Every feature still
99Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets 99has an active flag and a bind (SDL scancode; mouse buttons 600..604; None =
100None (always-on), `Enter` cancels. 100always-on), and all settings are persisted in the INI described under GUI
101 101vocabulary / decisions.
102> The text menu below is being replaced by the GUI described in the next
103> section. The GUI now drives the same feature state/config. The old tree is
104> kept only for a verification pass; **Insert** toggles the GUI and the old
105> text tree is on **F1**.
106
107```
108HL2hake
109 Bunnyhop
110 Active
111 Uni-directional auto-strafe
112 ABH
113 Active
114 Jumpbug
115 Active
116 Assist timing
117 Melee killbot
118 Active
119 Spread control
120 Active
121 No spread
122 ESP
123 Active
124 Show neutral / healthbar / healthnum / name / skeleton / box / speed graph
125 Aimbot
126 Active
127 Bind
128 Vischeck
129 Bones -> Head / Torso / Left arm / Right arm / Left leg / Right leg
130 Smoothness (1..45 deg/tick, Left/Right)
131 FOV (1..180 deg, Left/Right)
132 Show FOV circle
133 Triggerbot
134 Active
135 Bind
136 Silent aim
137 Active
138 Bind
139 FOV (1..180 deg, Left/Right)
140```
141
142Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
143 102
144## GUI vocabulary / decisions 103## GUI vocabulary / decisions
145 104
@@ -170,7 +129,7 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
170 category and expanded cards persisted under `[gui]` in the same INI. Esc 129 category and expanded cards persisted under `[gui]` in the same INI. Esc
171 does not close the GUI; the toggle key (Insert) is the only close path. 130 does not close the GUI; the toggle key (Insert) is the only close path.
172 There is no title-bar X. 131 There is no title-bar X.
173- GUI controls write the same state the text tree used; sliders persist when 132- GUI controls write the shared feature state; sliders persist when
174 the drag ends, toggles/binds/layout persist immediately. `[gui]` stores 133 the drag ends, toggles/binds/layout persist immediately. `[gui]` stores
175 `x`, `y`, `category` and an `expanded` bitmask (card index order). Every 134 `x`, `y`, `category` and an `expanded` bitmask (card index order). Every
176 feature now persists an `active` and `bind` pair, including movement, melee 135 feature now persists an `active` and `bind` pair, including movement, melee
diff --git a/docs/adr/0001-custom-isurface-gui.md b/docs/adr/0001-custom-isurface-gui.md
index 21fc7f6..f2f1e4b 100644
--- a/docs/adr/0001-custom-isurface-gui.md
+++ b/docs/adr/0001-custom-isurface-gui.md
@@ -53,7 +53,5 @@ path.
53- No new runtime dependency or GL context management. 53- No new runtime dependency or GL context management.
54- The GUI remains inside the engine's VGUI paint path, so it inherits the 54- The GUI remains inside the engine's VGUI paint path, so it inherits the
55 existing font registration and in-game overlay behavior. 55 existing font registration and in-game overlay behavior.
56- The GUI drives the real feature state/config. The old text menu is removed 56- The GUI drives the real feature state/config and is the only menu. The old
57 after a verification pass; Insert toggles the GUI and F1 keeps the old tree 57 text-tree menu was removed after parity; Insert toggles the GUI.
58 accessible until then. The replacement is complete when the old tree is
59 deleted.
diff --git a/src/gui.cpp b/src/gui.cpp
index 7baf383..132068e 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -1,8 +1,7 @@
1// gui.cpp — custom immediate-mode GUI 1// gui.cpp — custom immediate-mode GUI
2// 2//
3// Categories, feature cards and controls are wired to the real settings in 3// Categories, feature cards and controls are wired to the real settings in
4// menu.cpp. The old text tree is still reachable on F1 until the GUI has been 4// menu.cpp. This is the only menu; Insert toggles it.
5// verified as a full replacement.
6 5
7#include "hl2h.h" 6#include "hl2h.h"
8 7
@@ -653,7 +652,6 @@ static void open_gui() {
653 g_scroll = 0.0f; 652 g_scroll = 0.0f;
654 if (g_cat < 0 || g_cat >= CAT_COUNT) 653 if (g_cat < 0 || g_cat >= CAT_COUNT)
655 g_cat = 0; 654 g_cat = 0;
656 menu_force_close();
657 logf("gui: opened"); 655 logf("gui: opened");
658} 656}
659 657
@@ -707,8 +705,6 @@ static void handle_capture(const unsigned char *keys) {
707 continue; 705 continue;
708 if (k == 73) // Insert is reserved for opening/closing the GUI 706 if (k == 73) // Insert is reserved for opening/closing the GUI
709 continue; 707 continue;
710 if (k == 58) // F1 opens the old text tree during the verification pass
711 continue;
712 if (card.feat) 708 if (card.feat)
713 card.feat->bind = k; 709 card.feat->bind = k;
714 g_capture_card = -1; 710 g_capture_card = -1;
@@ -1271,12 +1267,19 @@ void gui_draw() {
1271 Rect foot{win.x, win.y + win.h - S(30.0f), win.w, S(30.0f)}; 1267 Rect foot{win.x, win.y + win.h - S(30.0f), win.w, S(30.0f)};
1272 fill(foot, Col{24, 27, 33, 255}); 1268 fill(foot, Col{24, 27, 33, 255});
1273 line(foot.x, foot.y, foot.x + foot.w, foot.y, C_BORDER); 1269 line(foot.x, foot.y, foot.x + foot.w, foot.y, C_BORDER);
1274 text_in(foot, foot.x + S(12.0f), foot.y + S(6.0f), 1270 const char *hint =
1275 "config: ~/.config/hl2hake/config", g_font_body, g_body_h, 1271 g_capture_card >= 0 ? "press a key / Esc" : "INS close";
1276 foot.w - S(220.0f), C_DIM); 1272 int hint_w = text_w(hint, g_body_h);
1277 text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f), 1273 int hint_x = foot.x + foot.w - hint_w - S(12.0f);
1278 g_capture_card >= 0 ? "press a key / Esc" : "INS close | F1 old menu", 1274 int left_x = foot.x + S(12.0f);
1279 g_font_body, g_body_h, S(190.0f), C_DIM); 1275 if (hint_x < left_x)
1276 hint_x = left_x;
1277 int left_max = hint_x - left_x - S(12.0f);
1278 if (left_max > 0)
1279 text_in(foot, left_x, foot.y + S(6.0f), g_config_label, g_font_body,
1280 g_body_h, left_max, C_DIM);
1281 text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h,
1282 hint_w + S(4.0f), C_DIM);
1280 1283
1281 draw_tooltip(); 1284 draw_tooltip();
1282} 1285}
diff --git a/src/hl2h.h b/src/hl2h.h
index 678c5af..0f03925 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -185,7 +185,7 @@ enum AimBone {
185}; 185};
186 186
187// --------------------------------------------------------------------------- 187// ---------------------------------------------------------------------------
188// feature state / settings (owned by menu.cpp until the text menu is removed) 188// feature state / settings (owned by menu.cpp alongside config)
189// --------------------------------------------------------------------------- 189// ---------------------------------------------------------------------------
190 190
191struct FeatureState { 191struct FeatureState {
@@ -268,7 +268,6 @@ bool head_position(int idx, Vector &out);
268int aim_points(int idx, unsigned group_mask, Vector *out, int max); 268int aim_points(int idx, unsigned group_mask, Vector *out, int max);
269 269
270void outer_draw(); 270void outer_draw();
271void menu_force_close();
272void gui_handle_input(); 271void gui_handle_input();
273void gui_draw(); 272void gui_draw();
274bool gui_is_open(); 273bool gui_is_open();
diff --git a/src/menu.cpp b/src/menu.cpp
index 2a96b02..5fc325e 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -1,4 +1,4 @@
1// menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse 1// menu.cpp — feature state, config, overlay draw + ISurface discovery
2#include "hl2h.h" 2#include "hl2h.h"
3#include <cmath> 3#include <cmath>
4#include <cstdio> 4#include <cstdio>
@@ -29,28 +29,6 @@ void init_conmsg() {
29 if (h) 29 if (h)
30 g_conmsg = (decltype(g_conmsg))dlsym(h, "_Z6ConMsgPKcz"); 30 g_conmsg = (decltype(g_conmsg))dlsym(h, "_Z6ConMsgPKcz");
31} 31}
32// ---------------------------------------------------------------------------
33// menu tree structure
34// ---------------------------------------------------------------------------
35
36enum NodeType { NODE_ROOT, NODE_FEATURE, NODE_TOGGLE, NODE_BIND, NODE_VALUE };
37
38struct MenuNode {
39 NodeType type;
40 const char *label;
41 bool expanded; // for root/feature: whether children are visible
42 int child_count;
43 MenuNode *children[12]; // max children per node
44
45 // leaf data
46 bool *active_ptr; // NODE_TOGGLE: pointer to the active flag
47 int *bind_ptr; // NODE_BIND: pointer to the key binding (SDL scancode)
48 const char *bind_name; // NODE_BIND: current bind display name
49 int *value_ptr = nullptr; // NODE_VALUE: pointer to the value
50 int value_min = 0;
51 int value_max = 0;
52 int value_step = 0;
53};
54 32
55// feature data (declared in hl2h.h; shared with the GUI) 33// feature data (declared in hl2h.h; shared with the GUI)
56FeatureState g_bhop{true, 0}; 34FeatureState g_bhop{true, 0};
@@ -63,7 +41,7 @@ FeatureState g_melee{true, 0};
63FeatureState g_spread{true, 0}; 41FeatureState g_spread{true, 0};
64bool g_spread_no_spread = true; 42bool g_spread_no_spread = true;
65FeatureState g_esp{true, 0}; 43FeatureState g_esp{true, 0};
66FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu 44FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in the GUI
67FeatureState g_triggerbot{true, 603}; // default Mouse4 45FeatureState g_triggerbot{true, 603}; // default Mouse4
68FeatureState g_silentaim{true, 602}; // default Mouse3 46FeatureState g_silentaim{true, 602}; // default Mouse3
69 47
@@ -76,146 +54,9 @@ int g_aim_fov = 60; // angular distance from the crosshair
76bool g_aim_fov_circle = true; 54bool g_aim_fov_circle = true;
77int g_silent_fov = 60; 55int g_silent_fov = 60;
78 56
79// ESP sub-options (menu "ESP" children). Defaults match the pre-option 57// ESP sub-options. Defaults match the pre-option behaviour: everything on.
80// behaviour: everything on.
81bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true}; 58bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true};
82 59
83// tree
84static MenuNode g_root{NODE_ROOT, "HL2hake", false, 0, {}, nullptr, nullptr, nullptr};
85static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", false, 1, {}, nullptr, nullptr, nullptr};
86static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr};
87static MenuNode g_bhop_autostrafe_node{NODE_TOGGLE, "Uni-directional auto-strafe", false, 0, {}, &g_bhop_autostrafe, nullptr, nullptr};
88static MenuNode g_abh_feat{NODE_FEATURE, "ABH", false, 1, {}, nullptr, nullptr, nullptr};
89static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr};
90static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr};
91static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr};
92static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr};
93static MenuNode g_melee_feat{NODE_FEATURE, "Melee killbot", false, 1, {}, nullptr, nullptr, nullptr};
94static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr};
95static MenuNode g_spread_feat{NODE_FEATURE, "Spread control", false, 1, {}, nullptr, nullptr, nullptr};
96static MenuNode g_spread_active{NODE_TOGGLE, "Active", false, 0, {}, &g_spread.active, nullptr, nullptr};
97static MenuNode g_spread_no_spread_node{NODE_TOGGLE, "No spread", false, 0, {}, &g_spread_no_spread, nullptr, nullptr};
98static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr};
99static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr};
100static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = {
101 {NODE_TOGGLE, "Show neutral", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NEUTRAL], nullptr, nullptr},
102 {NODE_TOGGLE, "Show healthbar", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], nullptr, nullptr},
103 {NODE_TOGGLE, "Show healthnum", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], nullptr, nullptr},
104 {NODE_TOGGLE, "Show name", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NAME], nullptr, nullptr},
105 {NODE_TOGGLE, "Show skeleton", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SKELETON], nullptr, nullptr},
106 {NODE_TOGGLE, "Show box", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_BOX], nullptr, nullptr},
107 {NODE_TOGGLE, "Show speed graph", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH], nullptr, nullptr},
108};
109static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nullptr, nullptr};
110static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr};
111static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr};
112static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr};
113static MenuNode g_aimbot_vischeck_node{NODE_TOGGLE, "Vischeck", false, 0, {}, &g_aim_vischeck, nullptr, nullptr};
114static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = {
115 {NODE_TOGGLE, "Head", false, 0, {}, &g_aim_bones[AIM_BONE_HEAD], nullptr, nullptr},
116 {NODE_TOGGLE, "Torso", false, 0, {}, &g_aim_bones[AIM_BONE_TORSO], nullptr, nullptr},
117 {NODE_TOGGLE, "Left arm", false, 0, {}, &g_aim_bones[AIM_BONE_LARM], nullptr, nullptr},
118 {NODE_TOGGLE, "Right arm", false, 0, {}, &g_aim_bones[AIM_BONE_RARM], nullptr, nullptr},
119 {NODE_TOGGLE, "Left leg", false, 0, {}, &g_aim_bones[AIM_BONE_LLEG], nullptr, nullptr},
120 {NODE_TOGGLE, "Right leg", false, 0, {}, &g_aim_bones[AIM_BONE_RLEG], nullptr, nullptr},
121};
122static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr,
123 nullptr, nullptr, &g_aim_smooth, 1, 45, 1};
124static MenuNode g_aimbot_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr,
125 nullptr, nullptr, &g_aim_fov, 1, 180, 1};
126static MenuNode g_aimbot_fov_circle_node{NODE_TOGGLE, "Show FOV circle", false, 0, {}, &g_aim_fov_circle, nullptr, nullptr};
127static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr};
128static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr};
129static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr};
130static MenuNode g_silentaim_feat{NODE_FEATURE, "Silent aim", false, 1, {}, nullptr, nullptr, nullptr};
131static MenuNode g_silentaim_active{NODE_TOGGLE, "Active", false, 0, {}, &g_silentaim.active, nullptr, nullptr};
132static MenuNode g_silentaim_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_silentaim.bind, nullptr};
133static MenuNode g_silentaim_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr,
134 nullptr, nullptr, &g_silent_fov, 1, 180, 1};
135
136static MenuNode *g_visible[48]; // flattened visible nodes for arrow navigation
137static int g_visible_count = 0;
138static int g_selected = 0;
139bool g_menu_open = false;
140static bool g_menu_draw_logged = false;
141
142static bool g_bind_mode = false;
143static bool g_insert_latch = false;
144static void build_tree() {
145 g_bhop_feat.children[0] = &g_bhop_active;
146 g_bhop_feat.children[1] = &g_bhop_autostrafe_node;
147 g_bhop_feat.child_count = 2;
148 g_abh_feat.children[0] = &g_abh_active;
149 g_abh_feat.child_count = 1;
150 g_jumpbug_feat.children[0] = &g_jumpbug_active;
151 g_jumpbug_feat.children[1] = &g_jumpbug_assist_node;
152 g_jumpbug_feat.child_count = 2;
153 g_melee_feat.children[0] = &g_melee_active;
154 g_melee_feat.child_count = 1;
155 g_spread_feat.children[0] = &g_spread_active;
156 g_spread_feat.children[1] = &g_spread_no_spread_node;
157 g_spread_feat.child_count = 2;
158 g_esp_feat.children[0] = &g_esp_active;
159 g_aimbot_feat.children[0] = &g_aimbot_active;
160 g_aimbot_feat.children[1] = &g_aimbot_bind;
161 g_aimbot_feat.children[2] = &g_aimbot_vischeck_node;
162 g_aimbot_feat.children[3] = &g_aimbot_bones_feat;
163 g_aimbot_feat.children[4] = &g_aimbot_smooth_node;
164 g_aimbot_feat.children[5] = &g_aimbot_fov_node;
165 g_aimbot_feat.children[6] = &g_aimbot_fov_circle_node;
166 g_aimbot_feat.child_count = 7;
167 for (int i = 0; i < AIM_BONE_COUNT; i++)
168 g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i];
169 g_aimbot_bones_feat.child_count = AIM_BONE_COUNT;
170 g_triggerbot_feat.children[0] = &g_triggerbot_active;
171 g_triggerbot_feat.children[1] = &g_triggerbot_bind;
172 g_triggerbot_feat.child_count = 2;
173 g_silentaim_feat.children[0] = &g_silentaim_active;
174 g_silentaim_feat.children[1] = &g_silentaim_bind;
175 g_silentaim_feat.children[2] = &g_silentaim_fov_node;
176 g_silentaim_feat.child_count = 3;
177 for (int i = 0; i < ESP_OPT_COUNT; i++)
178 g_esp_feat.children[i + 1] = &g_esp_opt_nodes[i];
179 g_esp_feat.child_count = ESP_OPT_COUNT + 1;
180 g_root.children[0] = &g_bhop_feat;
181 g_root.children[1] = &g_abh_feat;
182 g_root.children[2] = &g_jumpbug_feat;
183 g_root.children[3] = &g_melee_feat;
184 g_root.children[4] = &g_spread_feat;
185 g_root.children[5] = &g_esp_feat;
186 g_root.children[6] = &g_aimbot_feat;
187 g_root.children[7] = &g_triggerbot_feat;
188 g_root.children[8] = &g_silentaim_feat;
189 g_root.child_count = 9;
190}
191
192static int g_depth[48]; // depth of each visible row (for indentation)
193
194static void flatten(MenuNode *node, int depth) {
195 if (g_visible_count >= 48)
196 return;
197 node->type = (depth == 0) ? NODE_ROOT
198 : (node->child_count > 0) ? NODE_FEATURE
199 : (node->active_ptr) ? NODE_TOGGLE
200 : (node->value_ptr) ? NODE_VALUE
201 : NODE_BIND;
202 g_depth[g_visible_count] = depth;
203 g_visible[g_visible_count++] = node;
204 if (node->expanded) {
205 for (int i = 0; i < node->child_count; i++)
206 flatten(node->children[i], depth + 1);
207 }
208}
209
210static void rebuild_visible() {
211 g_visible_count = 0;
212 flatten(&g_root, 0);
213 if (g_selected >= g_visible_count)
214 g_selected = g_visible_count - 1;
215 if (g_selected < 0)
216 g_selected = 0;
217}
218
219// --------------------------------------------------------------------------- 60// ---------------------------------------------------------------------------
220// config persistence (INI format) 61// config persistence (INI format)
221// --------------------------------------------------------------------------- 62// ---------------------------------------------------------------------------
@@ -483,20 +324,6 @@ void config_reset_defaults() {
483 config_save(); 324 config_save();
484} 325}
485 326
486// ---------------------------------------------------------------------------
487// menu input handling
488// ---------------------------------------------------------------------------
489
490// SDL scancodes for navigation keys (matches the game's scancode set)
491static constexpr int SCAN_INSERT = 73; // reserved for the GUI
492static constexpr int SCAN_TEXT_MENU = 58; // F1 preview key for the old tree
493static constexpr int SCAN_UP = 82; // these are SDL scancodes
494static constexpr int SCAN_DOWN = 81;
495static constexpr int SCAN_LEFT = 80;
496static constexpr int SCAN_RIGHT = 79;
497static constexpr int SCAN_ENTER = 40;
498static constexpr int SCAN_ESCAPE = 41;
499
500// simplified scancode-to-name for common keys 327// simplified scancode-to-name for common keys
501const char *scancode_name(int sc) { 328const char *scancode_name(int sc) {
502 switch (sc) { 329 switch (sc) {
@@ -531,9 +358,8 @@ const char *scancode_name(int sc) {
531 } 358 }
532} 359}
533 360
534// get a snapshot of key states: returns array of currently-held scancodes 361// held state for feature binds
535static bool g_prev_key_state[608] = {}; 362static bool g_prev_key_state[608] = {};
536static bool g_key_down[608] = {}; // true for ONE frame on press
537 363
538// read SDL_GetKeyboardState — the game's own SDL is in-process 364// read SDL_GetKeyboardState — the game's own SDL is in-process
539using SDL_GetKeyboardState_fn = const uint8_t *(*)(int *); 365using SDL_GetKeyboardState_fn = const uint8_t *(*)(int *);
@@ -556,7 +382,6 @@ static void poll_mouse() {
556 for (int b = 0; b < 5; b++) { 382 for (int b = 0; b < 5; b++) {
557 bool down = (mask & (1u << b)) != 0; // SDL buttons 1..5 = bits 0..4 383 bool down = (mask & (1u << b)) != 0; // SDL buttons 1..5 = bits 0..4
558 int sc = MOUSE_BASE + b; 384 int sc = MOUSE_BASE + b;
559 g_key_down[sc] = down && !g_prev_key_state[sc];
560 g_prev_key_state[sc] = down; 385 g_prev_key_state[sc] = down;
561 } 386 }
562} 387}
@@ -570,18 +395,12 @@ static void poll_keyboard() {
570 int numkeys = 0; 395 int numkeys = 0;
571 const uint8_t *state = SDL_GetKeyboardState_real(&numkeys); 396 const uint8_t *state = SDL_GetKeyboardState_real(&numkeys);
572 if (state) 397 if (state)
573 for (int i = 0; i < numkeys && i < MOUSE_BASE; i++) { 398 for (int i = 0; i < numkeys && i < MOUSE_BASE; i++)
574 g_key_down[i] = state[i] && !g_prev_key_state[i]; 399 g_prev_key_state[i] = state[i] != 0;
575 g_prev_key_state[i] = state[i];
576 }
577 poll_mouse(); 400 poll_mouse();
578} 401}
579 402
580static bool key_pressed(int sc) { 403static bool key_held(int sc) {
581 return sc >= 0 && sc < MOUSE_BASE + 5 && g_key_down[sc];
582}
583
584[[maybe_unused]] static bool key_held(int sc) {
585 return sc >= 0 && sc < MOUSE_BASE + 5 && g_prev_key_state[sc]; 404 return sc >= 0 && sc < MOUSE_BASE + 5 && g_prev_key_state[sc];
586} 405}
587 406
@@ -650,118 +469,8 @@ int silentaim_fov() { return g_silent_fov; }
650 469
651bool aimbot_fov_circle() { return g_aim_fov_circle; } 470bool aimbot_fov_circle() { return g_aim_fov_circle; }
652 471
653void menu_force_close() {
654 g_menu_open = false;
655 g_bind_mode = false;
656 g_insert_latch = false;
657 g_menu_draw_logged = false;
658}
659
660static void menu_handle_input() {
661 poll_keyboard();
662 // SDL state can be sampled several times per rendered frame (both ISurface
663 // hooks call outer_draw). Use a physical-key latch instead of the one-poll
664 // edge so one press cannot open and close the menu in the same frame.
665 bool insert_down = key_held(SCAN_TEXT_MENU);
666 bool insert_edge = insert_down && !g_insert_latch;
667 if (insert_down)
668 g_insert_latch = true;
669 else
670 g_insert_latch = false;
671
672 if (!g_menu_open) {
673 if (insert_edge) {
674 g_menu_open = true;
675 g_bind_mode = false;
676 if (!g_root.expanded)
677 g_root.expanded = true;
678 rebuild_visible();
679 logf("menu: open rows=%d", g_visible_count);
680 }
681 return;
682 }
683
684 // menu is open
685 if (g_bind_mode) {
686 // wait for any key press (except Insert, Up, Down, Escape, Enter)
687 for (int i = 0; i < MOUSE_BASE + 5; i++) {
688 if (!g_key_down[i])
689 continue;
690 if (i == SCAN_INSERT || i == SCAN_TEXT_MENU || i == SCAN_UP ||
691 i == SCAN_DOWN || i == SCAN_LEFT || i == SCAN_RIGHT) {
692 g_bind_mode = false;
693 return; // cancel bind select
694 }
695 if (i == SCAN_ESCAPE) {
696 // set bind to None
697 MenuNode *n = g_visible[g_selected];
698 if (n->bind_ptr)
699 *n->bind_ptr = 0;
700 g_bind_mode = false;
701 config_save();
702 rebuild_visible();
703 return;
704 }
705 if (i == SCAN_ENTER) {
706 g_bind_mode = false;
707 return; // cancel, keep old bind
708 }
709 // any other key = new bind (features may share the same bind)
710 MenuNode *n = g_visible[g_selected];
711 if (n->bind_ptr)
712 *n->bind_ptr = i;
713 g_bind_mode = false;
714 config_save();
715 rebuild_visible();
716 return;
717 }
718 return; // no key pressed yet
719 }
720
721 // normal navigation
722 if (insert_edge || key_pressed(SCAN_ESCAPE)) {
723 g_menu_open = false;
724 g_menu_draw_logged = false;
725 logf("menu: closed");
726 return;
727 }
728 if (key_pressed(SCAN_UP) && g_selected > 0) {
729 g_selected--;
730 return;
731 }
732 if (key_pressed(SCAN_DOWN) && g_selected < g_visible_count - 1) {
733 g_selected++;
734 return;
735 }
736 if (key_pressed(SCAN_LEFT) || key_pressed(SCAN_RIGHT)) {
737 MenuNode *n = g_visible[g_selected];
738 if (n->type == NODE_VALUE && n->value_ptr) {
739 int dir = key_pressed(SCAN_RIGHT) ? 1 : -1;
740 int step = n->value_step ? n->value_step : 1;
741 int v = *n->value_ptr + dir * step;
742 if (v < n->value_min) v = n->value_min;
743 if (v > n->value_max) v = n->value_max;
744 *n->value_ptr = v;
745 config_save();
746 }
747 return;
748 }
749 if (key_pressed(SCAN_ENTER)) {
750 MenuNode *n = g_visible[g_selected];
751 if (n->child_count > 0) {
752 n->expanded = !n->expanded;
753 rebuild_visible();
754 } else if (n->type == NODE_TOGGLE && n->active_ptr) {
755 *n->active_ptr = !(*n->active_ptr);
756 config_save();
757 } else if (n->type == NODE_BIND) {
758 g_bind_mode = true;
759 }
760 }
761}
762
763// --------------------------------------------------------------------------- 472// ---------------------------------------------------------------------------
764// menu rendering (VGUI ISurface) 473// overlay rendering (VGUI ISurface)
765// --------------------------------------------------------------------------- 474// ---------------------------------------------------------------------------
766 475
767// VGUI surface interface 476// VGUI surface interface
@@ -869,13 +578,12 @@ void draw_text_col(int x, int y, const char *text, int r, int g, int b) {
869// (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in 578// (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in
870// PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. 579// PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip.
871void outer_draw() { 580void outer_draw() {
581 poll_keyboard();
872 gui_handle_input(); 582 gui_handle_input();
873 if (!gui_is_open()) 583 // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open
874 menu_handle_input();
875 // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open
876 static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; 584 static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr;
877 bool esp_on = feature_active(FEAT_ESP); 585 bool esp_on = feature_active(FEAT_ESP);
878 if (!g_surface || (!g_menu_open && !gui_is_open() && !draw_test && !esp_on)) 586 if (!g_surface || (!gui_is_open() && !draw_test && !esp_on))
879 return; 587 return;
880 588
881 // Level/menu -> game transitions can invalidate the VGUI font handle. 589 // Level/menu -> game transitions can invalidate the VGUI font handle.
@@ -929,50 +637,8 @@ void outer_draw() {
929 surf_draw_set_color(255, 255, 255, 255); 637 surf_draw_set_color(255, 255, 255, 255);
930 draw_text(28, 36, "HL2H DRAW TEST"); 638 draw_text(28, 36, "HL2H DRAW TEST");
931 } 639 }
932 if (gui_is_open()) { 640 if (gui_is_open())
933 gui_draw(); 641 gui_draw();
934 surf_pop_make_current(panel);
935 return;
936 }
937 if (!g_menu_open) {
938 surf_pop_make_current(panel);
939 return;
940 }
941 if (!g_menu_draw_logged) {
942 g_menu_draw_logged = true;
943 logf("menu: drawing rows=%d selected=%d", g_visible_count, g_selected);
944 }
945 for (int i = 0; i < g_visible_count; i++) {
946 MenuNode *n = g_visible[i];
947 char line[96];
948 int x = 12 + g_depth[i] * 12;
949 snprintf(line, sizeof(line), "%s %s",
950 (i == g_selected) ? ">" : (n->child_count ? "-" : " "), n->label);
951 draw_text(x, 10 + i * 20, line);
952 // right-aligned state
953 if (n->type == NODE_TOGGLE) {
954 draw_text(270, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]");
955 } else if (n->type == NODE_BIND) {
956 if (g_bind_mode && i == g_selected)
957 draw_text(270, 10 + i * 20, "[press key]");
958 else {
959 /* stable name for current bind */
960 static char bn[5][32];
961 static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot,
962 &g_triggerbot, &g_silentaim};
963 int fi = 0;
964 for (int k = 0; k < 5; k++)
965 if (n->bind_ptr == &feats[k]->bind)
966 fi = k;
967 snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr));
968 draw_text(270, 10 + i * 20, bn[fi]);
969 }
970 } else if (n->type == NODE_VALUE) {
971 char vb[32];
972 snprintf(vb, sizeof(vb), "%d deg/tick", *n->value_ptr);
973 draw_text(270, 10 + i * 20, vb);
974 }
975 }
976 surf_pop_make_current(panel); 642 surf_pop_make_current(panel);
977} 643}
978 644
@@ -1029,7 +695,6 @@ static ISurface *find_surface_interface() {
1029} 695}
1030 696
1031void install_menu() { 697void install_menu() {
1032 build_tree();
1033 register_bundled_font(); 698 register_bundled_font();
1034 logf("config: %s", config_path().c_str()); 699 logf("config: %s", config_path().c_str());
1035 config_load(); 700 config_load();