summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 18:15:18 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 18:15:18 +0100
commit83b36aaaf146325af2d93dfbaf21eccba91a5d74 (patch)
tree2f6cd5a1d38231df4a6650e223681863f651e1df /src
parentChams: material styles and viewmodel support (diff)
downloadhl2h-83b36aaaf146325af2d93dfbaf21eccba91a5d74.tar.gz
hl2h-83b36aaaf146325af2d93dfbaf21eccba91a5d74.zip
Chams: exact material styles, GUI dropdown, viewmodel hookup
- world_find_material now tries the exact requested style first (resident, then FindMaterial) before falling back to debugwhite/vgui white. The old candidate scan returned vgui/white for every style, so all materials looked flat. - Add a real CK_DROPDOWN control (overlay list, outside-click close, config save) and use it for the Chams material selector. - Chams viewmodel toggle included in config/UI.
Diffstat (limited to '')
-rw-r--r--src/gui.cpp136
-rw-r--r--src/world.cpp73
2 files changed, 171 insertions, 38 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 4a949f0..4adc440 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -183,7 +183,8 @@ enum CtrlKind {
183 CK_SECTION, 183 CK_SECTION,
184 CK_INFO, 184 CK_INFO,
185 CK_BUTTON, 185 CK_BUTTON,
186 CK_COLOR 186 CK_COLOR,
187 CK_DROPDOWN
187}; 188};
188 189
189enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET, ACT_SKY_RELOAD }; 190enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET, ACT_SKY_RELOAD };
@@ -195,6 +196,8 @@ struct Ctrl {
195 bool *bval; // CK_TOGGLE 196 bool *bval; // CK_TOGGLE
196 int *ival; // CK_SLIDER 197 int *ival; // CK_SLIDER
197 int *rgb; // CK_COLOR: three ints, 0..255 198 int *rgb; // CK_COLOR: three ints, 0..255
199 const char *const *opts; // CK_DROPDOWN
200 int nopts; // CK_DROPDOWN
198 int min, max; 201 int min, max;
199 int action; // CK_BUTTON 202 int action; // CK_BUTTON
200 bool zero_unlimited; // slider: value 0 displays as "All" 203 bool zero_unlimited; // slider: value 0 displays as "All"
@@ -253,6 +256,8 @@ static void add_ctrl(int card, int kind, const char *label, const char *desc,
253 c.action = action; 256 c.action = action;
254 c.zero_unlimited = false; 257 c.zero_unlimited = false;
255 c.rgb = nullptr; 258 c.rgb = nullptr;
259 c.opts = nullptr;
260 c.nopts = 0;
256 c.hue = 0.0f; 261 c.hue = 0.0f;
257 c.sat = 0.0f; 262 c.sat = 0.0f;
258 c.val = 1.0f; 263 c.val = 1.0f;
@@ -282,10 +287,20 @@ static void add_color(int card, const char *label, const char *desc,
282 add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE); 287 add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE);
283 g_ctrls[i].rgb = rgb; 288 g_ctrls[i].rgb = rgb;
284} 289}
290static void add_dropdown(int card, const char *label, const char *desc,
291 const char *const *opts, int nopts, int *value) {
292 int i = g_nctrl;
293 add_ctrl(card, CK_DROPDOWN, label, desc, 0, nopts > 0 ? nopts - 1 : 0,
294 nullptr, value, ACT_NONE);
295 g_ctrls[i].opts = opts;
296 g_ctrls[i].nopts = nopts;
297}
285 298
286static int ctrl_val(const Ctrl &c) { 299static int ctrl_val(const Ctrl &c) {
287 if (c.kind == CK_TOGGLE && c.bval) 300 if (c.kind == CK_TOGGLE && c.bval)
288 return *c.bval ? 1 : 0; 301 return *c.bval ? 1 : 0;
302 if (c.kind == CK_DROPDOWN)
303 return c.ival ? *c.ival : 0;
289 if (c.kind == CK_SLIDER && c.ival) 304 if (c.kind == CK_SLIDER && c.ival)
290 return *c.ival; 305 return *c.ival;
291 return 0; 306 return 0;
@@ -294,6 +309,13 @@ static void ctrl_set(Ctrl &c, int v) {
294 if (c.kind == CK_TOGGLE && c.bval) { 309 if (c.kind == CK_TOGGLE && c.bval) {
295 *c.bval = v != 0; 310 *c.bval = v != 0;
296 config_save(); 311 config_save();
312 } else if (c.kind == CK_DROPDOWN && c.ival) {
313 if (v < c.min) v = c.min;
314 if (v > c.max) v = c.max;
315 if (*c.ival != v) {
316 *c.ival = v;
317 config_save();
318 }
297 } else if (c.kind == CK_SLIDER && c.ival) { 319 } else if (c.kind == CK_SLIDER && c.ival) {
298 if (v < c.min) v = c.min; 320 if (v < c.min) v = c.min;
299 if (v > c.max) v = c.max; 321 if (v > c.max) v = c.max;
@@ -431,11 +453,11 @@ static void build_cards() {
431 add_toggle(chams, "Viewmodel (hands/weapon)", 453 add_toggle(chams, "Viewmodel (hands/weapon)",
432 "Apply chams to the first-person hands and weapon too.", 454 "Apply chams to the first-person hands and weapon too.",
433 &g_chams_viewmodel); 455 &g_chams_viewmodel);
434 add_slider(chams, "Material", 456 static const char *const chams_mat_names[] = {
435 "0 Flat, 1 Wireframe, 2 Additive glow, 3 Solid, 4 White.", 0, 4, 457 "Flat", "Wireframe", "Additive glow", "Solid", "White"};
436 &g_chams_mat_index); 458 add_dropdown(chams, "Material", "Chams material.", chams_mat_names, 5,
459 &g_chams_mat_index);
437 add_color(chams, "Colour", "Chams colour.", g_chams_rgb); 460 add_color(chams, "Colour", "Chams colour.", g_chams_rgb);
438
439 int view = add_card(2, nullptr, "Camera / display", 461 int view = add_card(2, nullptr, "Camera / display",
440 "Camera FOV, viewmodel FOV and HDR brightness.", true); 462 "Camera FOV, viewmodel FOV and HDR brightness.", true);
441 add_slider(view, "Camera FOV", 463 add_slider(view, "Camera FOV",
@@ -912,7 +934,7 @@ static int header_h(const Card &c) {
912} 934}
913 935
914static int label_max_w(const Ctrl &c) { 936static int label_max_w(const Ctrl &c) {
915 if (c.kind == CK_SLIDER) 937 if (c.kind == CK_SLIDER || c.kind == CK_DROPDOWN)
916 return S(214.0f); 938 return S(214.0f);
917 return card_content_w() - S(16.0f) - S(110.0f); 939 return card_content_w() - S(16.0f) - S(110.0f);
918} 940}
@@ -1081,6 +1103,52 @@ static bool control_rect(int ctrl_index, Rect &out_card, Rect &out_row) {
1081 return false; 1103 return false;
1082} 1104}
1083 1105
1106// Dropdown popup state (one open at a time). The value is the Ctrl index.
1107static int g_dropdown_ctrl = -1;
1108
1109static bool dropdown_rects(int ctrl_index, Rect &btn, Rect &pop,
1110 int &item_h) {
1111 Rect card, row;
1112 if (!control_rect(ctrl_index, card, row))
1113 return false;
1114 Ctrl &ct = g_ctrls[ctrl_index];
1115 if (ct.kind != CK_DROPDOWN || !ct.opts || ct.nopts <= 0)
1116 return false;
1117 int bw = S(150.0f);
1118 btn = Rect{card.x + card.w - S(16.0f) - bw,
1119 row.y + (row.h - S(26.0f)) / 2, bw, S(26.0f)};
1120 item_h = S(22.0f);
1121 pop = Rect{btn.x, btn.y + btn.h, btn.w, item_h * ct.nopts};
1122 if (pop.y + pop.h > g_win_y + g_win_h - S(34.0f))
1123 pop.y = btn.y - pop.h;
1124 return true;
1125}
1126
1127static void draw_dropdown() {
1128 if (g_dropdown_ctrl < 0)
1129 return;
1130 Rect btn, pop;
1131 int ih = 0;
1132 if (!dropdown_rects(g_dropdown_ctrl, btn, pop, ih)) {
1133 g_dropdown_ctrl = -1;
1134 return;
1135 }
1136 Ctrl &ct = g_ctrls[g_dropdown_ctrl];
1137 fill(pop, Col{25, 28, 34, 255});
1138 outline(pop, C_BORDER);
1139 int cur = ct.ival ? *ct.ival : 0;
1140 for (int i = 0; i < ct.nopts; i++) {
1141 Rect it{pop.x, pop.y + i * ih, pop.w, ih};
1142 if (it.has(g_mouse_x, g_mouse_y))
1143 fill(it, Col{45, 50, 60, 255});
1144 text_in(pop, it.x + S(9), it.y + (ih - g_body_h) / 2, ct.opts[i],
1145 g_font_body, g_body_h, it.w - S(28),
1146 i == cur ? C_ACCENT : C_TEXT);
1147 if (i == cur)
1148 fill(Rect{it.x + it.w - S(14), it.y + ih / 2 - S(3), S(6), S(6)},
1149 C_ACCENT);
1150 }
1151}
1084static bool picker_rects_for(int ctrl_index, PickerRects &out) { 1152static bool picker_rects_for(int ctrl_index, PickerRects &out) {
1085 if (ctrl_index < 0 || ctrl_index >= g_nctrl) 1153 if (ctrl_index < 0 || ctrl_index >= g_nctrl)
1086 return false; 1154 return false;
@@ -1219,6 +1287,7 @@ static void close_gui() {
1219 g_slider_drag = -1; 1287 g_slider_drag = -1;
1220 g_picker_ctrl = -1; 1288 g_picker_ctrl = -1;
1221 g_picker_drag = 0; 1289 g_picker_drag = 0;
1290 g_dropdown_ctrl = -1;
1222 g_reset_armed = false; 1291 g_reset_armed = false;
1223 input_clear_mouse_states(); 1292 input_clear_mouse_states();
1224 if (g_sdl_ready) { 1293 if (g_sdl_ready) {
@@ -1341,6 +1410,29 @@ static void handle_mouse_click() {
1341 return; 1410 return;
1342 int mx = g_mouse_x, my = g_mouse_y; 1411 int mx = g_mouse_x, my = g_mouse_y;
1343 1412
1413 // Open dropdown: consume the click on its popup first.
1414 if (g_dropdown_ctrl >= 0) {
1415 Rect btn, pop;
1416 int ih = 0;
1417 if (dropdown_rects(g_dropdown_ctrl, btn, pop, ih)) {
1418 if (pop.has(mx, my)) {
1419 Ctrl &ct = g_ctrls[g_dropdown_ctrl];
1420 int i = (my - pop.y) / ih;
1421 if (i >= 0 && i < ct.nopts && ct.ival) {
1422 *ct.ival = i;
1423 config_save();
1424 }
1425 g_dropdown_ctrl = -1;
1426 return;
1427 }
1428 if (btn.has(mx, my)) {
1429 g_dropdown_ctrl = -1;
1430 return;
1431 }
1432 }
1433 g_dropdown_ctrl = -1;
1434 }
1435
1344 // title bar: drag 1436 // title bar: drag
1345 Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; 1437 Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)};
1346 if (title.has(mx, my)) { 1438 if (title.has(mx, my)) {
@@ -1457,6 +1549,14 @@ static void handle_mouse_click() {
1457 } 1549 }
1458 return; 1550 return;
1459 } 1551 }
1552 if (ct.kind == CK_DROPDOWN) {
1553 int idx = c.c0 + j;
1554 Rect btn, pop;
1555 int ih = 0;
1556 if (dropdown_rects(idx, btn, pop, ih) && btn.has(mx, my))
1557 g_dropdown_ctrl = (g_dropdown_ctrl == idx) ? -1 : idx;
1558 return;
1559 }
1460 if (ct.kind == CK_COLOR) { 1560 if (ct.kind == CK_COLOR) {
1461 int idx = c.c0 + j; 1561 int idx = c.c0 + j;
1462 int base = label_row_h(ct); 1562 int base = label_row_h(ct);
@@ -1876,6 +1976,22 @@ static void draw_card(const Rect &clip, int index, int y) {
1876 on ? C_GOOD : C_DIM); 1976 on ? C_GOOD : C_DIM);
1877 } else if (ct.kind == CK_SLIDER) { 1977 } else if (ct.kind == CK_SLIDER) {
1878 draw_slider(clip, cr, by, rh, ct); 1978 draw_slider(clip, cr, by, rh, ct);
1979 } else if (ct.kind == CK_DROPDOWN) {
1980 int cur = ct.ival ? *ct.ival : 0;
1981 if (cur < 0) cur = 0;
1982 if (cur >= ct.nopts) cur = ct.nopts - 1;
1983 const char *opt =
1984 (ct.opts && cur >= 0 && cur < ct.nopts) ? ct.opts[cur] : "?";
1985 int bw = S(150.0f);
1986 Rect btn{cr.x + cr.w - S(16.0f) - bw,
1987 by + (base_h - S(26.0f)) / 2, bw, S(26.0f)};
1988 fill(btn, Col{45, 50, 60, 255}, &clip);
1989 outline(btn, g_dropdown_ctrl == c.c0 + j ? C_ACCENT : C_BORDER,
1990 &clip);
1991 text_in(clip, btn.x + S(9), btn.y + (btn.h - g_body_h) / 2, opt,
1992 g_font_body, g_body_h, btn.w - S(30), C_TEXT);
1993 text_in(clip, btn.x + btn.w - S(16), btn.y + (btn.h - g_body_h) / 2,
1994 "v", g_font_body, g_body_h, 0, C_DIM);
1879 } else if (ct.kind == CK_COLOR) { 1995 } else if (ct.kind == CK_COLOR) {
1880 int pr = ct.rgb ? ct.rgb[0] : 255; 1996 int pr = ct.rgb ? ct.rgb[0] : 255;
1881 int pg = ct.rgb ? ct.rgb[1] : 255; 1997 int pg = ct.rgb ? ct.rgb[1] : 255;
@@ -1893,6 +2009,13 @@ static void draw_card(const Rect &clip, int index, int y) {
1893} 2009}
1894 2010
1895static void draw_tooltip() { 2011static void draw_tooltip() {
2012 if (g_dropdown_ctrl >= 0) {
2013 Rect btn, pop;
2014 int ih = 0;
2015 if (dropdown_rects(g_dropdown_ctrl, btn, pop, ih) &&
2016 (pop.has(g_mouse_x, g_mouse_y) || btn.has(g_mouse_x, g_mouse_y)))
2017 return;
2018 }
1896 if (!g_hover_desc || !g_hover_desc[0]) 2019 if (!g_hover_desc || !g_hover_desc[0])
1897 return; 2020 return;
1898 float d = now_sec() - (float)g_hover_since; 2021 float d = now_sec() - (float)g_hover_since;
@@ -2102,6 +2225,7 @@ void gui_draw() {
2102 text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h, 0, 2225 text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h, 0,
2103 C_DIM); 2226 C_DIM);
2104 2227
2228 draw_dropdown();
2105 draw_tooltip(); 2229 draw_tooltip();
2106} 2230}
2107 2231
diff --git a/src/world.cpp b/src/world.cpp
index 897ad0a..a2e65cc 100644
--- a/src/world.cpp
+++ b/src/world.cpp
@@ -1015,20 +1015,19 @@ void *world_find_material(const char *name) {
1015 using GetFn = void *(*)(void *, unsigned short); 1015 using GetFn = void *(*)(void *, unsigned short);
1016 using CountFn = int (*)(void *); 1016 using CountFn = int (*)(void *);
1017 using NameFn = const char *(*)(void *); 1017 using NameFn = const char *(*)(void *);
1018 using FindFn = void *(*)(void *, const char *, const char *, bool,
1019 const char *);
1018 FirstFn first = (FirstFn)vfunc<FirstFn>(ms, 73); 1020 FirstFn first = (FirstFn)vfunc<FirstFn>(ms, 73);
1019 NextFn next = (NextFn)vfunc<NextFn>(ms, 74); 1021 NextFn next = (NextFn)vfunc<NextFn>(ms, 74);
1020 InvalidFn invalid = (InvalidFn)vfunc<InvalidFn>(ms, 75); 1022 InvalidFn invalid = (InvalidFn)vfunc<InvalidFn>(ms, 75);
1021 GetFn get = (GetFn)vfunc<GetFn>(ms, 76); 1023 GetFn get = (GetFn)vfunc<GetFn>(ms, 76);
1022 CountFn count = (CountFn)vfunc<CountFn>(ms, 77); 1024 CountFn count = (CountFn)vfunc<CountFn>(ms, 77);
1023 const char *candidates[] = {name, 1025 static const char *groups[] = {"Model textures", "Other textures", nullptr};
1024 "models/debug/debugwhite2", 1026
1025 "models/debug/debugwhite3", 1027 auto walk = [&](const char *want) -> void * {
1026 "vgui/white", 1028 int total = count ? count(ms) : 0;
1027 "debug/debugtranslucentsinglecolor", 1029 if (total <= 0 || total >= 200000)
1028 "debug/debugwireframevertexcolorignorez", 1030 return nullptr;
1029 nullptr};
1030 int total = count ? count(ms) : 0;
1031 if (total > 0 && total < 200000) {
1032 unsigned short h = first(ms); 1031 unsigned short h = first(ms);
1033 unsigned short inv = invalid(ms); 1032 unsigned short inv = invalid(ms);
1034 for (int i = 0; i < total && h != inv; i++) { 1033 for (int i = 0; i < total && h != inv; i++) {
@@ -1036,38 +1035,48 @@ void *world_find_material(const char *name) {
1036 if (mat) { 1035 if (mat) {
1037 NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0); 1036 NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0);
1038 const char *n = mat_name(mat); 1037 const char *n = mat_name(mat);
1039 if (n) { 1038 if (n && strcmp(n, want) == 0)
1040 for (int c = 0; candidates[c]; c++) 1039 return mat;
1041 if (strcmp(n, candidates[c]) == 0) {
1042 logf("chams: material %s = %p", n, mat);
1043 return mat;
1044 }
1045 }
1046 } 1040 }
1047 h = next(ms, h); 1041 h = next(ms, h);
1048 } 1042 }
1049 } 1043 return nullptr;
1050 // Not resident yet: let the material system load it. 1044 };
1051 using FindFn = void *(*)(void *, const char *, const char *, bool, 1045 auto find = [&](const char *want) -> void * {
1052 const char *);
1053 static const char *groups[] = {"Model textures", "Other textures", nullptr};
1054 for (int c = 0; candidates[c]; c++) {
1055 for (int g = 0; groups[g]; g++) { 1046 for (int g = 0; groups[g]; g++) {
1056 void *mat = vfunc<FindFn>(ms, 71)(ms, candidates[c], groups[g], false, 1047 void *mat = vfunc<FindFn>(ms, 71)(ms, want, groups[g], false, nullptr);
1057 nullptr);
1058 if (!mat) 1048 if (!mat)
1059 continue; 1049 continue;
1060 NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0); 1050 NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0);
1061 const char *n = mat_name(mat); 1051 const char *n = mat_name(mat);
1062 if (!n) 1052 if (n && strcmp(n, want) == 0)
1063 continue; 1053 return mat;
1064 for (int k = 0; candidates[k]; k++) { 1054 }
1065 if (strcmp(n, candidates[k]) == 0) { 1055 return nullptr;
1066 logf("chams: material %s = %p (FindMaterial)", n, mat); 1056 };
1067 return mat; 1057
1068 } 1058 // First the exact style requested (resident or loaded on demand), then only
1069 } 1059 // the basic white fallbacks. Returning vgui/white before trying the exact
1060 // name made every style look identical.
1061 void *mat = walk(name);
1062 if (!mat)
1063 mat = find(name);
1064 if (mat) {
1065 logf("chams: material %s = %p", name, mat);
1066 return mat;
1067 }
1068 static const char *fallbacks[] = {"models/debug/debugwhite", "vgui/white",
1069 nullptr};
1070 for (int i = 0; fallbacks[i]; i++) {
1071 mat = walk(fallbacks[i]);
1072 if (!mat)
1073 mat = find(fallbacks[i]);
1074 if (mat) {
1075 logf("chams: material %s (fallback for %s) = %p", fallbacks[i], name,
1076 mat);
1077 return mat;
1070 } 1078 }
1071 } 1079 }
1080 logf("chams: material %s not found", name);
1072 return nullptr; 1081 return nullptr;
1073} 1082}