diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 17:41:53 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 17:41:53 +0100 |
| commit | f4616119c64ddcaa14cf10727e7aba4a3e64910f (patch) | |
| tree | 2cb68ab8dfde040a6fd1baa0e77039fba32019a4 | |
| parent | Items/projectiles: ammo diagnostics, dispensers, launched RPG (diff) | |
| download | hl2h-f4616119c64ddcaa14cf10727e7aba4a3e64910f.tar.gz hl2h-f4616119c64ddcaa14cf10727e7aba4a3e64910f.zip | |
Items: skip the CNetworkArray length header for m_iAmmo/m_hMyWeapons
The datamap offset points at the array length header, so every ammo count was
read one type early. That made the RPG slot read the buckshot count (30) and
permanently exceed the 3-rocket cap, which is why RPG pickups never showed and
grenades/AR2/weapon ammo never hid. Read the elements 4 bytes after the field
offset.
| -rw-r--r-- | CONTEXT.md | 6 | ||||
| -rw-r--r-- | src/server.cpp | 9 |
2 files changed, 12 insertions, 3 deletions
| @@ -619,6 +619,12 @@ vocabulary / decisions. | |||
| 619 | `CBaseCombatCharacter::m_iAmmo` array) is compared with the `sk_max_*` cvar | 619 | `CBaseCombatCharacter::m_iAmmo` array) is compared with the `sk_max_*` cvar |
| 620 | (stock caps as fallback, e.g. RPG rockets = 3). Full ammo hides the pickup; | 620 | (stock caps as fallback, e.g. RPG rockets = 3). Full ammo hides the pickup; |
| 621 | if either value cannot be read the item stays visible. | 621 | if either value cannot be read the item stays visible. |
| 622 | - The datamap offset for `m_iAmmo`/`m_hMyWeapons` points at the | ||
| 623 | `CNetworkArray` length header; the actual elements start **4 bytes later**. | ||
| 624 | Reading at the raw offset shifted every count by one type (e.g. RPG read the | ||
| 625 | buckshot count 30 against a max of 3, permanently hiding rockets). | ||
| 626 | - Chargers and ammo/supply crates are dispensers and are exempt from the | ||
| 627 | fullness filter. | ||
| 622 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, | 628 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, |
| 623 | world weapon pickups, the two supply-crate models and generic items. | 629 | world weapon pickups, the two supply-crate models and generic items. |
| 624 | - Each tag is a colored marker with a name and optional distance, filtered by | 630 | - Each tag is a colored marker with a name and optional distance, filtered by |
diff --git a/src/server.cpp b/src/server.cpp index af0a8ff..09d198a 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -482,7 +482,9 @@ float server_grenade_radius(int idx) { | |||
| 482 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); | 482 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); |
| 483 | } | 483 | } |
| 484 | 484 | ||
| 485 | // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). | 485 | // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap |
| 486 | // offset points at the CNetworkArray length header; the elements start 4 bytes | ||
| 487 | // later (confirmed by the shifted current/max table). | ||
| 486 | int server_player_ammo(int idx, int type) { | 488 | int server_player_ammo(int idx, int type) { |
| 487 | if (!g_srv_ready || g_srv_ammo < 0 || type < 0 || type >= 32) | 489 | if (!g_srv_ready || g_srv_ammo < 0 || type < 0 || type >= 32) |
| 488 | return -1; | 490 | return -1; |
| @@ -490,10 +492,11 @@ int server_player_ammo(int idx, int type) { | |||
| 490 | server_entity_health(idx, nullptr, nullptr, &base); | 492 | server_entity_health(idx, nullptr, nullptr, &base); |
| 491 | if (!base) | 493 | if (!base) |
| 492 | return -1; | 494 | return -1; |
| 493 | return *(int *)((uintptr_t)base + g_srv_ammo + type * 4); | 495 | return *(int *)((uintptr_t)base + g_srv_ammo + 4 + type * 4); |
| 494 | } | 496 | } |
| 495 | 497 | ||
| 496 | // Server-side m_hMyWeapons[48] handle for one inventory slot (0 if empty). | 498 | // Server-side m_hMyWeapons[48] handle for one inventory slot (0 if empty). |
| 499 | // Same 4-byte CNetworkArray header as m_iAmmo. | ||
| 497 | uint32_t server_player_weapon(int idx, int slot) { | 500 | uint32_t server_player_weapon(int idx, int slot) { |
| 498 | if (!g_srv_ready || g_srv_myweapons < 0 || slot < 0 || slot >= 48) | 501 | if (!g_srv_ready || g_srv_myweapons < 0 || slot < 0 || slot >= 48) |
| 499 | return 0; | 502 | return 0; |
| @@ -501,7 +504,7 @@ uint32_t server_player_weapon(int idx, int slot) { | |||
| 501 | server_entity_health(idx, nullptr, nullptr, &base); | 504 | server_entity_health(idx, nullptr, nullptr, &base); |
| 502 | if (!base) | 505 | if (!base) |
| 503 | return 0; | 506 | return 0; |
| 504 | return *(uint32_t *)((uintptr_t)base + g_srv_myweapons + slot * 4); | 507 | return *(uint32_t *)((uintptr_t)base + g_srv_myweapons + 4 + slot * 4); |
| 505 | } | 508 | } |
| 506 | 509 | ||
| 507 | // Entity index of the player's active weapon from the authoritative server | 510 | // Entity index of the player's active weapon from the authoritative server |
