summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-05 18:09:43 +0100
committersalmonik <salmonik@tuta.io>2026-11-05 18:09:43 +0100
commita5d732920bd56c96e85db1ce2073bebf53e6b1ea (patch)
tree2692a1f19f5b6cc33d015091f3a176062ea9bcf3
parentGrenade trajectory: actual grenade state, full-length path, rounder rings (diff)
downloadhl2h-a5d732920bd56c96e85db1ce2073bebf53e6b1ea.tar.gz
hl2h-a5d732920bd56c96e85db1ce2073bebf53e6b1ea.zip
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.
-rw-r--r--CONTEXT.md17
-rw-r--r--src/esp.cpp33
-rw-r--r--src/server.cpp18
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.
702 Reading at the raw offset shifted every count by one type (e.g. RPG read the 702 Reading at the raw offset shifted every count by one type (e.g. RPG read the
703 buckshot count 30 against a max of 3, permanently hiding rockets). 703 buckshot count 30 against a max of 3, permanently hiding rockets).
704- Chargers and ammo/supply crates are dispensers and are exempt from the 704- Chargers and ammo/supply crates are dispensers and are exempt from the
705 fullness filter. Empty health/suit chargers are still hidden: both 705 fullness filter. Empty **suit** chargers are hidden (`CNewRecharge`
706 `CWallHealth` (health brush) and `CNewRecharge` (suit charger) expose 706 `m_iJuice` at 0x47c, derived from `m_iMaxJuice - 20`; 0 = depleted, -1
707 `m_iJuice`, but at different offsets because their bases differ 707 unreadable fails open). Health chargers are currently left visible: their
708 (`CBaseToggle` vs `CBaseAnimating`). `datamap_offsets` collects every match; 708 candidate `m_iJuice` offset (0x3c8 for `CWallHealth`/`CNewWallHealth`) read 0
709 the suit offset is `m_iMaxJuice - 20` and the health offset is the other 709 on a full charger, so the empty-hide is not trusted yet.
710 `m_iJuice` match (0x47c / 0x3c8 in this build). `server_item_juice` picks 710 `server_item_juice` logs up to eight `juice:` lines with the class and both
711 by the server class name. 0 removes the tag; -1 (unreadable) fails open. 711 candidate values to finish diagnosing this. Item classification also falls
712 back to the server class name when the client class is generic, and the
713 health branch accepts `healthcharger` (maps use `item_healthcharger`,
714 i.e. `CNewWallHealth`, not the `func_healthcharger` brush).
712- Scans the client entity list every 100 ms and tags health, ammo, armor/suit, 715- Scans the client entity list every 100 ms and tags health, ammo, armor/suit,
713 world weapon pickups, the two supply-crate models and generic items. 716 world weapon pickups, the two supply-crate models and generic items.
714- Each tag is a colored marker with a name and optional distance, filtered by 717- 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,
2619 return IK_WEAPON; 2619 return IK_WEAPON;
2620 2620
2621 if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") || 2621 if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") ||
2622 item_icontains(cls, "wallhealth")) { 2622 item_icontains(cls, "wallhealth") ||
2623 item_icontains(cls, "healthcharger")) {
2623 snprintf(label, outn, "Health"); 2624 snprintf(label, outn, "Health");
2624 return IK_HEALTH; 2625 return IK_HEALTH;
2625 } 2626 }
@@ -2668,6 +2669,13 @@ static bool item_is_charger(const char *model, const char *cls) {
2668 item_icontains(cls, "recharge"); 2669 item_icontains(cls, "recharge");
2669} 2670}
2670 2671
2672static bool item_is_suit_charger(const char *model, const char *cls) {
2673 return item_icontains(model, "suit_charger") ||
2674 item_icontains(model, "suitcharger") ||
2675 item_icontains(cls, "recharge") ||
2676 item_icontains(cls, "suitcharger");
2677}
2678
2671static void items_collect(int local_idx) { 2679static void items_collect(int local_idx) {
2672 static double last_scan = 0.0; 2680 static double last_scan = 0.0;
2673 double now = esp_now(); 2681 double now = esp_now();
@@ -2781,6 +2789,10 @@ static void items_collect(int local_idx) {
2781 if (!net) 2789 if (!net)
2782 continue; 2790 continue;
2783 const char *cls = entity_class_name(net, nullptr); 2791 const char *cls = entity_class_name(net, nullptr);
2792 const char *scls = nullptr;
2793 if (g_iface.engineserver &&
2794 (!cls || !*cls || strcmp(cls, "CBaseEntity") == 0))
2795 scls = server_entity_classname(idx);
2784 const char *model = entity_model_name(ent); 2796 const char *model = entity_model_name(ent);
2785 // CPhysicsProp's client m_nModelIndex is often 0 (e.g. item_item_crate); 2797 // CPhysicsProp's client m_nModelIndex is often 0 (e.g. item_item_crate);
2786 // fall back to the server entity's model index, which is set in Spawn(). 2798 // 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) {
2792 } 2804 }
2793 char label[128]; 2805 char label[128];
2794 int kind = classify_item(model, cls, label, sizeof(label)); 2806 int kind = classify_item(model, cls, label, sizeof(label));
2807 if (kind < 0 && scls)
2808 kind = classify_item(model, scls, label, sizeof(label));
2795 if (kind < 0) 2809 if (kind < 0)
2796 continue; 2810 continue;
2797 // Health/suit chargers are dispensers that stay useful after a refill, but 2811 // Health/suit chargers are dispensers that stay useful after a refill, but
2798 // an empty one cannot give anything. m_iJuice is 0 when depleted (until it 2812 // an empty suit charger cannot give anything. The health charger's
2799 // recharges); only hide the tag once the server value is readable. 2813 // m_iJuice offset is still being verified (it read 0 on a full charger),
2800 if (item_is_charger(model, cls) && server_item_juice(idx) == 0) 2814 // so it is left visible for now; server_item_juice logs both candidate
2801 continue; 2815 // values for diagnosis.
2816 bool is_charger = item_is_charger(model, cls) ||
2817 (scls && item_is_charger(model, scls));
2818 bool is_suit = item_is_suit_charger(model, cls) ||
2819 (scls && item_is_suit_charger(model, scls));
2820 if (is_charger) {
2821 int juice = server_item_juice(idx);
2822 if (is_suit && juice == 0)
2823 continue;
2824 }
2802 2825
2803 Vector origin{*(float *)((uintptr_t)ent + g_net.origin), 2826 Vector origin{*(float *)((uintptr_t)ent + g_net.origin),
2804 *(float *)((uintptr_t)ent + g_net.origin + 4), 2827 *(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) {
546 server_entity_health(idx, nullptr, nullptr, &base); 546 server_entity_health(idx, nullptr, nullptr, &base);
547 if (!base) 547 if (!base)
548 return -1; 548 return -1;
549 return *(int *)((uintptr_t)base + off); 549 int val = *(int *)((uintptr_t)base + off);
550 static int juice_logs = 0;
551 if (juice_logs < 8 && cls &&
552 (strstr(cls, "WallHealth") || strstr(cls, "Recharge") ||
553 strstr(cls, "healthcharger") || strstr(cls, "suitcharger"))) {
554 juice_logs++;
555 int hv = g_srv_juice_health >= 0
556 ? *(int *)((uintptr_t)base + g_srv_juice_health)
557 : -999;
558 int sv = g_srv_juice_suit >= 0
559 ? *(int *)((uintptr_t)base + g_srv_juice_suit)
560 : -999;
561 logf("juice: idx=%d cls=%s chosen=%d health_off=%d val=%d "
562 "suit_off=%d val=%d",
563 idx, cls, off, g_srv_juice_health, hv, g_srv_juice_suit, sv);
564 }
565 return val;
550} 566}
551 567
552// Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap 568// Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap