From a6f1e039ce988cf139c52d7853e96dc8496fb806 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 12:12:33 +0100 Subject: Item ESP: recover CPhysicsProp model names from the server Client CPhysicsProp entities frequently report m_nModelIndex == 0, so item crates appeared as model=? and were never classified. Fall back to the server entity's m_nModelIndex (set in CItem_ItemCrate::Spawn) when the client model index is missing, then resolve the name through the shared modelinfo. --- CONTEXT.md | 3 +++ src/esp.cpp | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index a3cbdf8..737a6b2 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -286,6 +286,9 @@ vocabulary / decisions. - Only `item_item_crate` and `item_beacon_crate` are tagged as supply crates. Other breakable crates are intentionally ignored; crate chunk debris is skipped. +- Client `CPhysicsProp` entities often have `m_nModelIndex == 0`, including + item crates. The item scan falls back to the server entity's model index + (`server_model_index`) to recover the model name. ## Roadmap / TODO diff --git a/src/esp.cpp b/src/esp.cpp index 2fb5d1b..ec3e94b 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -100,10 +100,9 @@ static const char *entity_class_name(void *net, void **out_cc) { // m_nModelIndex -> IVModelInfoClient(006)::GetModel/GetModelName. // Itanium ABI: two dtor slots, so GetModel=2, GetModelName=4. -static const char *entity_model_name(void *ent) { - if (!g_iface.modelinfo || g_net.modelindex < 0) return nullptr; - int modelindex = *(int *)((uintptr_t)ent + g_net.modelindex); - if (modelindex <= 0) return nullptr; +static const char *model_name_from_index(int modelindex) { + if (!g_iface.modelinfo || modelindex <= 0) + return nullptr; const void *model = vfunc(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex); if (!model) @@ -111,6 +110,12 @@ static const char *entity_model_name(void *ent) { return vfunc(g_iface.modelinfo, 4)(g_iface.modelinfo, model); } +static const char *entity_model_name(void *ent) { + if (!g_iface.modelinfo || g_net.modelindex < 0) return nullptr; + int modelindex = *(int *)((uintptr_t)ent + g_net.modelindex); + return model_name_from_index(modelindex); +} + // HL2 SP: generic NPCs (combine, metrocop, zombie, citizen, antlion, ...) all // use the single client class "CAI_BaseNPC" — only special NPCs register their // own class names (CNPC_Manhack, CNPC_Strider, CNPC_CombineGunship, ...). @@ -1629,6 +1634,7 @@ static void items_collect(int local_idx) { g_nitems = 0; if (!g_iface.entitylist || g_net.origin < 0 || g_net.modelindex < 0) return; + resolve_server_netvars(); using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); @@ -1664,6 +1670,14 @@ static void items_collect(int local_idx) { continue; const char *cls = entity_class_name(net, nullptr); const char *model = entity_model_name(ent); + // CPhysicsProp's client m_nModelIndex is often 0 (e.g. item_item_crate); + // fall back to the server entity's model index, which is set in Spawn(). + if ((!model || !*model) && g_iface.engineserver) { + void *sbase = nullptr; + server_entity_health(idx, nullptr, nullptr, &sbase); + if (sbase) + model = model_name_from_index(server_model_index(sbase)); + } char label[128]; int kind = classify_item(model, cls, label, sizeof(label)); -- cgit v1.2.3