From 1267387f58d41d1421e3c2122a6930de0b279d12 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 15:55:08 +0100 Subject: Items: fallen owned weapons count as ammo; fuse diagnostics - World weapons of a type already in the local player's inventory are retagged as ammo ("SMG ammo", ...). Inventory scan only counts weapons owned by the local player index, so NPC weapons do not trigger it. - Projectile fuse: log det/cur/base/raw server value, and fall back to a 3-second first-sighting countdown when m_flDetonateTime or engine time is unavailable. --- src/esp.cpp | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 6 deletions(-) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index c956d1b..e273bf6 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -2049,6 +2049,21 @@ static bool item_weapon_is_owned(void *ent) { return false; } +// Entity index of the weapon's owner, or -1. Used to distinguish the local +// player's inventory from NPC weapons. +static int item_weapon_owner_idx(void *ent) { + if (!ent) + return -1; + uint32_t h = 0; + if (g_net.owner >= 0) + h = *(uint32_t *)((uintptr_t)ent + g_net.owner); + if (!item_handle_valid(h) && g_net.weapon_owner >= 0) + h = *(uint32_t *)((uintptr_t)ent + g_net.weapon_owner); + if (!item_handle_valid(h)) + return -1; + return (int)(h & 0x7FF); +} + static int local_active_weapon_idx(int local_idx) { if (local_idx <= 0 || g_net.activeweapon < 0) return -1; @@ -2104,6 +2119,44 @@ static int classify_weapon(const char *model, const char *cls, char *label, return 1; } +// Weapon identity for the "owned weapon -> fallen weapon is ammo" rule. +enum { + WT_NONE = 0, + WT_PISTOL, + WT_357, + WT_SMG, + WT_AR2, + WT_SHOTGUN, + WT_CROSSBOW, + WT_RPG, + WT_GRENADE, + WT_SNIPER, + WT_COUNT +}; + +static int weapon_type_id(const char *model, const char *cls) { + if (item_icontains(model, "pistol") || item_icontains(cls, "pistol")) + return WT_PISTOL; + if (item_icontains(model, "357") || item_icontains(cls, "357")) + return WT_357; + if (item_icontains(model, "smg") || item_icontains(cls, "smg")) + return WT_SMG; + if (item_icontains(model, "ar2") || item_icontains(model, "irifle") || + item_icontains(cls, "ar2") || item_icontains(cls, "irifle")) + return WT_AR2; + if (item_icontains(model, "shotgun") || item_icontains(cls, "shotgun")) + return WT_SHOTGUN; + if (item_icontains(model, "crossbow") || item_icontains(cls, "crossbow")) + return WT_CROSSBOW; + if (item_icontains(model, "rocket") || item_icontains(cls, "rpg")) + return WT_RPG; + if (item_icontains(model, "grenade") || item_icontains(cls, "frag")) + return WT_GRENADE; + if (item_icontains(model, "sniper") || item_icontains(cls, "sniper")) + return WT_SNIPER; + return WT_NONE; +} + static int classify_item(const char *model, const char *cls, char *label, size_t outn) { if (!model && !cls) @@ -2291,6 +2344,23 @@ static void items_collect(int local_idx) { if (hi < 0 || hi > 4096) hi = 4096; int active_idx = local_active_weapon_idx(local_idx); + + // Which weapon types the local player already carries. A fallen weapon of + // the same type is then just an ammo pickup. + bool owned_weapon[WT_COUNT] = {}; + for (int idx = 1; idx <= hi; idx++) { + if (idx == local_idx) + continue; + void *ent = entitylist_get(idx); + if (!ent || item_weapon_owner_idx(ent) != local_idx) + continue; + void *net = entity_networkable(idx); + const char *cls = net ? entity_class_name(net, nullptr) : nullptr; + const char *model = entity_model_name(ent); + int wt = weapon_type_id(model, cls); + if (wt > 0) + owned_weapon[wt] = true; + } static bool cats_logged = false; if (!cats_logged) { cats_logged = true; @@ -2347,6 +2417,13 @@ static void items_collect(int local_idx) { if (kind == IK_WEAPON) { if (idx == active_idx || item_weapon_is_owned(ent)) continue; + int wt = weapon_type_id(model, cls); + if (wt > 0 && owned_weapon[wt]) { + char base_label[160]; + snprintf(base_label, sizeof(base_label), "%s", label); + snprintf(label, sizeof(label), "%.100s ammo", base_label); + kind = IK_AMMO; + } } // Distance applies to every tag; 0 means unlimited. if (g_item_max_dist > 0 && dist > (float)g_item_max_dist) @@ -2511,6 +2588,23 @@ static const char *proj_name(int kind) { } } +// Fallback fuse clock when the server detonate time cannot be read. The stock +// frag fuse is 3 s from the first sighting. +static double proj_first_seen(int idx, double now) { + static int idxs[32]; + static double first[32]; + static int n = 0; + for (int i = 0; i < n; i++) + if (idxs[i] == idx) + return first[i]; + if (n < 32) { + idxs[n] = idx; + first[n] = now; + n++; + } + return now; +} + static void projs_collect(int local_idx) { static double last_scan = 0.0; double now = esp_now(); @@ -2578,19 +2672,24 @@ static void projs_collect(int local_idx) { p.radius = kind == PK_RPG ? 200.0f : kind == PK_BALL ? 150.0f : 300.0f; // m_flDetonateTime is server-only (not in the send table). p.fuse = -1.0f; + float det = -1.0f; if (kind != PK_RPG) { - float det = server_grenade_detonate(idx); + det = server_grenade_detonate(idx); if (det > 0.0f && curtime > 0.0f) p.fuse = det - curtime; - if (!(p.fuse > -2.0f && p.fuse < 30.0f)) - p.fuse = -1.0f; + if (!(p.fuse > -2.0f && p.fuse < 30.0f)) { + p.fuse = 3.0f - (float)(now - proj_first_seen(idx, now)); + if (!(p.fuse > -2.0f && p.fuse < 30.0f)) + p.fuse = -1.0f; + } } static int logs = 0; if (logs < 8) { logs++; - logf("projectile: idx=%d cls=%s model=%s kind=%s fuse=%.2f " - "radius=%.0f", - idx, cls ? cls : "?", model, proj_name(kind), p.fuse, p.radius); + logf("projectile: idx=%d cls=%s model=%s kind=%s det=%.2f cur=%.2f " + "fuse=%.2f radius=%.0f", + idx, cls ? cls : "?", model, proj_name(kind), det, curtime, p.fuse, + p.radius); } } } -- cgit v1.2.3