summaryrefslogtreecommitdiffstats
path: root/src/studio.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 17:51:20 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 17:51:20 +0100
commit49df428d95a7c443d418d5b7e2a93af599b54f76 (patch)
tree604e64048aacc5e2b86f441e03e5e5ab8600bef2 /src/studio.cpp
parentItems: skip the CNetworkArray length header for m_iAmmo/m_hMyWeapons (diff)
downloadhl2h-49df428d95a7c443d418d5b7e2a93af599b54f76.tar.gz
hl2h-49df428d95a7c443d418d5b7e2a93af599b54f76.zip
Add chams
- Hook IVModelRender::DrawModelExecute (VEngineModel016 slot 19) and force models/debug/debugwhite for NPC models, identified by ModelRenderInfo_t::entity_index. - Two passes: MATERIAL_VAR_IGNOREZ through walls, then normal depth, with IMaterial::ColorModulate for the configured colour; restore the shared material's flag/colour afterwards. - Material lookup via the IMaterialSystem list, FindMaterial fallback. - New Chams feature/card with Through walls and colour; [chams] config. - Remove the temporary ammo/projectile/fuse diagnostics.
Diffstat (limited to 'src/studio.cpp')
-rw-r--r--src/studio.cpp98
1 files changed, 98 insertions, 0 deletions
diff --git a/src/studio.cpp b/src/studio.cpp
index 23be97f..1b7b77d 100644
--- a/src/studio.cpp
+++ b/src/studio.cpp
@@ -181,8 +181,106 @@ std::string loaded_module_path(const char *needle) {
181 return found; 181 return found;
182} 182}
183 183
184// ---------------------------------------------------------------------------
185// chams: force a flat material on NPC draws
186// ---------------------------------------------------------------------------
187//
188// IVModelRender::DrawModelExecute is called once per model. For NPC entities
189// we force models/debug/debugwhite, optionally with MATERIAL_VAR_IGNOREZ for
190// the through-wall pass, and draw twice. IVModelRender::ForcedMaterialOverride
191// is slot 1; DrawModelExecute is slot 19 in VEngineModel016.
192
193using ModelExecFn = void (*)(void *, const void *, const void *, void *);
194using ForceOverrideFn = void (*)(void *, void *, int);
195
196void *g_modelrender = nullptr;
197ModelExecFn g_orig_model_exec = nullptr;
198ForceOverrideFn g_force_override = nullptr;
199void *g_chams_mat = nullptr;
200bool g_chams_mat_tried = false;
201
202void *chams_material() {
203 if (g_chams_mat_tried)
204 return g_chams_mat;
205 g_chams_mat_tried = true;
206 g_chams_mat = world_find_material("models/debug/debugwhite");
207 if (g_chams_mat)
208 logf("chams: material models/debug/debugwhite = %p", g_chams_mat);
209 else
210 logf("chams: models/debug/debugwhite not found");
211 return g_chams_mat;
212}
213
214void chams_apply(void *mat, bool ignorez) {
215 using ColorFn = void (*)(void *, float, float, float);
216 using FlagFn = void (*)(void *, int, bool);
217 vfunc<ColorFn>(mat, 28)(mat, (float)g_chams_rgb[0] / 255.0f,
218 (float)g_chams_rgb[1] / 255.0f,
219 (float)g_chams_rgb[2] / 255.0f);
220 vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* MATERIAL_VAR_IGNOREZ */, ignorez);
221}
222
223void hkdModelExecute(void *self, const void *state, const void *info,
224 void *bone) {
225 if (!g_orig_model_exec)
226 return;
227 if (feature_active(FEAT_CHAMS) && info && g_force_override) {
228 // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit.
229 int idx = *(const int *)((const char *)info + 0x30);
230 if (idx > 0 && idx <= 8192 && esp_is_npc_index(idx)) {
231 void *mat = chams_material();
232 if (mat) {
233 if (g_chams_walls) {
234 chams_apply(mat, true);
235 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
236 g_orig_model_exec(self, state, info, bone);
237 }
238 chams_apply(mat, false);
239 g_force_override(self, mat, 0);
240 g_orig_model_exec(self, state, info, bone);
241 g_force_override(self, nullptr, 0);
242 // debugwhite is shared: restore colour and depth behaviour.
243 using ColorFn = void (*)(void *, float, float, float);
244 using FlagFn = void (*)(void *, int, bool);
245 vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false);
246 vfunc<ColorFn>(mat, 28)(mat, 1.0f, 1.0f, 1.0f);
247 return;
248 }
249 }
250 }
251 g_orig_model_exec(self, state, info, bone);
252}
253
184} // namespace 254} // namespace
185 255
256void chams_init() {
257 if (g_modelrender || !g_iface.modelrender) {
258 if (!g_iface.modelrender)
259 logf("chams: VEngineModel016 unavailable");
260 return;
261 }
262 void **vt = *(void ***)g_iface.modelrender;
263 if (!vt) {
264 logf("chams: model render has no vtable");
265 return;
266 }
267 g_force_override = (ForceOverrideFn)vt[1];
268 g_orig_model_exec = (ModelExecFn)vt[19];
269 if (!g_engine.contains((uintptr_t)g_force_override) ||
270 !g_engine.contains((uintptr_t)g_orig_model_exec)) {
271 logf("chams: implausible vtable entries override=%p draw=%p",
272 (void *)g_force_override, (void *)g_orig_model_exec);
273 return;
274 }
275 static void *nvt[32];
276 memcpy(nvt, vt, sizeof(nvt));
277 nvt[19] = (void *)&hkdModelExecute;
278 g_modelrender = g_iface.modelrender;
279 *(void ***)g_modelrender = nvt;
280 logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender,
281 (void *)g_orig_model_exec, (void *)g_force_override);
282}
283
186void studio_props_init() { 284void studio_props_init() {
187 if (g_install_tried) 285 if (g_install_tried)
188 return; 286 return;