summaryrefslogtreecommitdiffstats
path: root/src/menu.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/menu.cpp359
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
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();