diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-27 18:36:31 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-27 18:36:31 +0100 |
| commit | 8ba97262a0219d7defd928d7bfb5f4214f235fc8 (patch) | |
| tree | ae7e56661007bda80e61e776a5c123866349da43 /src/menu.cpp | |
| parent | GUI: wire real feature state and config (diff) | |
| download | hl2h-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/menu.cpp | 359 |
1 files changed, 12 insertions, 347 deletions
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 | |||
| 36 | enum NodeType { NODE_ROOT, NODE_FEATURE, NODE_TOGGLE, NODE_BIND, NODE_VALUE }; | ||
| 37 | |||
| 38 | struct 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) |
| 56 | FeatureState g_bhop{true, 0}; | 34 | FeatureState g_bhop{true, 0}; |
| @@ -63,7 +41,7 @@ FeatureState g_melee{true, 0}; | |||
| 63 | FeatureState g_spread{true, 0}; | 41 | FeatureState g_spread{true, 0}; |
| 64 | bool g_spread_no_spread = true; | 42 | bool g_spread_no_spread = true; |
| 65 | FeatureState g_esp{true, 0}; | 43 | FeatureState g_esp{true, 0}; |
| 66 | FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu | 44 | FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in the GUI |
| 67 | FeatureState g_triggerbot{true, 603}; // default Mouse4 | 45 | FeatureState g_triggerbot{true, 603}; // default Mouse4 |
| 68 | FeatureState g_silentaim{true, 602}; // default Mouse3 | 46 | FeatureState g_silentaim{true, 602}; // default Mouse3 |
| 69 | 47 | ||
| @@ -76,146 +54,9 @@ int g_aim_fov = 60; // angular distance from the crosshair | |||
| 76 | bool g_aim_fov_circle = true; | 54 | bool g_aim_fov_circle = true; |
| 77 | int g_silent_fov = 60; | 55 | int 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. | ||
| 81 | bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true}; | 58 | bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true}; |
| 82 | 59 | ||
| 83 | // tree | ||
| 84 | static MenuNode g_root{NODE_ROOT, "HL2hake", false, 0, {}, nullptr, nullptr, nullptr}; | ||
| 85 | static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 86 | static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr}; | ||
| 87 | static MenuNode g_bhop_autostrafe_node{NODE_TOGGLE, "Uni-directional auto-strafe", false, 0, {}, &g_bhop_autostrafe, nullptr, nullptr}; | ||
| 88 | static MenuNode g_abh_feat{NODE_FEATURE, "ABH", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 89 | static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; | ||
| 90 | static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 91 | static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr}; | ||
| 92 | static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; | ||
| 93 | static MenuNode g_melee_feat{NODE_FEATURE, "Melee killbot", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 94 | static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr}; | ||
| 95 | static MenuNode g_spread_feat{NODE_FEATURE, "Spread control", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 96 | static MenuNode g_spread_active{NODE_TOGGLE, "Active", false, 0, {}, &g_spread.active, nullptr, nullptr}; | ||
| 97 | static MenuNode g_spread_no_spread_node{NODE_TOGGLE, "No spread", false, 0, {}, &g_spread_no_spread, nullptr, nullptr}; | ||
| 98 | static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 99 | static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; | ||
| 100 | static 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 | }; | ||
| 109 | static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 110 | static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; | ||
| 111 | static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr}; | ||
| 112 | static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr}; | ||
| 113 | static MenuNode g_aimbot_vischeck_node{NODE_TOGGLE, "Vischeck", false, 0, {}, &g_aim_vischeck, nullptr, nullptr}; | ||
| 114 | static 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 | }; | ||
| 122 | static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, | ||
| 123 | nullptr, nullptr, &g_aim_smooth, 1, 45, 1}; | ||
| 124 | static MenuNode g_aimbot_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr, | ||
| 125 | nullptr, nullptr, &g_aim_fov, 1, 180, 1}; | ||
| 126 | static MenuNode g_aimbot_fov_circle_node{NODE_TOGGLE, "Show FOV circle", false, 0, {}, &g_aim_fov_circle, nullptr, nullptr}; | ||
| 127 | static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 128 | static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr}; | ||
| 129 | static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr}; | ||
| 130 | static MenuNode g_silentaim_feat{NODE_FEATURE, "Silent aim", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 131 | static MenuNode g_silentaim_active{NODE_TOGGLE, "Active", false, 0, {}, &g_silentaim.active, nullptr, nullptr}; | ||
| 132 | static MenuNode g_silentaim_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_silentaim.bind, nullptr}; | ||
| 133 | static MenuNode g_silentaim_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr, | ||
| 134 | nullptr, nullptr, &g_silent_fov, 1, 180, 1}; | ||
| 135 | |||
| 136 | static MenuNode *g_visible[48]; // flattened visible nodes for arrow navigation | ||
| 137 | static int g_visible_count = 0; | ||
| 138 | static int g_selected = 0; | ||
| 139 | bool g_menu_open = false; | ||
| 140 | static bool g_menu_draw_logged = false; | ||
| 141 | |||
| 142 | static bool g_bind_mode = false; | ||
| 143 | static bool g_insert_latch = false; | ||
| 144 | static 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 | |||
| 192 | static int g_depth[48]; // depth of each visible row (for indentation) | ||
| 193 | |||
| 194 | static 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 | |||
| 210 | static 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) | ||
| 491 | static constexpr int SCAN_INSERT = 73; // reserved for the GUI | ||
| 492 | static constexpr int SCAN_TEXT_MENU = 58; // F1 preview key for the old tree | ||
| 493 | static constexpr int SCAN_UP = 82; // these are SDL scancodes | ||
| 494 | static constexpr int SCAN_DOWN = 81; | ||
| 495 | static constexpr int SCAN_LEFT = 80; | ||
| 496 | static constexpr int SCAN_RIGHT = 79; | ||
| 497 | static constexpr int SCAN_ENTER = 40; | ||
| 498 | static constexpr int SCAN_ESCAPE = 41; | ||
| 499 | |||
| 500 | // simplified scancode-to-name for common keys | 327 | // simplified scancode-to-name for common keys |
| 501 | const char *scancode_name(int sc) { | 328 | const 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 |
| 535 | static bool g_prev_key_state[608] = {}; | 362 | static bool g_prev_key_state[608] = {}; |
| 536 | static 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 |
| 539 | using SDL_GetKeyboardState_fn = const uint8_t *(*)(int *); | 365 | using 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 | ||
| 580 | static bool key_pressed(int sc) { | 403 | static 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 | ||
| 651 | bool aimbot_fov_circle() { return g_aim_fov_circle; } | 470 | bool aimbot_fov_circle() { return g_aim_fov_circle; } |
| 652 | 471 | ||
| 653 | void 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 | |||
| 660 | static 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. |
| 871 | void outer_draw() { | 580 | void 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 | ||
| 1031 | void install_menu() { | 697 | void 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(); |
