summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 18:36:31 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 18:36:31 +0100
commit8ba97262a0219d7defd928d7bfb5f4214f235fc8 (patch)
treeae7e56661007bda80e61e776a5c123866349da43 /src
parentGUI: wire real feature state and config (diff)
downloadhl2h-8ba97262a0219d7defd928d7bfb5f4214f235fc8.tar.gz
hl2h-8ba97262a0219d7defd928d7bfb5f4214f235fc8.zip
GUI: fix footer clipping; remove the old text menu
- Right-align the footer hint and size the config label from the remaining width, so "INS close" is no longer truncated. - Delete the text-tree menu now that the GUI has feature parity: remove the tree/input/render code, the F1 toggle and menu_force_close. - Keep only held-key polling for feature binds; poll it every frame from outer_draw. - Update CONTEXT/ADR: the GUI is the only menu.
Diffstat (limited to '')
-rw-r--r--src/gui.cpp25
-rw-r--r--src/hl2h.h3
-rw-r--r--src/menu.cpp359
3 files changed, 27 insertions, 360 deletions
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();