diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-05 20:32:59 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-05 20:32:59 +0100 |
| commit | 57b53d579f32f094c1cdefad62a6224baf270e0d (patch) | |
| tree | 67b335b8ac0933078628c0e7b746a4bb253533fd | |
| parent | Items: keep health chargers visible, add charger diagnostics (diff) | |
| download | hl2h-57b53d579f32f094c1cdefad62a6224baf270e0d.tar.gz hl2h-57b53d579f32f094c1cdefad62a6224baf270e0d.zip | |
Items: server ammo counts and frag-grenade type
- local_ammo now prefers the authoritative server m_iAmmo array (header skip)
and only falls back to the client netvar array. The client offset can be off
by an element, which made "full" checks flicker as other ammo was spent.
- Frag grenade pickups were falling through to a generic "Ammo" label with
ammo_type=-1, so the usable filter never ran. w_grenade/w_frag/
items/grenade/frag_grenade are now "Frag grenade" and map to m_iAmmo[11]
(sk_max_grenade, stock 5); AR2/SMG1 grenade variants map to m_iAmmo[8].
| -rw-r--r-- | CONTEXT.md | 17 | ||||
| -rw-r--r-- | src/esp.cpp | 31 |
2 files changed, 37 insertions, 11 deletions
| @@ -692,11 +692,18 @@ vocabulary / decisions. | |||
| 692 | `CBaseCombatCharacter::m_hMyWeapons[48]` array (client `m_hMyWeapons` and the | 692 | `CBaseCombatCharacter::m_hMyWeapons[48]` array (client `m_hMyWeapons` and the |
| 693 | remembered active weapon are fallbacks). NPC-held weapons are ignored. | 693 | remembered active weapon are fallbacks). NPC-held weapons are ignored. |
| 694 | Weapons the player does not own stay under the Weapons category. | 694 | Weapons the player does not own stay under the Weapons category. |
| 695 | - Ammo pickups also honour "Only if usable": the reserve count from | 695 | - Ammo pickups also honour "Only if usable": the reserve count from the |
| 696 | `m_iAmmo[type]` (client array when resolved, otherwise the server | 696 | authoritative server `CBaseCombatCharacter::m_iAmmo` array is compared with |
| 697 | `CBaseCombatCharacter::m_iAmmo` array) is compared with the `sk_max_*` cvar | 697 | the `sk_max_*` cvar (stock caps as fallback, e.g. RPG rockets = 3); the |
| 698 | (stock caps as fallback, e.g. RPG rockets = 3). Full ammo hides the pickup; | 698 | client array is only a fallback. Full ammo hides the pickup; if either value |
| 699 | if either value cannot be read the item stays visible. | 699 | cannot be read the item stays visible. Preferring the server array fixed |
| 700 | "full" counts flickering as other ammo was spent — the client `m_iAmmo` | ||
| 701 | netvar offset can be off by an element. | ||
| 702 | - Frag grenade pickups were falling through to a generic "Ammo" label with | ||
| 703 | `ammo_type = -1`, so the usable filter never applied and they always showed. | ||
| 704 | `models/weapons/w_grenade`, `w_frag`, `items/grenade` and `frag_grenade` are | ||
| 705 | now labelled "Frag grenade" and mapped to `m_iAmmo[11]` (`sk_max_grenade`, | ||
| 706 | stock 5). AR2/SMG1 grenade variants map to `m_iAmmo[8]` (stock 3). | ||
| 700 | - The datamap offset for `m_iAmmo`/`m_hMyWeapons` points at the | 707 | - The datamap offset for `m_iAmmo`/`m_hMyWeapons` points at the |
| 701 | `CNetworkArray` length header; the actual elements start **4 bytes later**. | 708 | `CNetworkArray` length header; the actual elements start **4 bytes later**. |
| 702 | Reading at the raw offset shifted every count by one type (e.g. RPG read the | 709 | Reading at the raw offset shifted every count by one type (e.g. RPG read the |
diff --git a/src/esp.cpp b/src/esp.cpp index 3785107..7ddc86b 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -2419,10 +2419,15 @@ static int item_ammo_type(const char *model) { | |||
| 2419 | item_icontains(model, "w_rocket_launcher")) | 2419 | item_icontains(model, "w_rocket_launcher")) |
| 2420 | return 7; | 2420 | return 7; |
| 2421 | if (item_icontains(model, "ar2_grenade") || | 2421 | if (item_icontains(model, "ar2_grenade") || |
| 2422 | item_icontains(model, "ammocrate_smg2")) | 2422 | item_icontains(model, "ammocrate_smg2") || |
| 2423 | item_icontains(model, "smg1_grenade") || | ||
| 2424 | item_icontains(model, "smg_grenade")) | ||
| 2423 | return 8; | 2425 | return 8; |
| 2424 | if (item_icontains(model, "ammocrate_grenade") || | 2426 | if (item_icontains(model, "ammocrate_grenade") || |
| 2425 | item_icontains(model, "w_grenade") || item_icontains(model, "w_frag")) | 2427 | item_icontains(model, "w_grenade") || |
| 2428 | item_icontains(model, "w_frag") || | ||
| 2429 | item_icontains(model, "frag_grenade") || | ||
| 2430 | item_icontains(model, "items/grenade")) | ||
| 2426 | return 11; | 2431 | return 11; |
| 2427 | if (item_icontains(model, "w_smg1") || item_icontains(model, "w_smg2")) | 2432 | if (item_icontains(model, "w_smg1") || item_icontains(model, "w_smg2")) |
| 2428 | return 3; | 2433 | return 3; |
| @@ -2479,12 +2484,19 @@ static int ammo_max(int type) { | |||
| 2479 | static int local_ammo(void *local_ent, int type) { | 2484 | static int local_ammo(void *local_ent, int type) { |
| 2480 | if (type < 0 || type >= 32) | 2485 | if (type < 0 || type >= 32) |
| 2481 | return -1; | 2486 | return -1; |
| 2487 | // Prefer the authoritative server array; the client m_iAmmo netvar offset | ||
| 2488 | // can be off by an element, which made "full" counts flicker as other ammo | ||
| 2489 | // was spent. server_player_ammo already skips the CNetworkArray header. | ||
| 2490 | int local = engine_get_local_player(); | ||
| 2491 | if (local > 0 && g_iface.engineserver) { | ||
| 2492 | resolve_server_netvars(); | ||
| 2493 | int s = server_player_ammo(local, type); | ||
| 2494 | if (s >= 0) | ||
| 2495 | return s; | ||
| 2496 | } | ||
| 2482 | if (local_ent && g_net.ammo >= 0) | 2497 | if (local_ent && g_net.ammo >= 0) |
| 2483 | return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4); | 2498 | return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4); |
| 2484 | // Client array offset may not resolve; the listen-server player has the | 2499 | return -1; |
| 2485 | // same array in its datamap. | ||
| 2486 | int local = engine_get_local_player(); | ||
| 2487 | return local > 0 ? server_player_ammo(local, type) : -1; | ||
| 2488 | } | 2500 | } |
| 2489 | 2501 | ||
| 2490 | static int classify_item(const char *model, const char *cls, char *label, | 2502 | static int classify_item(const char *model, const char *cls, char *label, |
| @@ -2610,6 +2622,13 @@ static int classify_item(const char *model, const char *cls, char *label, | |||
| 2610 | snprintf(label, outn, "AR2 grenade"); | 2622 | snprintf(label, outn, "AR2 grenade"); |
| 2611 | return IK_AMMO; | 2623 | return IK_AMMO; |
| 2612 | } | 2624 | } |
| 2625 | if (item_icontains(model, "w_grenade") || | ||
| 2626 | item_icontains(model, "w_frag") || | ||
| 2627 | item_icontains(model, "frag_grenade") || | ||
| 2628 | item_icontains(model, "items/grenade")) { | ||
| 2629 | snprintf(label, outn, "Frag grenade"); | ||
| 2630 | return IK_AMMO; | ||
| 2631 | } | ||
| 2613 | if (item_icontains(model, "models/items/")) { | 2632 | if (item_icontains(model, "models/items/")) { |
| 2614 | snprintf(label, outn, "Ammo"); | 2633 | snprintf(label, outn, "Ammo"); |
| 2615 | return IK_AMMO; | 2634 | return IK_AMMO; |
