From 710e8cc59557bc95ed74af8726ea13012fde50c3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 17:25:31 +0100 Subject: 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. --- src/server.cpp | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'src/server.cpp') 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; static int g_srv_armor = -1; static int g_srv_detonate = -1; static int g_srv_grenade_radius = -1; +static int g_srv_ammo = -1; +static int g_srv_myweapons = -1; static bool g_srv_ready = false; static bool g_srv_checked = false; @@ -97,7 +99,7 @@ static int datamap_offset(const char *name, int want_type, int min_off = 0x40, continue; if (foff < min_off || foff > max_off) continue; - if (fsize < 1 || fsize > 128) + if (fsize < 1 || fsize > 512) continue; // DEFINE_KEYFIELD entries have an external name ("health"); plain // DEFINE_FIELD entries have none. Either way it must be in server.so. @@ -160,17 +162,22 @@ bool resolve_server_netvars() { // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); + // CBaseCombatCharacter arrays: ammo counts and the weapon handle list. + g_srv_ammo = datamap_offset("m_iAmmo", FIELD_INTEGER, 0x40, 0x4000); + g_srv_myweapons = datamap_offset("m_hMyWeapons", FIELD_EHANDLE); g_srv_ready = g_srv_health > 0; if (g_srv_ready) { logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " - "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d", + "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d " + "ammo=%d myweapons=%d", g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, - g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius); + g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius, + g_srv_ammo, g_srv_myweapons); fail_logged = false; } else if (!fail_logged) { fail_logged = true; @@ -475,6 +482,28 @@ float server_grenade_radius(int idx) { return *(float *)((uintptr_t)base + g_srv_grenade_radius); } +// Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). +int server_player_ammo(int idx, int type) { + if (!g_srv_ready || g_srv_ammo < 0 || type < 0 || type >= 32) + return -1; + void *base = nullptr; + server_entity_health(idx, nullptr, nullptr, &base); + if (!base) + return -1; + return *(int *)((uintptr_t)base + g_srv_ammo + type * 4); +} + +// Server-side m_hMyWeapons[48] handle for one inventory slot (0 if empty). +uint32_t server_player_weapon(int idx, int slot) { + if (!g_srv_ready || g_srv_myweapons < 0 || slot < 0 || slot >= 48) + return 0; + void *base = nullptr; + server_entity_health(idx, nullptr, nullptr, &base); + if (!base) + return 0; + return *(uint32_t *)((uintptr_t)base + g_srv_myweapons + slot * 4); +} + // Entity index of the player's active weapon from the authoritative server // entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. int server_active_weapon(int idx) { -- cgit v1.2.3