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 | |
| 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.
| -rw-r--r-- | CONTEXT.md | 53 | ||||
| -rw-r--r-- | docs/adr/0001-custom-isurface-gui.md | 6 | ||||
| -rw-r--r-- | src/gui.cpp | 25 | ||||
| -rw-r--r-- | src/hl2h.h | 3 | ||||
| -rw-r--r-- | src/menu.cpp | 359 |
5 files changed, 35 insertions, 411 deletions
| @@ -94,52 +94,11 @@ Relevant vtable slots (this build): | |||
| 94 | 94 | ||
| 95 | ## Menu | 95 | ## Menu |
| 96 | 96 | ||
| 97 | Preview key: `F1` (the final GUI will replace this tree). Starts fully collapsed on first open. `F1` toggles; `Up`/`Down` navigate; | 97 | There is a single menu: the custom GUI, toggled with `Insert`. The old |
| 98 | `Enter` expands/toggles; `Left`/`Right` edit values; `Escape` closes. | 98 | text-tree menu was removed after the GUI reached parity. Every feature still |
| 99 | Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets | 99 | has an active flag and a bind (SDL scancode; mouse buttons 600..604; None = |
| 100 | None (always-on), `Enter` cancels. | 100 | always-on), and all settings are persisted in the INI described under GUI |
| 101 | 101 | vocabulary / 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 | ``` | ||
| 108 | HL2hake | ||
| 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 | |||
| 142 | Binds 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 | } |
| @@ -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 | ||
| 191 | struct FeatureState { | 191 | struct FeatureState { |
| @@ -268,7 +268,6 @@ bool head_position(int idx, Vector &out); | |||
| 268 | int aim_points(int idx, unsigned group_mask, Vector *out, int max); | 268 | int aim_points(int idx, unsigned group_mask, Vector *out, int max); |
| 269 | 269 | ||
| 270 | void outer_draw(); | 270 | void outer_draw(); |
| 271 | void menu_force_close(); | ||
| 272 | void gui_handle_input(); | 271 | void gui_handle_input(); |
| 273 | void gui_draw(); | 272 | void gui_draw(); |
| 274 | bool gui_is_open(); | 273 | bool 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 | |||
| 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(); |
