summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 18:58:13 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 18:58:13 +0100
commit5ddc9eb9d02ddc84e4c3a45bdcb2941513fb0346 (patch)
treec83aa673488b0628e0f63cdc8b82e45c7693be9d
parentChams: apply model-safe styles to the viewmodel (diff)
downloadhl2h-5ddc9eb9d02ddc84e4c3a45bdcb2941513fb0346.tar.gz
hl2h-5ddc9eb9d02ddc84e4c3a45bdcb2941513fb0346.zip
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.
-rw-r--r--CONTEXT.md53
-rw-r--r--src/esp.cpp20
-rw-r--r--src/gui.cpp39
-rw-r--r--src/hl2h.h19
-rw-r--r--src/menu.cpp108
-rw-r--r--src/studio.cpp94
6 files changed, 228 insertions, 105 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index e323588..be526fc 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -285,35 +285,30 @@ vocabulary / decisions.
285## Chams 285## Chams
286 286
287- Hooks `IVModelRender::DrawModelExecute` (slot 19 in `VEngineModel016`). 287- Hooks `IVModelRender::DrawModelExecute` (slot 19 in `VEngineModel016`).
288 The model's entity is `ModelRenderInfo_t::entity_index` (32-bit offset 0x30); 288 The model's entity is `ModelRenderInfo_t::entity_index` (32-bit offset 0x30).
289 only entities for which `esp_is_npc_index` says the client class is an NPC 289 NPCs are grouped by `esp_npc_relation` (hostile / neutral / friendly);
290 are affected. 290 viewmodel entities (class contains `viewmodel` or model path `.../v_...`)
291- Uses `IVModelRender::ForcedMaterialOverride` (slot 1) with 291 are a fourth group.
292 `models/debug/debugwhite` and draws twice: first with 292- Each relationship has independent **visible** and **through-wall** settings:
293 `MATERIAL_VAR_IGNOREZ` (1 << 15) set through `IMaterial::SetMaterialVarFlag` 293 on/off, style, and colour. The viewmodel has on/off, style and colour only.
294 (slot 29) for the through-wall pass, then without it. Colour is applied via 294 The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15) through
295 `IMaterial::ColorModulate` (slot 28) **and** the shader's `$color`/`$color2` 295 `IMaterial::SetMaterialVarFlag` (slot 29); colours use `$color`/`$color2`
296 vars (IMaterialVar slot 11, SetVecValue 3-float slot 11), because some forced 296 (IMaterialVar slot 11, SetVecValue 3-float slot 11) and `ColorModulate`
297 materials ignore ColorModulate. The shared material's flag and colour are 297 (slot 28). Shared materials are restored after each draw.
298 restored to (false, white) after the draw. 298- Styles are built from the resident materials and material flags:
299- The base game does not mount `models/debug/debugwhite` (EP2-only) or 299 0 Flat (`vgui/white` with FLAT), 1 Wireframe (`debug/debugwireframe`),
300 `debug/additivevertexcolorvertexalpha`; the style material is preloaded from 300 2 Additive (`debug/debugtranslucentsinglecolor` + ADDITIVE), 3 Solid,
301 `outer_draw` (throttled) and cached. Styles: 0 Flat (`vgui/white`), 301 4 White (solid, colour forced white), 5 Fullbright (solid + FLAT|SELFILLUM),
302 1 Wireframe (`debug/debugwireframe`), 2 Additive (`vgui/white_additive`), 302 6 Glow outline (solid plus a `debug/debugwireframe` contour pass).
303 3 Solid (`debug/debugtranslucentsinglecolor`), 4 White 303 `models/debug/debugwhite` is EP2-only and the old VGUI additive material is
304 (`debug/debugtranslucentvertexcolor`), 5 Fullbright 304 invisible on NPCs; the styles no longer depend on them.
305 (`debug/debugmrmfullbright2`). A missing style falls back to the solid one; 305- Style materials are preloaded from `outer_draw` (throttled) and cached; the
306 the render hook never calls FindMaterial itself. 306 render hook never calls FindMaterial itself. A missing style uses solid.
307- `Viewmodel (hands/weapon)` applies the override to entities whose class 307- Feature is off by default. Config sections: `[chams]` (active/bind),
308 contains `viewmodel` or whose model path is `.../v_...`. Viewmodels always 308 `[chams_hostile|neutral|friendly]` (`vis`, `vis_mat`, `vis_r/g/b`, `inv`,
309 use the solid style (no style flags) with the selected colour; VGUI/white 309 `inv_mat`, `inv_r/g/b`) and `[chams_vm]` (`on`, `mat`, `r/g/b`). The GUI has
310 materials do not draw skinned models and made the hands/weapon disappear. 310 a Hostile/Neutral/Friendly section each, plus Viewmodel.
311 Only the normal-depth pass is used. 311- This is a material override, not stencil outlines.
312- Feature is off by default; `[chams] active/bind/walls/viewmodel/material/r/g/b`,
313 with Through walls, Viewmodel, a Material dropdown and colour in the Visuals
314 card.
315- This is a flat material override, not stencil outlines; depth handling is
316 the simple ignorez/normal two-pass.
317 312
318## Bones / ESP 313## Bones / ESP
319 314
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) {
187 return model && (strstr(model, "/v_") || strstr(model, "\\v_")); 187 return model && (strstr(model, "/v_") || strstr(model, "\\v_"));
188} 188}
189 189
190// 0 = hostile, 1 = neutral, 2 = friendly, -1 = not an NPC.
191static bool npc_is_friendly(int idx, const char *model, int local_idx,
192 bool &neutral);
193int esp_npc_relation(int idx) {
194 if (!esp_is_npc_index(idx))
195 return -1;
196 int local = engine_get_local_player();
197 if (local <= 0)
198 return 0;
199 void *ent = entitylist_get(idx);
200 const char *model = ent ? entity_model_name(ent) : nullptr;
201 bool neutral = false;
202 bool friendly = npc_is_friendly(idx, model, local, neutral);
203 if (neutral)
204 return 1;
205 if (friendly)
206 return 2;
207 return 0;
208}
209
190static bool model_is_friendly(const char *m) { 210static bool model_is_friendly(const char *m) {
191 static const char *patterns[] = { 211 static const char *patterns[] = {
192 "citizen", "humans/", "male_", "female_", "barney", "alyx", 212 "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() {
446 "Draw the projectile's damage radius on the ground.", &g_proj_radius); 446 "Draw the projectile's damage radius on the ground.", &g_proj_radius);
447 447
448 int chams = add_card(2, &g_chams, "Chams", 448 int chams = add_card(2, &g_chams, "Chams",
449 "Flat-colour NPC models.", true); 449 "Per-relationship model colours.", true);
450 add_toggle(chams, "Through walls", 450 static const char *const chams_mat_names[] = {
451 "Draw a second pass with the depth test disabled.", 451 "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright",
452 &g_chams_walls); 452 "Glow outline"};
453 auto add_chams_group = [&](const char *title, ChamsGroup &g) {
454 add_section(chams, title);
455 add_toggle(chams, "Visible", "Draw the normal-depth pass.",
456 &g.vis_on);
457 add_dropdown(chams, "Visible material", "Material for the visible pass.",
458 chams_mat_names, 7, &g.vis_mat);
459 add_color(chams, "Visible colour", "Colour for the visible pass.",
460 g.vis_rgb);
461 add_toggle(chams, "Through walls", "Draw a pass with the depth test off.",
462 &g.inv_on);
463 add_dropdown(chams, "Invisible material",
464 "Material for the through-wall pass.", chams_mat_names, 7,
465 &g.inv_mat);
466 add_color(chams, "Invisible colour",
467 "Colour for the through-wall pass.", g.inv_rgb);
468 };
469 add_chams_group("Hostile", g_chams_hostile);
470 add_chams_group("Neutral", g_chams_neutral);
471 add_chams_group("Friendly", g_chams_friendly);
472 add_section(chams, "Viewmodel");
453 add_toggle(chams, "Viewmodel (hands/weapon)", 473 add_toggle(chams, "Viewmodel (hands/weapon)",
454 "Apply chams to the first-person hands and weapon too.", 474 "Apply chams to the first-person hands and weapon.",
455 &g_chams_viewmodel); 475 &g_chams_viewmodel);
456 static const char *const chams_mat_names[] = { 476 add_dropdown(chams, "Viewmodel material", "Material for the viewmodel.",
457 "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright"}; 477 chams_mat_names, 7, &g_chams_vm_mat);
458 add_dropdown(chams, "Material", "Chams material.", chams_mat_names, 6, 478 add_color(chams, "Viewmodel colour", "Colour for the viewmodel.",
459 &g_chams_mat_index); 479 g_chams_vm_rgb);
460 add_color(chams, "Colour", "Chams colour.", g_chams_rgb);
461 int view = add_card(2, nullptr, "Camera / display", 480 int view = add_card(2, nullptr, "Camera / display",
462 "Camera FOV, viewmodel FOV and HDR brightness.", true); 481 "Camera FOV, viewmodel FOV and HDR brightness.", true);
463 add_slider(view, "Camera FOV", 482 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,
230 g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, 230 g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright,
231 g_world_tint, g_fog, g_skybox, g_projectiles, g_chams; 231 g_world_tint, g_fog, g_skybox, g_projectiles, g_chams;
232extern bool g_proj_fuse, g_proj_radius; 232extern bool g_proj_fuse, g_proj_radius;
233extern bool g_chams_walls; 233
234// Chams settings per relationship, and separate visible / through-wall
235// (invisible) material and colour. The viewmodel has a single style.
236struct ChamsGroup {
237 bool vis_on;
238 int vis_mat;
239 int vis_rgb[3];
240 bool inv_on;
241 int inv_mat;
242 int inv_rgb[3];
243};
244extern ChamsGroup g_chams_hostile, g_chams_neutral, g_chams_friendly;
234extern bool g_chams_viewmodel; 245extern bool g_chams_viewmodel;
235extern int g_chams_mat_index; 246extern int g_chams_vm_mat;
236extern int g_chams_rgb[3]; 247extern int g_chams_vm_rgb[3];extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on;
237extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on;
238extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; 248extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3];
239extern bool g_fog_skybox, g_fog_disable; 249extern bool g_fog_skybox, g_fog_disable;
240extern int g_fog_rgb[3], g_fog_start, g_fog_end; 250extern int g_fog_rgb[3], g_fog_start, g_fog_end;
@@ -337,6 +347,7 @@ void silentaim_reset_lock();
337void *entitylist_get(int idx); 347void *entitylist_get(int idx);
338bool esp_is_npc_index(int idx); 348bool esp_is_npc_index(int idx);
339bool esp_is_viewmodel_index(int idx); 349bool esp_is_viewmodel_index(int idx);
350int esp_npc_relation(int idx);
340void *world_find_material(const char *name); 351void *world_find_material(const char *name);
341void chams_init(); 352void chams_init();
342void chams_preload(); 353void 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
63FeatureState g_projectiles{true, 0}; // grenade / rocket ESP 63FeatureState g_projectiles{true, 0}; // grenade / rocket ESP
64bool g_proj_fuse = true; 64bool g_proj_fuse = true;
65bool g_proj_radius = true; 65bool g_proj_radius = true;
66FeatureState g_chams{false, 0}; // flat model material, optional through walls 66FeatureState g_chams{false, 0};
67bool g_chams_walls = true; 67ChamsGroup g_chams_hostile{true, 3, {255, 60, 60}, true, 3, {255, 60, 60}};
68ChamsGroup g_chams_neutral{true, 3, {220, 220, 220}, false, 3, {220, 220, 220}};
69ChamsGroup g_chams_friendly{true, 3, {60, 255, 60}, false, 3, {60, 255, 60}};
68bool g_chams_viewmodel = false; 70bool g_chams_viewmodel = false;
69int g_chams_mat_index = 0; 71int g_chams_vm_mat = 3;
70int g_chams_rgb[3] = {255, 60, 60}; 72int g_chams_vm_rgb[3] = {200, 200, 255};
71bool g_item_health = true; 73bool g_item_health = true;
72bool g_item_ammo = true; 74bool g_item_ammo = true;
73bool g_item_armor = true; 75bool g_item_armor = true;
@@ -239,11 +241,20 @@ void config_save() {
239 fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", 241 fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n",
240 g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, 242 g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse,
241 (int)g_proj_radius); 243 (int)g_proj_radius);
242 fprintf(f, "[chams]\nactive=%d\nbind=%d\nwalls=%d\nviewmodel=%d\n" 244 fprintf(f, "[chams]\nactive=%d\nbind=%d\n", g_chams.active, g_chams.bind);
243 "material=%d\nr=%d\ng=%d\nb=%d\n", 245 auto save_chams = [&](const char *name, const ChamsGroup &g) {
244 g_chams.active, g_chams.bind, (int)g_chams_walls, 246 fprintf(f, "[%s]\nvis=%d\nvis_mat=%d\nvis_r=%d\nvis_g=%d\nvis_b=%d\n"
245 (int)g_chams_viewmodel, g_chams_mat_index, g_chams_rgb[0], 247 "inv=%d\ninv_mat=%d\ninv_r=%d\ninv_g=%d\ninv_b=%d\n",
246 g_chams_rgb[1], g_chams_rgb[2]); 248 name, (int)g.vis_on, g.vis_mat, g.vis_rgb[0], g.vis_rgb[1],
249 g.vis_rgb[2], (int)g.inv_on, g.inv_mat, g.inv_rgb[0],
250 g.inv_rgb[1], g.inv_rgb[2]);
251 };
252 save_chams("chams_hostile", g_chams_hostile);
253 save_chams("chams_neutral", g_chams_neutral);
254 save_chams("chams_friendly", g_chams_friendly);
255 fprintf(f, "[chams_vm]\non=%d\nmat=%d\nr=%d\ng=%d\nb=%d\n",
256 (int)g_chams_viewmodel, g_chams_vm_mat, g_chams_vm_rgb[0],
257 g_chams_vm_rgb[1], g_chams_vm_rgb[2]);
247 fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" 258 fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n"
248 "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n" 259 "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n"
249 "usable=%d\n", 260 "usable=%d\n",
@@ -294,10 +305,14 @@ static bool config_load() {
294 bool in_esp = false, in_bhop = false, in_aim = false, 305 bool in_esp = false, in_bhop = false, in_aim = false,
295 in_jumpbug = false, in_spread = false, in_items = false, 306 in_jumpbug = false, in_spread = false, in_items = false,
296 in_tint = false, in_fog = false, in_view = false, in_movement = false, 307 in_tint = false, in_fog = false, in_view = false, in_movement = false,
297 in_proj = false, in_chams = false; 308 in_proj = false;
309 ChamsGroup *chams_grp = nullptr;
310 bool in_chams_vm = false;
298 while (fgets(line, sizeof(line), f)) { 311 while (fgets(line, sizeof(line), f)) {
299 line[strcspn(line, "\r\n")] = 0; 312 line[strcspn(line, "\r\n")] = 0;
300 if (line[0] == '[') { 313 if (line[0] == '[') {
314 chams_grp = nullptr;
315 in_chams_vm = false;
301 if (strstr(line, "[bhop]")) 316 if (strstr(line, "[bhop]"))
302 cur = &g_bhop; 317 cur = &g_bhop;
303 else if (strstr(line, "[abh]")) 318 else if (strstr(line, "[abh]"))
@@ -324,6 +339,19 @@ static bool config_load() {
324 cur = &g_projectiles; 339 cur = &g_projectiles;
325 else if (strstr(line, "[chams]")) 340 else if (strstr(line, "[chams]"))
326 cur = &g_chams; 341 cur = &g_chams;
342 else if (strstr(line, "[chams_hostile]")) {
343 cur = nullptr;
344 chams_grp = &g_chams_hostile;
345 } else if (strstr(line, "[chams_neutral]")) {
346 cur = nullptr;
347 chams_grp = &g_chams_neutral;
348 } else if (strstr(line, "[chams_friendly]")) {
349 cur = nullptr;
350 chams_grp = &g_chams_friendly;
351 } else if (strstr(line, "[chams_vm]")) {
352 cur = nullptr;
353 in_chams_vm = true;
354 }
327 else if (strstr(line, "[aimbot]")) 355 else if (strstr(line, "[aimbot]"))
328 cur = &g_aimbot; 356 cur = &g_aimbot;
329 else if (strstr(line, "[triggerbot]")) 357 else if (strstr(line, "[triggerbot]"))
@@ -347,8 +375,8 @@ static bool config_load() {
347 in_view = strstr(line, "[view]") != nullptr; 375 in_view = strstr(line, "[view]") != nullptr;
348 in_movement = strstr(line, "[movement]") != nullptr; 376 in_movement = strstr(line, "[movement]") != nullptr;
349 in_proj = (cur == &g_projectiles); 377 in_proj = (cur == &g_projectiles);
350 in_chams = (cur == &g_chams); 378 } else if (cur || in_view || in_movement || chams_grp ||
351 } else if (cur || in_view || in_movement) { 379 in_chams_vm) {
352 char *eq = strchr(line, '='); 380 char *eq = strchr(line, '=');
353 if (!eq) 381 if (!eq)
354 continue; 382 continue;
@@ -394,22 +422,50 @@ static bool config_load() {
394 else if (strcmp(key, "radius") == 0) 422 else if (strcmp(key, "radius") == 0)
395 g_proj_radius = atoi(val) != 0; 423 g_proj_radius = atoi(val) != 0;
396 } 424 }
397 if (in_chams) { 425 if (chams_grp) {
398 if (strcmp(key, "walls") == 0) 426 ChamsGroup &g = *chams_grp;
399 g_chams_walls = atoi(val) != 0; 427 if (strcmp(key, "vis") == 0)
400 else if (strcmp(key, "viewmodel") == 0) 428 g.vis_on = atoi(val) != 0;
429 else if (strcmp(key, "vis_mat") == 0) {
430 int v = atoi(val);
431 if (v < 0) v = 0;
432 if (v > 6) v = 6;
433 g.vis_mat = v;
434 } else if (strcmp(key, "vis_r") == 0 || strcmp(key, "vis_g") == 0 ||
435 strcmp(key, "vis_b") == 0) {
436 int v = atoi(val);
437 if (v < 0) v = 0;
438 if (v > 255) v = 255;
439 g.vis_rgb[key[4] == 'r' ? 0 : key[4] == 'g' ? 1 : 2] = v;
440 } else if (strcmp(key, "inv") == 0)
441 g.inv_on = atoi(val) != 0;
442 else if (strcmp(key, "inv_mat") == 0) {
443 int v = atoi(val);
444 if (v < 0) v = 0;
445 if (v > 6) v = 6;
446 g.inv_mat = v;
447 } else if (strcmp(key, "inv_r") == 0 || strcmp(key, "inv_g") == 0 ||
448 strcmp(key, "inv_b") == 0) {
449 int v = atoi(val);
450 if (v < 0) v = 0;
451 if (v > 255) v = 255;
452 g.inv_rgb[key[4] == 'r' ? 0 : key[4] == 'g' ? 1 : 2] = v;
453 }
454 }
455 if (in_chams_vm) {
456 if (strcmp(key, "on") == 0)
401 g_chams_viewmodel = atoi(val) != 0; 457 g_chams_viewmodel = atoi(val) != 0;
402 else if (strcmp(key, "material") == 0) { 458 else if (strcmp(key, "mat") == 0) {
403 int v = atoi(val); 459 int v = atoi(val);
404 if (v < 0) v = 0; 460 if (v < 0) v = 0;
405 if (v > 5) v = 5; 461 if (v > 6) v = 6;
406 g_chams_mat_index = v; 462 g_chams_vm_mat = v;
407 } else if (strcmp(key, "r") == 0 || strcmp(key, "g") == 0 || 463 } else if (strcmp(key, "r") == 0 || strcmp(key, "g") == 0 ||
408 strcmp(key, "b") == 0) { 464 strcmp(key, "b") == 0) {
409 int v = atoi(val); 465 int v = atoi(val);
410 if (v < 0) v = 0; 466 if (v < 0) v = 0;
411 if (v > 255) v = 255; 467 if (v > 255) v = 255;
412 g_chams_rgb[key[0] == 'r' ? 0 : key[0] == 'g' ? 1 : 2] = v; 468 g_chams_vm_rgb[key[0] == 'r' ? 0 : key[0] == 'g' ? 1 : 2] = v;
413 } 469 }
414 } 470 }
415 if (in_aim) { 471 if (in_aim) {
@@ -637,12 +693,14 @@ void config_reset_defaults() {
637 g_proj_fuse = true; 693 g_proj_fuse = true;
638 g_proj_radius = true; 694 g_proj_radius = true;
639 g_chams = {false, 0}; 695 g_chams = {false, 0};
640 g_chams_walls = true; 696 g_chams_hostile = {true, 3, {255, 60, 60}, true, 3, {255, 60, 60}};
697 g_chams_neutral = {true, 3, {220, 220, 220}, false, 3, {220, 220, 220}};
698 g_chams_friendly = {true, 3, {60, 255, 60}, false, 3, {60, 255, 60}};
641 g_chams_viewmodel = false; 699 g_chams_viewmodel = false;
642 g_chams_mat_index = 0; 700 g_chams_vm_mat = 3;
643 g_chams_rgb[0] = 255; 701 g_chams_vm_rgb[0] = 200;
644 g_chams_rgb[1] = 60; 702 g_chams_vm_rgb[1] = 200;
645 g_chams_rgb[2] = 60; 703 g_chams_vm_rgb[2] = 255;
646 g_item_health = true; 704 g_item_health = true;
647 g_item_ammo = true; 705 g_item_ammo = true;
648 g_item_armor = true; 706 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);
197void *g_modelrender = nullptr; 197void *g_modelrender = nullptr;
198ModelExecFn g_orig_model_exec = nullptr; 198ModelExecFn g_orig_model_exec = nullptr;
199ForceOverrideFn g_force_override = nullptr; 199ForceOverrideFn g_force_override = nullptr;
200void *g_style_mats[6] = {}; 200void *g_style_mats[7] = {};
201 201
202void *viewmodel_material(); 202void *viewmodel_material();
203 203
204const char *chams_material_name(int idx) { 204const char *chams_material_name(int idx) {
205 switch (idx) { 205 switch (idx) {
206 case 1: return "debug/debugwireframe"; 206 case 1: return "debug/debugwireframe";
207 case 2: return "vgui/white_additive"; 207 case 2:
208 case 3: return "debug/debugtranslucentsinglecolor"; 208 case 3:
209 case 4: return "debug/debugtranslucentvertexcolor"; 209 case 4:
210 case 5: return "debug/debugmrmfullbright2"; 210 case 5:
211 case 6: return "debug/debugtranslucentsinglecolor";
211 default: return "vgui/white"; // models/debug/debugwhite is EP2-only 212 default: return "vgui/white"; // models/debug/debugwhite is EP2-only
212 } 213 }
213} 214}
214 215
215void *chams_material() { 216void *style_material(int idx) {
216 int idx = g_chams_mat_index;
217 if (idx < 0) idx = 0; 217 if (idx < 0) idx = 0;
218 if (idx > 5) idx = 5; 218 if (idx > 6) idx = 6;
219 if (g_style_mats[idx]) 219 if (g_style_mats[idx])
220 return g_style_mats[idx]; 220 return g_style_mats[idx];
221 if (g_style_mats[3]) 221 if (g_style_mats[3])
@@ -223,8 +223,7 @@ void *chams_material() {
223 return viewmodel_material(); 223 return viewmodel_material();
224} 224}
225 225
226// Viewmodels need a model shader; the VGUI white materials used by some 226// Viewmodels need a model shader; vgui materials do not draw skinned models.
227// styles do not draw skinned models and make the hands/weapon disappear.
228void *viewmodel_material() { 227void *viewmodel_material() {
229 if (g_style_mats[3]) 228 if (g_style_mats[3])
230 return g_style_mats[3]; 229 return g_style_mats[3];
@@ -249,13 +248,15 @@ void chams_color(void *mat, float r, float g, float b) {
249 vfunc<ColorFn>(mat, 28)(mat, r, g, b); 248 vfunc<ColorFn>(mat, 28)(mat, r, g, b);
250} 249}
251 250
252void chams_apply(void *mat, bool ignorez, int style, bool force_white) { 251void chams_apply(void *mat, bool ignorez, int style, bool force_white,
252 const int *rgb) {
253 using FlagFn = void (*)(void *, int, bool); 253 using FlagFn = void (*)(void *, int, bool);
254 float r = force_white ? 1.0f : (float)g_chams_rgb[0] / 255.0f; 254 float r = force_white ? 1.0f : (float)rgb[0] / 255.0f;
255 float g = force_white ? 1.0f : (float)g_chams_rgb[1] / 255.0f; 255 float g = force_white ? 1.0f : (float)rgb[1] / 255.0f;
256 float b = force_white ? 1.0f : (float)g_chams_rgb[2] / 255.0f; 256 float b = force_white ? 1.0f : (float)rgb[2] / 255.0f;
257 chams_color(mat, r, g, b); 257 chams_color(mat, r, g, b);
258 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright. 258 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright,
259 // 6 Glow outline (solid base; the wireframe overlay is a second draw).
259 vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez); 260 vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez);
260 vfunc<FlagFn>(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1); 261 vfunc<FlagFn>(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1);
261 vfunc<FlagFn>(mat, 29)(mat, 1 << 7 /* ADDITIVE */, style == 2); 262 vfunc<FlagFn>(mat, 29)(mat, 1 << 7 /* ADDITIVE */, style == 2);
@@ -282,31 +283,50 @@ void hkdModelExecute(void *self, const void *state, const void *info,
282 // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. 283 // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit.
283 int idx = *(const int *)((const char *)info + 0x30); 284 int idx = *(const int *)((const char *)info + 0x30);
284 if (idx > 0 && idx <= 8192) { 285 if (idx > 0 && idx <= 8192) {
285 bool is_npc = esp_is_npc_index(idx);
286 bool is_vm = g_chams_viewmodel && esp_is_viewmodel_index(idx); 286 bool is_vm = g_chams_viewmodel && esp_is_viewmodel_index(idx);
287 if (is_npc || is_vm) { 287 ChamsGroup *grp = nullptr;
288 int style = g_chams_mat_index; 288 if (!is_vm) {
289 if (style < 0) style = 0; 289 int rel = esp_npc_relation(idx);
290 if (style > 5) style = 5; 290 if (rel >= 0)
291 // VGUI materials do not draw skinned models: use the solid model 291 grp = rel == 0 ? &g_chams_hostile
292 // shader for the viewmodel with the same flags (flat/additive). 292 : rel == 1 ? &g_chams_neutral : &g_chams_friendly;
293 bool vgui_style = (style == 0 || style == 2); 293 }
294 void *mat = (is_vm && vgui_style) ? viewmodel_material() 294 if (is_vm || grp) {
295 : chams_material(); 295 auto draw_pass = [&](int style, const int *rgb,
296 if (!mat) 296 bool ignorez) -> bool {
297 mat = viewmodel_material(); 297 if (style < 0) style = 0;
298 if (mat) { 298 if (style > 6) style = 6;
299 void *mat = style_material(style);
300 if (!mat)
301 return false;
299 bool white = style == 4; 302 bool white = style == 4;
300 if (g_chams_walls && !is_vm) { 303 chams_apply(mat, ignorez, style, white, rgb);
301 chams_apply(mat, true, style, white); 304 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
302 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
303 g_orig_model_exec(self, state, info, bone);
304 }
305 chams_apply(mat, false, style, white);
306 g_force_override(self, mat, 0);
307 g_orig_model_exec(self, state, info, bone); 305 g_orig_model_exec(self, state, info, bone);
308 g_force_override(self, nullptr, 0); 306 if (style == 6) {
307 // Solid base plus a wireframe contour pass for depth.
308 void *wire = g_style_mats[1];
309 if (wire && wire != mat) {
310 chams_apply(wire, ignorez, 1, false, rgb);
311 g_force_override(self, wire, 0);
312 g_orig_model_exec(self, state, info, bone);
313 chams_reset(wire);
314 }
315 }
309 chams_reset(mat); 316 chams_reset(mat);
317 return true;
318 };
319 bool did = false;
320 if (is_vm) {
321 did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false);
322 } else {
323 if (grp->inv_on)
324 did |= draw_pass(grp->inv_mat, grp->inv_rgb, true);
325 if (grp->vis_on)
326 did |= draw_pass(grp->vis_mat, grp->vis_rgb, false);
327 }
328 if (did) {
329 g_force_override(self, nullptr, 0);
310 return; 330 return;
311 } 331 }
312 } 332 }
@@ -337,7 +357,7 @@ void chams_preload() {
337 void *probe = world_find_material("vgui/white"); 357 void *probe = world_find_material("vgui/white");
338 logf("chams: probe vgui/white = %p", probe); 358 logf("chams: probe vgui/white = %p", probe);
339 } 359 }
340 for (int i = 0; i < 6; i++) { 360 for (int i = 0; i < 7; i++) {
341 if (g_style_mats[i]) 361 if (g_style_mats[i])
342 continue; 362 continue;
343 void *m = world_find_material(chams_material_name(i)); 363 void *m = world_find_material(chams_material_name(i));