From 83b36aaaf146325af2d93dfbaf21eccba91a5d74 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 18:15:18 +0100 Subject: 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. --- src/gui.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/world.cpp | 73 +++++++++++++++++-------------- 2 files changed, 171 insertions(+), 38 deletions(-) (limited to 'src') 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 { CK_SECTION, CK_INFO, CK_BUTTON, - CK_COLOR + CK_COLOR, + CK_DROPDOWN }; enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET, ACT_SKY_RELOAD }; @@ -195,6 +196,8 @@ struct Ctrl { bool *bval; // CK_TOGGLE int *ival; // CK_SLIDER int *rgb; // CK_COLOR: three ints, 0..255 + const char *const *opts; // CK_DROPDOWN + int nopts; // CK_DROPDOWN int min, max; int action; // CK_BUTTON 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, c.action = action; c.zero_unlimited = false; c.rgb = nullptr; + c.opts = nullptr; + c.nopts = 0; c.hue = 0.0f; c.sat = 0.0f; c.val = 1.0f; @@ -282,10 +287,20 @@ static void add_color(int card, const char *label, const char *desc, add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE); g_ctrls[i].rgb = rgb; } +static void add_dropdown(int card, const char *label, const char *desc, + const char *const *opts, int nopts, int *value) { + int i = g_nctrl; + add_ctrl(card, CK_DROPDOWN, label, desc, 0, nopts > 0 ? nopts - 1 : 0, + nullptr, value, ACT_NONE); + g_ctrls[i].opts = opts; + g_ctrls[i].nopts = nopts; +} static int ctrl_val(const Ctrl &c) { if (c.kind == CK_TOGGLE && c.bval) return *c.bval ? 1 : 0; + if (c.kind == CK_DROPDOWN) + return c.ival ? *c.ival : 0; if (c.kind == CK_SLIDER && c.ival) return *c.ival; return 0; @@ -294,6 +309,13 @@ static void ctrl_set(Ctrl &c, int v) { if (c.kind == CK_TOGGLE && c.bval) { *c.bval = v != 0; config_save(); + } else if (c.kind == CK_DROPDOWN && c.ival) { + if (v < c.min) v = c.min; + if (v > c.max) v = c.max; + if (*c.ival != v) { + *c.ival = v; + config_save(); + } } else if (c.kind == CK_SLIDER && c.ival) { if (v < c.min) v = c.min; if (v > c.max) v = c.max; @@ -431,11 +453,11 @@ static void build_cards() { add_toggle(chams, "Viewmodel (hands/weapon)", "Apply chams to the first-person hands and weapon too.", &g_chams_viewmodel); - add_slider(chams, "Material", - "0 Flat, 1 Wireframe, 2 Additive glow, 3 Solid, 4 White.", 0, 4, - &g_chams_mat_index); + static const char *const chams_mat_names[] = { + "Flat", "Wireframe", "Additive glow", "Solid", "White"}; + add_dropdown(chams, "Material", "Chams material.", chams_mat_names, 5, + &g_chams_mat_index); add_color(chams, "Colour", "Chams colour.", g_chams_rgb); - int view = add_card(2, nullptr, "Camera / display", "Camera FOV, viewmodel FOV and HDR brightness.", true); add_slider(view, "Camera FOV", @@ -912,7 +934,7 @@ static int header_h(const Card &c) { } static int label_max_w(const Ctrl &c) { - if (c.kind == CK_SLIDER) + if (c.kind == CK_SLIDER || c.kind == CK_DROPDOWN) return S(214.0f); return card_content_w() - S(16.0f) - S(110.0f); } @@ -1081,6 +1103,52 @@ static bool control_rect(int ctrl_index, Rect &out_card, Rect &out_row) { return false; } +// Dropdown popup state (one open at a time). The value is the Ctrl index. +static int g_dropdown_ctrl = -1; + +static bool dropdown_rects(int ctrl_index, Rect &btn, Rect &pop, + int &item_h) { + Rect card, row; + if (!control_rect(ctrl_index, card, row)) + return false; + Ctrl &ct = g_ctrls[ctrl_index]; + if (ct.kind != CK_DROPDOWN || !ct.opts || ct.nopts <= 0) + return false; + int bw = S(150.0f); + btn = Rect{card.x + card.w - S(16.0f) - bw, + row.y + (row.h - S(26.0f)) / 2, bw, S(26.0f)}; + item_h = S(22.0f); + pop = Rect{btn.x, btn.y + btn.h, btn.w, item_h * ct.nopts}; + if (pop.y + pop.h > g_win_y + g_win_h - S(34.0f)) + pop.y = btn.y - pop.h; + return true; +} + +static void draw_dropdown() { + if (g_dropdown_ctrl < 0) + return; + Rect btn, pop; + int ih = 0; + if (!dropdown_rects(g_dropdown_ctrl, btn, pop, ih)) { + g_dropdown_ctrl = -1; + return; + } + Ctrl &ct = g_ctrls[g_dropdown_ctrl]; + fill(pop, Col{25, 28, 34, 255}); + outline(pop, C_BORDER); + int cur = ct.ival ? *ct.ival : 0; + for (int i = 0; i < ct.nopts; i++) { + Rect it{pop.x, pop.y + i * ih, pop.w, ih}; + if (it.has(g_mouse_x, g_mouse_y)) + fill(it, Col{45, 50, 60, 255}); + text_in(pop, it.x + S(9), it.y + (ih - g_body_h) / 2, ct.opts[i], + g_font_body, g_body_h, it.w - S(28), + i == cur ? C_ACCENT : C_TEXT); + if (i == cur) + fill(Rect{it.x + it.w - S(14), it.y + ih / 2 - S(3), S(6), S(6)}, + C_ACCENT); + } +} static bool picker_rects_for(int ctrl_index, PickerRects &out) { if (ctrl_index < 0 || ctrl_index >= g_nctrl) return false; @@ -1219,6 +1287,7 @@ static void close_gui() { g_slider_drag = -1; g_picker_ctrl = -1; g_picker_drag = 0; + g_dropdown_ctrl = -1; g_reset_armed = false; input_clear_mouse_states(); if (g_sdl_ready) { @@ -1341,6 +1410,29 @@ static void handle_mouse_click() { return; int mx = g_mouse_x, my = g_mouse_y; + // Open dropdown: consume the click on its popup first. + if (g_dropdown_ctrl >= 0) { + Rect btn, pop; + int ih = 0; + if (dropdown_rects(g_dropdown_ctrl, btn, pop, ih)) { + if (pop.has(mx, my)) { + Ctrl &ct = g_ctrls[g_dropdown_ctrl]; + int i = (my - pop.y) / ih; + if (i >= 0 && i < ct.nopts && ct.ival) { + *ct.ival = i; + config_save(); + } + g_dropdown_ctrl = -1; + return; + } + if (btn.has(mx, my)) { + g_dropdown_ctrl = -1; + return; + } + } + g_dropdown_ctrl = -1; + } + // title bar: drag Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; if (title.has(mx, my)) { @@ -1457,6 +1549,14 @@ static void handle_mouse_click() { } return; } + if (ct.kind == CK_DROPDOWN) { + int idx = c.c0 + j; + Rect btn, pop; + int ih = 0; + if (dropdown_rects(idx, btn, pop, ih) && btn.has(mx, my)) + g_dropdown_ctrl = (g_dropdown_ctrl == idx) ? -1 : idx; + return; + } if (ct.kind == CK_COLOR) { int idx = c.c0 + j; int base = label_row_h(ct); @@ -1876,6 +1976,22 @@ static void draw_card(const Rect &clip, int index, int y) { on ? C_GOOD : C_DIM); } else if (ct.kind == CK_SLIDER) { draw_slider(clip, cr, by, rh, ct); + } else if (ct.kind == CK_DROPDOWN) { + int cur = ct.ival ? *ct.ival : 0; + if (cur < 0) cur = 0; + if (cur >= ct.nopts) cur = ct.nopts - 1; + const char *opt = + (ct.opts && cur >= 0 && cur < ct.nopts) ? ct.opts[cur] : "?"; + int bw = S(150.0f); + Rect btn{cr.x + cr.w - S(16.0f) - bw, + by + (base_h - S(26.0f)) / 2, bw, S(26.0f)}; + fill(btn, Col{45, 50, 60, 255}, &clip); + outline(btn, g_dropdown_ctrl == c.c0 + j ? C_ACCENT : C_BORDER, + &clip); + text_in(clip, btn.x + S(9), btn.y + (btn.h - g_body_h) / 2, opt, + g_font_body, g_body_h, btn.w - S(30), C_TEXT); + text_in(clip, btn.x + btn.w - S(16), btn.y + (btn.h - g_body_h) / 2, + "v", g_font_body, g_body_h, 0, C_DIM); } else if (ct.kind == CK_COLOR) { int pr = ct.rgb ? ct.rgb[0] : 255; int pg = ct.rgb ? ct.rgb[1] : 255; @@ -1893,6 +2009,13 @@ static void draw_card(const Rect &clip, int index, int y) { } static void draw_tooltip() { + if (g_dropdown_ctrl >= 0) { + Rect btn, pop; + int ih = 0; + if (dropdown_rects(g_dropdown_ctrl, btn, pop, ih) && + (pop.has(g_mouse_x, g_mouse_y) || btn.has(g_mouse_x, g_mouse_y))) + return; + } if (!g_hover_desc || !g_hover_desc[0]) return; float d = now_sec() - (float)g_hover_since; @@ -2102,6 +2225,7 @@ void gui_draw() { text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h, 0, C_DIM); + draw_dropdown(); draw_tooltip(); } 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) { using GetFn = void *(*)(void *, unsigned short); using CountFn = int (*)(void *); using NameFn = const char *(*)(void *); + using FindFn = void *(*)(void *, const char *, const char *, bool, + const char *); FirstFn first = (FirstFn)vfunc(ms, 73); NextFn next = (NextFn)vfunc(ms, 74); InvalidFn invalid = (InvalidFn)vfunc(ms, 75); GetFn get = (GetFn)vfunc(ms, 76); CountFn count = (CountFn)vfunc(ms, 77); - const char *candidates[] = {name, - "models/debug/debugwhite2", - "models/debug/debugwhite3", - "vgui/white", - "debug/debugtranslucentsinglecolor", - "debug/debugwireframevertexcolorignorez", - nullptr}; - int total = count ? count(ms) : 0; - if (total > 0 && total < 200000) { + static const char *groups[] = {"Model textures", "Other textures", nullptr}; + + auto walk = [&](const char *want) -> void * { + int total = count ? count(ms) : 0; + if (total <= 0 || total >= 200000) + return nullptr; unsigned short h = first(ms); unsigned short inv = invalid(ms); for (int i = 0; i < total && h != inv; i++) { @@ -1036,38 +1035,48 @@ void *world_find_material(const char *name) { if (mat) { NameFn mat_name = (NameFn)vfunc(mat, 0); const char *n = mat_name(mat); - if (n) { - for (int c = 0; candidates[c]; c++) - if (strcmp(n, candidates[c]) == 0) { - logf("chams: material %s = %p", n, mat); - return mat; - } - } + if (n && strcmp(n, want) == 0) + return mat; } h = next(ms, h); } - } - // Not resident yet: let the material system load it. - using FindFn = void *(*)(void *, const char *, const char *, bool, - const char *); - static const char *groups[] = {"Model textures", "Other textures", nullptr}; - for (int c = 0; candidates[c]; c++) { + return nullptr; + }; + auto find = [&](const char *want) -> void * { for (int g = 0; groups[g]; g++) { - void *mat = vfunc(ms, 71)(ms, candidates[c], groups[g], false, - nullptr); + void *mat = vfunc(ms, 71)(ms, want, groups[g], false, nullptr); if (!mat) continue; NameFn mat_name = (NameFn)vfunc(mat, 0); const char *n = mat_name(mat); - if (!n) - continue; - for (int k = 0; candidates[k]; k++) { - if (strcmp(n, candidates[k]) == 0) { - logf("chams: material %s = %p (FindMaterial)", n, mat); - return mat; - } - } + if (n && strcmp(n, want) == 0) + return mat; + } + return nullptr; + }; + + // First the exact style requested (resident or loaded on demand), then only + // the basic white fallbacks. Returning vgui/white before trying the exact + // name made every style look identical. + void *mat = walk(name); + if (!mat) + mat = find(name); + if (mat) { + logf("chams: material %s = %p", name, mat); + return mat; + } + static const char *fallbacks[] = {"models/debug/debugwhite", "vgui/white", + nullptr}; + for (int i = 0; fallbacks[i]; i++) { + mat = walk(fallbacks[i]); + if (!mat) + mat = find(fallbacks[i]); + if (mat) { + logf("chams: material %s (fallback for %s) = %p", fallbacks[i], name, + mat); + return mat; } } + logf("chams: material %s not found", name); return nullptr; } -- cgit v1.2.3