From 5ddc9eb9d02ddc84e4c3a45bdcb2941513fb0346 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 18:58:13 +0100 Subject: Chams: per-relationship, visible/invisible styles and viewmodel - Hostile/neutral/friendly each have independent visible and through-wall settings: enable, style and colour. Viewmodel has its own enable/style/colour. - Styles: Flat, Wireframe, Additive, Solid, White, Fullbright, Glow outline. Additive uses the model solid material + ADDITIVE flag (the VGUI additive was invisible on NPCs). White/fullbright are solid-based, so the viewmodel no longer disappears/checkerboards. Glow outline adds a wireframe contour pass for depth. - Config: [chams] active/bind, [chams_hostile|neutral|friendly] vis/inv settings, [chams_vm]. GUI sections for each group. --- src/esp.cpp | 20 +++++++++++ src/gui.cpp | 39 +++++++++++++++------ src/hl2h.h | 19 +++++++--- src/menu.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++++------------- src/studio.cpp | 94 +++++++++++++++++++++++++++++-------------------- 5 files changed, 204 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/esp.cpp b/src/esp.cpp index 1631df1..d32a8bc 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -187,6 +187,26 @@ bool esp_is_viewmodel_index(int idx) { return model && (strstr(model, "/v_") || strstr(model, "\\v_")); } +// 0 = hostile, 1 = neutral, 2 = friendly, -1 = not an NPC. +static bool npc_is_friendly(int idx, const char *model, int local_idx, + bool &neutral); +int esp_npc_relation(int idx) { + if (!esp_is_npc_index(idx)) + return -1; + int local = engine_get_local_player(); + if (local <= 0) + return 0; + void *ent = entitylist_get(idx); + const char *model = ent ? entity_model_name(ent) : nullptr; + bool neutral = false; + bool friendly = npc_is_friendly(idx, model, local, neutral); + if (neutral) + return 1; + if (friendly) + return 2; + return 0; +} + static bool model_is_friendly(const char *m) { static const char *patterns[] = { "citizen", "humans/", "male_", "female_", "barney", "alyx", diff --git a/src/gui.cpp b/src/gui.cpp index 9c09bec..3cbb40a 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -446,18 +446,37 @@ static void build_cards() { "Draw the projectile's damage radius on the ground.", &g_proj_radius); int chams = add_card(2, &g_chams, "Chams", - "Flat-colour NPC models.", true); - add_toggle(chams, "Through walls", - "Draw a second pass with the depth test disabled.", - &g_chams_walls); + "Per-relationship model colours.", true); + static const char *const chams_mat_names[] = { + "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright", + "Glow outline"}; + auto add_chams_group = [&](const char *title, ChamsGroup &g) { + add_section(chams, title); + add_toggle(chams, "Visible", "Draw the normal-depth pass.", + &g.vis_on); + add_dropdown(chams, "Visible material", "Material for the visible pass.", + chams_mat_names, 7, &g.vis_mat); + add_color(chams, "Visible colour", "Colour for the visible pass.", + g.vis_rgb); + add_toggle(chams, "Through walls", "Draw a pass with the depth test off.", + &g.inv_on); + add_dropdown(chams, "Invisible material", + "Material for the through-wall pass.", chams_mat_names, 7, + &g.inv_mat); + add_color(chams, "Invisible colour", + "Colour for the through-wall pass.", g.inv_rgb); + }; + add_chams_group("Hostile", g_chams_hostile); + add_chams_group("Neutral", g_chams_neutral); + add_chams_group("Friendly", g_chams_friendly); + add_section(chams, "Viewmodel"); add_toggle(chams, "Viewmodel (hands/weapon)", - "Apply chams to the first-person hands and weapon too.", + "Apply chams to the first-person hands and weapon.", &g_chams_viewmodel); - static const char *const chams_mat_names[] = { - "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright"}; - add_dropdown(chams, "Material", "Chams material.", chams_mat_names, 6, - &g_chams_mat_index); - add_color(chams, "Colour", "Chams colour.", g_chams_rgb); + add_dropdown(chams, "Viewmodel material", "Material for the viewmodel.", + chams_mat_names, 7, &g_chams_vm_mat); + add_color(chams, "Viewmodel colour", "Colour for the viewmodel.", + g_chams_vm_rgb); int view = add_card(2, nullptr, "Camera / display", "Camera FOV, viewmodel FOV and HDR brightness.", true); add_slider(view, "Camera FOV", diff --git a/src/hl2h.h b/src/hl2h.h index 0f7e1f2..4c9012f 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -230,11 +230,21 @@ extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, g_world_tint, g_fog, g_skybox, g_projectiles, g_chams; extern bool g_proj_fuse, g_proj_radius; -extern bool g_chams_walls; + +// Chams settings per relationship, and separate visible / through-wall +// (invisible) material and colour. The viewmodel has a single style. +struct ChamsGroup { + bool vis_on; + int vis_mat; + int vis_rgb[3]; + bool inv_on; + int inv_mat; + int inv_rgb[3]; +}; +extern ChamsGroup g_chams_hostile, g_chams_neutral, g_chams_friendly; extern bool g_chams_viewmodel; -extern int g_chams_mat_index; -extern int g_chams_rgb[3]; -extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; +extern int g_chams_vm_mat; +extern int g_chams_vm_rgb[3];extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; extern bool g_fog_skybox, g_fog_disable; extern int g_fog_rgb[3], g_fog_start, g_fog_end; @@ -337,6 +347,7 @@ void silentaim_reset_lock(); void *entitylist_get(int idx); bool esp_is_npc_index(int idx); bool esp_is_viewmodel_index(int idx); +int esp_npc_relation(int idx); void *world_find_material(const char *name); void chams_init(); void chams_preload(); diff --git a/src/menu.cpp b/src/menu.cpp index 6a3c607..bdb9531 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -63,11 +63,13 @@ FeatureState g_skybox{false, 0}; // skybox.png image override FeatureState g_projectiles{true, 0}; // grenade / rocket ESP bool g_proj_fuse = true; bool g_proj_radius = true; -FeatureState g_chams{false, 0}; // flat model material, optional through walls -bool g_chams_walls = true; +FeatureState g_chams{false, 0}; +ChamsGroup g_chams_hostile{true, 3, {255, 60, 60}, true, 3, {255, 60, 60}}; +ChamsGroup g_chams_neutral{true, 3, {220, 220, 220}, false, 3, {220, 220, 220}}; +ChamsGroup g_chams_friendly{true, 3, {60, 255, 60}, false, 3, {60, 255, 60}}; bool g_chams_viewmodel = false; -int g_chams_mat_index = 0; -int g_chams_rgb[3] = {255, 60, 60}; +int g_chams_vm_mat = 3; +int g_chams_vm_rgb[3] = {200, 200, 255}; bool g_item_health = true; bool g_item_ammo = true; bool g_item_armor = true; @@ -239,11 +241,20 @@ void config_save() { fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, (int)g_proj_radius); - fprintf(f, "[chams]\nactive=%d\nbind=%d\nwalls=%d\nviewmodel=%d\n" - "material=%d\nr=%d\ng=%d\nb=%d\n", - g_chams.active, g_chams.bind, (int)g_chams_walls, - (int)g_chams_viewmodel, g_chams_mat_index, g_chams_rgb[0], - g_chams_rgb[1], g_chams_rgb[2]); + fprintf(f, "[chams]\nactive=%d\nbind=%d\n", g_chams.active, g_chams.bind); + auto save_chams = [&](const char *name, const ChamsGroup &g) { + fprintf(f, "[%s]\nvis=%d\nvis_mat=%d\nvis_r=%d\nvis_g=%d\nvis_b=%d\n" + "inv=%d\ninv_mat=%d\ninv_r=%d\ninv_g=%d\ninv_b=%d\n", + name, (int)g.vis_on, g.vis_mat, g.vis_rgb[0], g.vis_rgb[1], + g.vis_rgb[2], (int)g.inv_on, g.inv_mat, g.inv_rgb[0], + g.inv_rgb[1], g.inv_rgb[2]); + }; + save_chams("chams_hostile", g_chams_hostile); + save_chams("chams_neutral", g_chams_neutral); + save_chams("chams_friendly", g_chams_friendly); + fprintf(f, "[chams_vm]\non=%d\nmat=%d\nr=%d\ng=%d\nb=%d\n", + (int)g_chams_viewmodel, g_chams_vm_mat, g_chams_vm_rgb[0], + g_chams_vm_rgb[1], g_chams_vm_rgb[2]); fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n" "usable=%d\n", @@ -294,10 +305,14 @@ static bool config_load() { bool in_esp = false, in_bhop = false, in_aim = false, in_jumpbug = false, in_spread = false, in_items = false, in_tint = false, in_fog = false, in_view = false, in_movement = false, - in_proj = false, in_chams = false; + in_proj = false; + ChamsGroup *chams_grp = nullptr; + bool in_chams_vm = false; while (fgets(line, sizeof(line), f)) { line[strcspn(line, "\r\n")] = 0; if (line[0] == '[') { + chams_grp = nullptr; + in_chams_vm = false; if (strstr(line, "[bhop]")) cur = &g_bhop; else if (strstr(line, "[abh]")) @@ -324,6 +339,19 @@ static bool config_load() { cur = &g_projectiles; else if (strstr(line, "[chams]")) cur = &g_chams; + else if (strstr(line, "[chams_hostile]")) { + cur = nullptr; + chams_grp = &g_chams_hostile; + } else if (strstr(line, "[chams_neutral]")) { + cur = nullptr; + chams_grp = &g_chams_neutral; + } else if (strstr(line, "[chams_friendly]")) { + cur = nullptr; + chams_grp = &g_chams_friendly; + } else if (strstr(line, "[chams_vm]")) { + cur = nullptr; + in_chams_vm = true; + } else if (strstr(line, "[aimbot]")) cur = &g_aimbot; else if (strstr(line, "[triggerbot]")) @@ -347,8 +375,8 @@ static bool config_load() { in_view = strstr(line, "[view]") != nullptr; in_movement = strstr(line, "[movement]") != nullptr; in_proj = (cur == &g_projectiles); - in_chams = (cur == &g_chams); - } else if (cur || in_view || in_movement) { + } else if (cur || in_view || in_movement || chams_grp || + in_chams_vm) { char *eq = strchr(line, '='); if (!eq) continue; @@ -394,22 +422,50 @@ static bool config_load() { else if (strcmp(key, "radius") == 0) g_proj_radius = atoi(val) != 0; } - if (in_chams) { - if (strcmp(key, "walls") == 0) - g_chams_walls = atoi(val) != 0; - else if (strcmp(key, "viewmodel") == 0) + if (chams_grp) { + ChamsGroup &g = *chams_grp; + if (strcmp(key, "vis") == 0) + g.vis_on = atoi(val) != 0; + else if (strcmp(key, "vis_mat") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 6) v = 6; + g.vis_mat = v; + } else if (strcmp(key, "vis_r") == 0 || strcmp(key, "vis_g") == 0 || + strcmp(key, "vis_b") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 255) v = 255; + g.vis_rgb[key[4] == 'r' ? 0 : key[4] == 'g' ? 1 : 2] = v; + } else if (strcmp(key, "inv") == 0) + g.inv_on = atoi(val) != 0; + else if (strcmp(key, "inv_mat") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 6) v = 6; + g.inv_mat = v; + } else if (strcmp(key, "inv_r") == 0 || strcmp(key, "inv_g") == 0 || + strcmp(key, "inv_b") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 255) v = 255; + g.inv_rgb[key[4] == 'r' ? 0 : key[4] == 'g' ? 1 : 2] = v; + } + } + if (in_chams_vm) { + if (strcmp(key, "on") == 0) g_chams_viewmodel = atoi(val) != 0; - else if (strcmp(key, "material") == 0) { + else if (strcmp(key, "mat") == 0) { int v = atoi(val); if (v < 0) v = 0; - if (v > 5) v = 5; - g_chams_mat_index = v; + if (v > 6) v = 6; + g_chams_vm_mat = v; } else if (strcmp(key, "r") == 0 || strcmp(key, "g") == 0 || strcmp(key, "b") == 0) { int v = atoi(val); if (v < 0) v = 0; if (v > 255) v = 255; - g_chams_rgb[key[0] == 'r' ? 0 : key[0] == 'g' ? 1 : 2] = v; + g_chams_vm_rgb[key[0] == 'r' ? 0 : key[0] == 'g' ? 1 : 2] = v; } } if (in_aim) { @@ -637,12 +693,14 @@ void config_reset_defaults() { g_proj_fuse = true; g_proj_radius = true; g_chams = {false, 0}; - g_chams_walls = true; + g_chams_hostile = {true, 3, {255, 60, 60}, true, 3, {255, 60, 60}}; + g_chams_neutral = {true, 3, {220, 220, 220}, false, 3, {220, 220, 220}}; + g_chams_friendly = {true, 3, {60, 255, 60}, false, 3, {60, 255, 60}}; g_chams_viewmodel = false; - g_chams_mat_index = 0; - g_chams_rgb[0] = 255; - g_chams_rgb[1] = 60; - g_chams_rgb[2] = 60; + g_chams_vm_mat = 3; + g_chams_vm_rgb[0] = 200; + g_chams_vm_rgb[1] = 200; + g_chams_vm_rgb[2] = 255; g_item_health = true; g_item_ammo = true; g_item_armor = true; diff --git a/src/studio.cpp b/src/studio.cpp index b428a26..803b6b2 100644 --- a/src/studio.cpp +++ b/src/studio.cpp @@ -197,25 +197,25 @@ using ForceOverrideFn = void (*)(void *, void *, int); void *g_modelrender = nullptr; ModelExecFn g_orig_model_exec = nullptr; ForceOverrideFn g_force_override = nullptr; -void *g_style_mats[6] = {}; +void *g_style_mats[7] = {}; void *viewmodel_material(); const char *chams_material_name(int idx) { switch (idx) { case 1: return "debug/debugwireframe"; - case 2: return "vgui/white_additive"; - case 3: return "debug/debugtranslucentsinglecolor"; - case 4: return "debug/debugtranslucentvertexcolor"; - case 5: return "debug/debugmrmfullbright2"; + case 2: + case 3: + case 4: + case 5: + case 6: return "debug/debugtranslucentsinglecolor"; default: return "vgui/white"; // models/debug/debugwhite is EP2-only } } -void *chams_material() { - int idx = g_chams_mat_index; +void *style_material(int idx) { if (idx < 0) idx = 0; - if (idx > 5) idx = 5; + if (idx > 6) idx = 6; if (g_style_mats[idx]) return g_style_mats[idx]; if (g_style_mats[3]) @@ -223,8 +223,7 @@ void *chams_material() { return viewmodel_material(); } -// Viewmodels need a model shader; the VGUI white materials used by some -// styles do not draw skinned models and make the hands/weapon disappear. +// Viewmodels need a model shader; vgui materials do not draw skinned models. void *viewmodel_material() { if (g_style_mats[3]) return g_style_mats[3]; @@ -249,13 +248,15 @@ void chams_color(void *mat, float r, float g, float b) { vfunc(mat, 28)(mat, r, g, b); } -void chams_apply(void *mat, bool ignorez, int style, bool force_white) { +void chams_apply(void *mat, bool ignorez, int style, bool force_white, + const int *rgb) { using FlagFn = void (*)(void *, int, bool); - float r = force_white ? 1.0f : (float)g_chams_rgb[0] / 255.0f; - float g = force_white ? 1.0f : (float)g_chams_rgb[1] / 255.0f; - float b = force_white ? 1.0f : (float)g_chams_rgb[2] / 255.0f; + float r = force_white ? 1.0f : (float)rgb[0] / 255.0f; + float g = force_white ? 1.0f : (float)rgb[1] / 255.0f; + float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; chams_color(mat, r, g, b); - // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright. + // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright, + // 6 Glow outline (solid base; the wireframe overlay is a second draw). vfunc(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez); vfunc(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1); vfunc(mat, 29)(mat, 1 << 7 /* ADDITIVE */, style == 2); @@ -282,31 +283,50 @@ void hkdModelExecute(void *self, const void *state, const void *info, // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. int idx = *(const int *)((const char *)info + 0x30); if (idx > 0 && idx <= 8192) { - bool is_npc = esp_is_npc_index(idx); bool is_vm = g_chams_viewmodel && esp_is_viewmodel_index(idx); - if (is_npc || is_vm) { - int style = g_chams_mat_index; - if (style < 0) style = 0; - if (style > 5) style = 5; - // VGUI materials do not draw skinned models: use the solid model - // shader for the viewmodel with the same flags (flat/additive). - bool vgui_style = (style == 0 || style == 2); - void *mat = (is_vm && vgui_style) ? viewmodel_material() - : chams_material(); - if (!mat) - mat = viewmodel_material(); - if (mat) { + ChamsGroup *grp = nullptr; + if (!is_vm) { + int rel = esp_npc_relation(idx); + if (rel >= 0) + grp = rel == 0 ? &g_chams_hostile + : rel == 1 ? &g_chams_neutral : &g_chams_friendly; + } + if (is_vm || grp) { + auto draw_pass = [&](int style, const int *rgb, + bool ignorez) -> bool { + if (style < 0) style = 0; + if (style > 6) style = 6; + void *mat = style_material(style); + if (!mat) + return false; bool white = style == 4; - if (g_chams_walls && !is_vm) { - chams_apply(mat, true, style, white); - g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); - g_orig_model_exec(self, state, info, bone); - } - chams_apply(mat, false, style, white); - g_force_override(self, mat, 0); + chams_apply(mat, ignorez, style, white, rgb); + g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); g_orig_model_exec(self, state, info, bone); - g_force_override(self, nullptr, 0); + if (style == 6) { + // Solid base plus a wireframe contour pass for depth. + void *wire = g_style_mats[1]; + if (wire && wire != mat) { + chams_apply(wire, ignorez, 1, false, rgb); + g_force_override(self, wire, 0); + g_orig_model_exec(self, state, info, bone); + chams_reset(wire); + } + } chams_reset(mat); + return true; + }; + bool did = false; + if (is_vm) { + did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false); + } else { + if (grp->inv_on) + did |= draw_pass(grp->inv_mat, grp->inv_rgb, true); + if (grp->vis_on) + did |= draw_pass(grp->vis_mat, grp->vis_rgb, false); + } + if (did) { + g_force_override(self, nullptr, 0); return; } } @@ -337,7 +357,7 @@ void chams_preload() { void *probe = world_find_material("vgui/white"); logf("chams: probe vgui/white = %p", probe); } - for (int i = 0; i < 6; i++) { + for (int i = 0; i < 7; i++) { if (g_style_mats[i]) continue; void *m = world_find_material(chams_material_name(i)); -- cgit v1.2.3