summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-05 20:32:59 +0100
committersalmonik <salmonik@tuta.io>2026-11-05 20:32:59 +0100
commit57b53d579f32f094c1cdefad62a6224baf270e0d (patch)
tree67b335b8ac0933078628c0e7b746a4bb253533fd
parentItems: keep health chargers visible, add charger diagnostics (diff)
downloadhl2h-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.md17
-rw-r--r--src/esp.cpp31
2 files changed, 37 insertions, 11 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 8ff833c..4508fe6 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -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) {
2479static int local_ammo(void *local_ent, int type) { 2484static 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
2490static int classify_item(const char *model, const char *cls, char *label, 2502static 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;