diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-05 18:09:43 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-05 18:09:43 +0100 |
| commit | a5d732920bd56c96e85db1ce2073bebf53e6b1ea (patch) | |
| tree | 2692a1f19f5b6cc33d015091f3a176062ea9bcf3 | |
| parent | Grenade trajectory: actual grenade state, full-length path, rounder rings (diff) | |
| download | hl2h-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.md | 17 | ||||
| -rw-r--r-- | src/esp.cpp | 33 | ||||
| -rw-r--r-- | src/server.cpp | 18 |
3 files changed, 55 insertions, 13 deletions
| @@ -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 | ||
| 2672 | static 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 | |||
| 2671 | static void items_collect(int local_idx) { | 2679 | static 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 |
