diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 17:25:31 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 17:25:31 +0100 |
| commit | 710e8cc59557bc95ed74af8726ea13012fde50c3 (patch) | |
| tree | f1fd242c12d2ce5a939fbf1d3828e3546bc8f1cf | |
| parent | Netvars: catch array element props and log the walk (diff) | |
| download | hl2h-710e8cc59557bc95ed74af8726ea13012fde50c3.tar.gz hl2h-710e8cc59557bc95ed74af8726ea13012fde50c3.zip | |
Items: read ammo and inventory from the server arrays
The client m_iAmmo/m_hMyWeapons recv props still do not resolve, but the
listen server has the same CBaseCombatCharacter arrays in its datamap. Resolve
m_iAmmo and m_hMyWeapons there:
- reserve ammo comes from server_player_ammo() when the client array is
missing, so full-ammo filtering works (RPG rockets at 3);
- the owned-weapon mask is filled from the server m_hMyWeapons[48] handles,
so fallen weapons count as ammo before the player equips them.
Widen the datamap field-size filter to 512 for the 192-byte handle array.
| -rw-r--r-- | CONTEXT.md | 15 | ||||
| -rw-r--r-- | src/esp.cpp | 33 | ||||
| -rw-r--r-- | src/hl2h.h | 2 | ||||
| -rw-r--r-- | src/server.cpp | 35 |
4 files changed, 73 insertions, 12 deletions
| @@ -610,14 +610,15 @@ vocabulary / decisions. | |||
| 610 | - A world weapon whose type is already in the local player's inventory is | 610 | - A world weapon whose type is already in the local player's inventory is |
| 611 | reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Owner handles are | 611 | reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Owner handles are |
| 612 | unreliable on this client (`m_hOwnerEntity`/`m_hOwner` resolve to -1), so | 612 | unreliable on this client (`m_hOwnerEntity`/`m_hOwner` resolve to -1), so |
| 613 | the inventory is read directly from the local `m_hMyWeapons[48]` handle | 613 | the inventory is read from the authoritative server |
| 614 | array (and remembered active types as a fallback). NPC-held weapons are | 614 | `CBaseCombatCharacter::m_hMyWeapons[48]` array (client `m_hMyWeapons` and the |
| 615 | therefore ignored. Weapons the player does not own stay under the Weapons | 615 | remembered active weapon are fallbacks). NPC-held weapons are ignored. |
| 616 | category. | 616 | Weapons the player does not own stay under the Weapons category. |
| 617 | - Ammo pickups also honour "Only if usable": the reserve count from | 617 | - Ammo pickups also honour "Only if usable": the reserve count from |
| 618 | `m_iAmmo[type]` is compared with the `sk_max_*` cvar (stock caps as fallback, | 618 | `m_iAmmo[type]` (client array when resolved, otherwise the server |
| 619 | e.g. RPG rockets = 3). Full ammo hides the pickup; if either value cannot | 619 | `CBaseCombatCharacter::m_iAmmo` array) is compared with the `sk_max_*` cvar |
| 620 | be read the item stays visible. | 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 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, | 622 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, |
| 622 | world weapon pickups, the two supply-crate models and generic items. | 623 | world weapon pickups, the two supply-crate models and generic items. |
| 623 | - Each tag is a colored marker with a name and optional distance, filtered by | 624 | - 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 1d4a6a5..b4d3eee 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -2396,9 +2396,14 @@ static int ammo_max(int type) { | |||
| 2396 | } | 2396 | } |
| 2397 | 2397 | ||
| 2398 | static int local_ammo(void *local_ent, int type) { | 2398 | static int local_ammo(void *local_ent, int type) { |
| 2399 | if (!local_ent || g_net.ammo < 0 || type < 0 || type >= 32) | 2399 | if (type < 0 || type >= 32) |
| 2400 | return -1; | 2400 | return -1; |
| 2401 | return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4); | 2401 | if (local_ent && g_net.ammo >= 0) |
| 2402 | return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4); | ||
| 2403 | // Client array offset may not resolve; the listen-server player has the | ||
| 2404 | // same array in its datamap. | ||
| 2405 | int local = engine_get_local_player(); | ||
| 2406 | return local > 0 ? server_player_ammo(local, type) : -1; | ||
| 2402 | } | 2407 | } |
| 2403 | 2408 | ||
| 2404 | static int classify_item(const char *model, const char *cls, char *label, | 2409 | static int classify_item(const char *model, const char *cls, char *label, |
| @@ -2627,6 +2632,30 @@ static void items_collect(int local_idx) { | |||
| 2627 | if (wt > 0) | 2632 | if (wt > 0) |
| 2628 | owned_mask |= 1u << wt; | 2633 | owned_mask |= 1u << wt; |
| 2629 | } | 2634 | } |
| 2635 | // Server-side inventory, independent of the client recv-table arrays. | ||
| 2636 | if (g_iface.engineserver) { | ||
| 2637 | for (int slot = 0; slot < 48; slot++) { | ||
| 2638 | uint32_t h = server_player_weapon(local_idx, slot); | ||
| 2639 | if (!item_handle_valid(h)) | ||
| 2640 | continue; | ||
| 2641 | int wi = (int)(h & 0x7FF); | ||
| 2642 | if (wi <= 0 || wi > 4096) | ||
| 2643 | continue; | ||
| 2644 | void *wep = entitylist_get(wi); | ||
| 2645 | void *net = entity_networkable(wi); | ||
| 2646 | const char *wc = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 2647 | const char *wm = wep ? entity_model_name(wep) : nullptr; | ||
| 2648 | if (!wm) { | ||
| 2649 | void *sb = nullptr; | ||
| 2650 | server_entity_health(wi, nullptr, nullptr, &sb); | ||
| 2651 | if (sb) | ||
| 2652 | wm = model_name_from_index(server_model_index(sb)); | ||
| 2653 | } | ||
| 2654 | int wt = weapon_type_id(wm, wc); | ||
| 2655 | if (wt > 0) | ||
| 2656 | owned_mask |= 1u << wt; | ||
| 2657 | } | ||
| 2658 | } | ||
| 2630 | bool owned_weapon[WT_COUNT] = {}; | 2659 | bool owned_weapon[WT_COUNT] = {}; |
| 2631 | for (int t = 1; t < WT_COUNT; t++) | 2660 | for (int t = 1; t < WT_COUNT; t++) |
| 2632 | owned_weapon[t] = (owned_mask & (1u << t)) != 0; | 2661 | owned_weapon[t] = (owned_mask & (1u << t)) != 0; |
| @@ -297,6 +297,8 @@ bool server_npc_has_follow_target(int idx); | |||
| 297 | int server_npc_state(int idx); | 297 | int server_npc_state(int idx); |
| 298 | int server_npc_takedamage(int idx); | 298 | int server_npc_takedamage(int idx); |
| 299 | int server_player_armor(int idx); | 299 | int server_player_armor(int idx); |
| 300 | int server_player_ammo(int idx, int type); | ||
| 301 | uint32_t server_player_weapon(int idx, int slot); | ||
| 300 | float server_grenade_detonate(int idx); | 302 | float server_grenade_detonate(int idx); |
| 301 | float server_grenade_radius(int idx); | 303 | float server_grenade_radius(int idx); |
| 302 | int server_entity_count(); | 304 | int server_entity_count(); |
diff --git a/src/server.cpp b/src/server.cpp index 1313f00..af0a8ff 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -58,6 +58,8 @@ static int g_srv_mapname = -1; | |||
| 58 | static int g_srv_armor = -1; | 58 | static int g_srv_armor = -1; |
| 59 | static int g_srv_detonate = -1; | 59 | static int g_srv_detonate = -1; |
| 60 | static int g_srv_grenade_radius = -1; | 60 | static int g_srv_grenade_radius = -1; |
| 61 | static int g_srv_ammo = -1; | ||
| 62 | static int g_srv_myweapons = -1; | ||
| 61 | static bool g_srv_ready = false; | 63 | static bool g_srv_ready = false; |
| 62 | static bool g_srv_checked = false; | 64 | static bool g_srv_checked = false; |
| 63 | 65 | ||
| @@ -97,7 +99,7 @@ static int datamap_offset(const char *name, int want_type, int min_off = 0x40, | |||
| 97 | continue; | 99 | continue; |
| 98 | if (foff < min_off || foff > max_off) | 100 | if (foff < min_off || foff > max_off) |
| 99 | continue; | 101 | continue; |
| 100 | if (fsize < 1 || fsize > 128) | 102 | if (fsize < 1 || fsize > 512) |
| 101 | continue; | 103 | continue; |
| 102 | // DEFINE_KEYFIELD entries have an external name ("health"); plain | 104 | // DEFINE_KEYFIELD entries have an external name ("health"); plain |
| 103 | // DEFINE_FIELD entries have none. Either way it must be in server.so. | 105 | // DEFINE_FIELD entries have none. Either way it must be in server.so. |
| @@ -160,17 +162,22 @@ bool resolve_server_netvars() { | |||
| 160 | // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). | 162 | // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). |
| 161 | g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); | 163 | g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); |
| 162 | g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); | 164 | g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); |
| 165 | // CBaseCombatCharacter arrays: ammo counts and the weapon handle list. | ||
| 166 | g_srv_ammo = datamap_offset("m_iAmmo", FIELD_INTEGER, 0x40, 0x4000); | ||
| 167 | g_srv_myweapons = datamap_offset("m_hMyWeapons", FIELD_EHANDLE); | ||
| 163 | g_srv_ready = g_srv_health > 0; | 168 | g_srv_ready = g_srv_health > 0; |
| 164 | if (g_srv_ready) { | 169 | if (g_srv_ready) { |
| 165 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " | 170 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " |
| 166 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " | 171 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " |
| 167 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " | 172 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " |
| 168 | "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d", | 173 | "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d " |
| 174 | "ammo=%d myweapons=%d", | ||
| 169 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, | 175 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, |
| 170 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, | 176 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, |
| 171 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, | 177 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, |
| 172 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, | 178 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, |
| 173 | g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius); | 179 | g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius, |
| 180 | g_srv_ammo, g_srv_myweapons); | ||
| 174 | fail_logged = false; | 181 | fail_logged = false; |
| 175 | } else if (!fail_logged) { | 182 | } else if (!fail_logged) { |
| 176 | fail_logged = true; | 183 | fail_logged = true; |
| @@ -475,6 +482,28 @@ float server_grenade_radius(int idx) { | |||
| 475 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); | 482 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); |
| 476 | } | 483 | } |
| 477 | 484 | ||
| 485 | // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). | ||
| 486 | int server_player_ammo(int idx, int type) { | ||
| 487 | if (!g_srv_ready || g_srv_ammo < 0 || type < 0 || type >= 32) | ||
| 488 | return -1; | ||
| 489 | void *base = nullptr; | ||
| 490 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 491 | if (!base) | ||
| 492 | return -1; | ||
| 493 | return *(int *)((uintptr_t)base + g_srv_ammo + type * 4); | ||
| 494 | } | ||
| 495 | |||
| 496 | // Server-side m_hMyWeapons[48] handle for one inventory slot (0 if empty). | ||
| 497 | uint32_t server_player_weapon(int idx, int slot) { | ||
| 498 | if (!g_srv_ready || g_srv_myweapons < 0 || slot < 0 || slot >= 48) | ||
| 499 | return 0; | ||
| 500 | void *base = nullptr; | ||
| 501 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 502 | if (!base) | ||
| 503 | return 0; | ||
| 504 | return *(uint32_t *)((uintptr_t)base + g_srv_myweapons + slot * 4); | ||
| 505 | } | ||
| 506 | |||
| 478 | // Entity index of the player's active weapon from the authoritative server | 507 | // Entity index of the player's active weapon from the authoritative server |
| 479 | // entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. | 508 | // entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. |
| 480 | int server_active_weapon(int idx) { | 509 | int server_active_weapon(int idx) { |
