From a5d732920bd56c96e85db1ce2073bebf53e6b1ea Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 5 Nov 2026 18:09:43 +0100 Subject: Items: keep health chargers visible, add charger diagnostics The health charger's candidate m_iJuice offset (0x3c8) read 0 on a full charger, so the empty-hide made health chargers disappear entirely. Restrict the hide to suit chargers (verified 0x47c) until the health offset is confirmed; health chargers stay visible. Classify with the server class name when the client class is generic, accept `healthcharger` in the health branch (maps use item_healthcharger / CNewWallHealth, not the func_healthcharger brush), and log up to eight `juice:` lines with the class and both candidate offsets/values. --- CONTEXT.md | 17 ++++++++++------- src/esp.cpp | 33 ++++++++++++++++++++++++++++----- src/server.cpp | 18 +++++++++++++++++- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 54ca0b4..8ff833c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -702,13 +702,16 @@ vocabulary / decisions. Reading at the raw offset shifted every count by one type (e.g. RPG read the buckshot count 30 against a max of 3, permanently hiding rockets). - Chargers and ammo/supply crates are dispensers and are exempt from the - fullness filter. Empty health/suit chargers are still hidden: both - `CWallHealth` (health brush) and `CNewRecharge` (suit charger) expose - `m_iJuice`, but at different offsets because their bases differ - (`CBaseToggle` vs `CBaseAnimating`). `datamap_offsets` collects every match; - the suit offset is `m_iMaxJuice - 20` and the health offset is the other - `m_iJuice` match (0x47c / 0x3c8 in this build). `server_item_juice` picks - by the server class name. 0 removes the tag; -1 (unreadable) fails open. + fullness filter. Empty **suit** chargers are hidden (`CNewRecharge` + `m_iJuice` at 0x47c, derived from `m_iMaxJuice - 20`; 0 = depleted, -1 + unreadable fails open). Health chargers are currently left visible: their + candidate `m_iJuice` offset (0x3c8 for `CWallHealth`/`CNewWallHealth`) read 0 + on a full charger, so the empty-hide is not trusted yet. + `server_item_juice` logs up to eight `juice:` lines with the class and both + candidate values to finish diagnosing this. Item classification also falls + back to the server class name when the client class is generic, and the + health branch accepts `healthcharger` (maps use `item_healthcharger`, + i.e. `CNewWallHealth`, not the `func_healthcharger` brush). - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, world weapon pickups, the two supply-crate models and generic items. - Each tag is a colored marker with a name and optional distance, filtered by diff --git a/src/esp.cpp b/src/esp.cpp index c358b2f..3785107 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -2619,7 +2619,8 @@ static int classify_item(const char *model, const char *cls, char *label, return IK_WEAPON; if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") || - item_icontains(cls, "wallhealth")) { + item_icontains(cls, "wallhealth") || + item_icontains(cls, "healthcharger")) { snprintf(label, outn, "Health"); return IK_HEALTH; } @@ -2668,6 +2669,13 @@ static bool item_is_charger(const char *model, const char *cls) { item_icontains(cls, "recharge"); } +static bool item_is_suit_charger(const char *model, const char *cls) { + return item_icontains(model, "suit_charger") || + item_icontains(model, "suitcharger") || + item_icontains(cls, "recharge") || + item_icontains(cls, "suitcharger"); +} + static void items_collect(int local_idx) { static double last_scan = 0.0; double now = esp_now(); @@ -2781,6 +2789,10 @@ static void items_collect(int local_idx) { if (!net) continue; const char *cls = entity_class_name(net, nullptr); + const char *scls = nullptr; + if (g_iface.engineserver && + (!cls || !*cls || strcmp(cls, "CBaseEntity") == 0)) + scls = server_entity_classname(idx); 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(). @@ -2792,13 +2804,24 @@ static void items_collect(int local_idx) { } char label[128]; int kind = classify_item(model, cls, label, sizeof(label)); + if (kind < 0 && scls) + kind = classify_item(model, scls, label, sizeof(label)); if (kind < 0) continue; // Health/suit chargers are dispensers that stay useful after a refill, but - // an empty one cannot give anything. m_iJuice is 0 when depleted (until it - // recharges); only hide the tag once the server value is readable. - if (item_is_charger(model, cls) && server_item_juice(idx) == 0) - continue; + // an empty suit charger cannot give anything. The health charger's + // m_iJuice offset is still being verified (it read 0 on a full charger), + // so it is left visible for now; server_item_juice logs both candidate + // values for diagnosis. + bool is_charger = item_is_charger(model, cls) || + (scls && item_is_charger(model, scls)); + bool is_suit = item_is_suit_charger(model, cls) || + (scls && item_is_suit_charger(model, scls)); + if (is_charger) { + int juice = server_item_juice(idx); + if (is_suit && juice == 0) + continue; + } Vector origin{*(float *)((uintptr_t)ent + g_net.origin), *(float *)((uintptr_t)ent + g_net.origin + 4), diff --git a/src/server.cpp b/src/server.cpp index cffc24f..b1661ac 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -546,7 +546,23 @@ int server_item_juice(int idx) { server_entity_health(idx, nullptr, nullptr, &base); if (!base) return -1; - return *(int *)((uintptr_t)base + off); + int val = *(int *)((uintptr_t)base + off); + static int juice_logs = 0; + if (juice_logs < 8 && cls && + (strstr(cls, "WallHealth") || strstr(cls, "Recharge") || + strstr(cls, "healthcharger") || strstr(cls, "suitcharger"))) { + juice_logs++; + int hv = g_srv_juice_health >= 0 + ? *(int *)((uintptr_t)base + g_srv_juice_health) + : -999; + int sv = g_srv_juice_suit >= 0 + ? *(int *)((uintptr_t)base + g_srv_juice_suit) + : -999; + logf("juice: idx=%d cls=%s chosen=%d health_off=%d val=%d " + "suit_off=%d val=%d", + idx, cls, off, g_srv_juice_health, hv, g_srv_juice_suit, sv); + } + return val; } // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap -- cgit v1.2.3