diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 18:58:13 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 18:58:13 +0100 |
| commit | 5ddc9eb9d02ddc84e4c3a45bdcb2941513fb0346 (patch) | |
| tree | c83aa673488b0628e0f63cdc8b82e45c7693be9d | |
| parent | Chams: apply model-safe styles to the viewmodel (diff) | |
| download | hl2h-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.md | 53 | ||||
| -rw-r--r-- | src/esp.cpp | 20 | ||||
| -rw-r--r-- | src/gui.cpp | 39 | ||||
| -rw-r--r-- | src/hl2h.h | 19 | ||||
| -rw-r--r-- | src/menu.cpp | 108 | ||||
| -rw-r--r-- | src/studio.cpp | 94 |
6 files changed, 228 insertions, 105 deletions
| @@ -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. | ||
| 191 | static bool npc_is_friendly(int idx, const char *model, int local_idx, | ||
| 192 | bool &neutral); | ||
| 193 | int 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 | |||
| 190 | static bool model_is_friendly(const char *m) { | 210 | static 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", |
| @@ -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; |
| 232 | extern bool g_proj_fuse, g_proj_radius; | 232 | extern bool g_proj_fuse, g_proj_radius; |
| 233 | extern 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. | ||
| 236 | struct 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 | }; | ||
| 244 | extern ChamsGroup g_chams_hostile, g_chams_neutral, g_chams_friendly; | ||
| 234 | extern bool g_chams_viewmodel; | 245 | extern bool g_chams_viewmodel; |
| 235 | extern int g_chams_mat_index; | 246 | extern int g_chams_vm_mat; |
| 236 | extern int g_chams_rgb[3]; | 247 | extern int g_chams_vm_rgb[3];extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; |
| 237 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; | ||
| 238 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; | 248 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; |
| 239 | extern bool g_fog_skybox, g_fog_disable; | 249 | extern bool g_fog_skybox, g_fog_disable; |
| 240 | extern int g_fog_rgb[3], g_fog_start, g_fog_end; | 250 | extern int g_fog_rgb[3], g_fog_start, g_fog_end; |
| @@ -337,6 +347,7 @@ void silentaim_reset_lock(); | |||
| 337 | void *entitylist_get(int idx); | 347 | void *entitylist_get(int idx); |
| 338 | bool esp_is_npc_index(int idx); | 348 | bool esp_is_npc_index(int idx); |
| 339 | bool esp_is_viewmodel_index(int idx); | 349 | bool esp_is_viewmodel_index(int idx); |
| 350 | int esp_npc_relation(int idx); | ||
| 340 | void *world_find_material(const char *name); | 351 | void *world_find_material(const char *name); |
| 341 | void chams_init(); | 352 | void chams_init(); |
| 342 | void chams_preload(); | 353 | 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 | |||
| 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP | 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP |
| 64 | bool g_proj_fuse = true; | 64 | bool g_proj_fuse = true; |
| 65 | bool g_proj_radius = true; | 65 | bool g_proj_radius = true; |
| 66 | FeatureState g_chams{false, 0}; // flat model material, optional through walls | 66 | FeatureState g_chams{false, 0}; |
| 67 | bool g_chams_walls = true; | 67 | ChamsGroup g_chams_hostile{true, 3, {255, 60, 60}, true, 3, {255, 60, 60}}; |
| 68 | ChamsGroup g_chams_neutral{true, 3, {220, 220, 220}, false, 3, {220, 220, 220}}; | ||
| 69 | ChamsGroup g_chams_friendly{true, 3, {60, 255, 60}, false, 3, {60, 255, 60}}; | ||
| 68 | bool g_chams_viewmodel = false; | 70 | bool g_chams_viewmodel = false; |
| 69 | int g_chams_mat_index = 0; | 71 | int g_chams_vm_mat = 3; |
| 70 | int g_chams_rgb[3] = {255, 60, 60}; | 72 | int g_chams_vm_rgb[3] = {200, 200, 255}; |
| 71 | bool g_item_health = true; | 73 | bool g_item_health = true; |
| 72 | bool g_item_ammo = true; | 74 | bool g_item_ammo = true; |
| 73 | bool g_item_armor = true; | 75 | bool 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); | |||
| 197 | void *g_modelrender = nullptr; | 197 | void *g_modelrender = nullptr; |
| 198 | ModelExecFn g_orig_model_exec = nullptr; | 198 | ModelExecFn g_orig_model_exec = nullptr; |
| 199 | ForceOverrideFn g_force_override = nullptr; | 199 | ForceOverrideFn g_force_override = nullptr; |
| 200 | void *g_style_mats[6] = {}; | 200 | void *g_style_mats[7] = {}; |
| 201 | 201 | ||
| 202 | void *viewmodel_material(); | 202 | void *viewmodel_material(); |
| 203 | 203 | ||
| 204 | const char *chams_material_name(int idx) { | 204 | const 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 | ||
| 215 | void *chams_material() { | 216 | void *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. | ||
| 228 | void *viewmodel_material() { | 227 | void *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 | ||
| 252 | void chams_apply(void *mat, bool ignorez, int style, bool force_white) { | 251 | void 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)); |
