summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 15:55:08 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 15:55:08 +0100
commit1267387f58d41d1421e3c2122a6930de0b279d12 (patch)
tree1d4c5cbfdecb9aa85f16ff4f14bbc189241bf87a
parentAdd usable item filter, projectile ESP and gravity-gun melee (diff)
downloadhl2h-1267387f58d41d1421e3c2122a6930de0b279d12.tar.gz
hl2h-1267387f58d41d1421e3c2122a6930de0b279d12.zip
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.
-rw-r--r--CONTEXT.md10
-rw-r--r--src/esp.cpp111
-rw-r--r--src/server.cpp9
3 files changed, 122 insertions, 8 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 96a4660..d8fdb97 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -594,6 +594,11 @@ vocabulary / decisions.
594 is the server player's `m_iMaxHealth` (not a hardcoded 100); armor uses the 594 is the server player's `m_iMaxHealth` (not a hardcoded 100); armor uses the
595 HL2 `MAX_NORMAL_BATTERY` cap of 100, read from the server 595 HL2 `MAX_NORMAL_BATTERY` cap of 100, read from the server
596 `m_ArmorValue`. If the values cannot be read the filter fails open. 596 `m_ArmorValue`. If the values cannot be read the filter fails open.
597- A world weapon whose type is already in the local player's inventory is
598 reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Inventory types come
599 from client weapon entities whose owner handle resolves to the local player
600 index; NPC-held weapons are ignored. Weapons the player does not own stay
601 under the Weapons category.
597- Scans the client entity list every 100 ms and tags health, ammo, armor/suit, 602- Scans the client entity list every 100 ms and tags health, ammo, armor/suit,
598 world weapon pickups, the two supply-crate models and generic items. 603 world weapon pickups, the two supply-crate models and generic items.
599- Each tag is a colored marker with a name and optional distance, filtered by 604- Each tag is a colored marker with a name and optional distance, filtered by
@@ -621,7 +626,10 @@ vocabulary / decisions.
621 `CGrenadeFrag`-style class names or by an owner handle. 626 `CGrenadeFrag`-style class names or by an owner handle.
622- Fuse: `CBaseGrenade::m_flDetonateTime` is not networked, so it is read from 627- Fuse: `CBaseGrenade::m_flDetonateTime` is not networked, so it is read from
623 the server entity datamap (`FIELD_TIME`) and compared with 628 the server entity datamap (`FIELD_TIME`) and compared with
624 `IVEngineClient::Time()` (slot 14). RPG rockets have no fuse. 629 `IVEngineClient::Time()` (slot 14). RPG rockets have no fuse. If the server
630 value or the engine time is unavailable, a 3-second countdown from the first
631 sighting is used so the label still ticks. The first 8 projectiles log
632 `det=`, `cur=`, the server base and the raw fuse value for diagnosis.
625- Blast radius: networked client `m_DmgRadius` when available, server 633- Blast radius: networked client `m_DmgRadius` when available, server
626 `m_DmgRadius` (`FIELD_FLOAT`) as fallback, then 300/200/150/300 for 634 `m_DmgRadius` (`FIELD_FLOAT`) as fallback, then 300/200/150/300 for
627 frag/RPG/AR2/bomb. 635 frag/RPG/AR2/bomb.
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) {
2049 return false; 2049 return false;
2050} 2050}
2051 2051
2052// Entity index of the weapon's owner, or -1. Used to distinguish the local
2053// player's inventory from NPC weapons.
2054static int item_weapon_owner_idx(void *ent) {
2055 if (!ent)
2056 return -1;
2057 uint32_t h = 0;
2058 if (g_net.owner >= 0)
2059 h = *(uint32_t *)((uintptr_t)ent + g_net.owner);
2060 if (!item_handle_valid(h) && g_net.weapon_owner >= 0)
2061 h = *(uint32_t *)((uintptr_t)ent + g_net.weapon_owner);
2062 if (!item_handle_valid(h))
2063 return -1;
2064 return (int)(h & 0x7FF);
2065}
2066
2052static int local_active_weapon_idx(int local_idx) { 2067static int local_active_weapon_idx(int local_idx) {
2053 if (local_idx <= 0 || g_net.activeweapon < 0) 2068 if (local_idx <= 0 || g_net.activeweapon < 0)
2054 return -1; 2069 return -1;
@@ -2104,6 +2119,44 @@ static int classify_weapon(const char *model, const char *cls, char *label,
2104 return 1; 2119 return 1;
2105} 2120}
2106 2121
2122// Weapon identity for the "owned weapon -> fallen weapon is ammo" rule.
2123enum {
2124 WT_NONE = 0,
2125 WT_PISTOL,
2126 WT_357,
2127 WT_SMG,
2128 WT_AR2,
2129 WT_SHOTGUN,
2130 WT_CROSSBOW,
2131 WT_RPG,
2132 WT_GRENADE,
2133 WT_SNIPER,
2134 WT_COUNT
2135};
2136
2137static int weapon_type_id(const char *model, const char *cls) {
2138 if (item_icontains(model, "pistol") || item_icontains(cls, "pistol"))
2139 return WT_PISTOL;
2140 if (item_icontains(model, "357") || item_icontains(cls, "357"))
2141 return WT_357;
2142 if (item_icontains(model, "smg") || item_icontains(cls, "smg"))
2143 return WT_SMG;
2144 if (item_icontains(model, "ar2") || item_icontains(model, "irifle") ||
2145 item_icontains(cls, "ar2") || item_icontains(cls, "irifle"))
2146 return WT_AR2;
2147 if (item_icontains(model, "shotgun") || item_icontains(cls, "shotgun"))
2148 return WT_SHOTGUN;
2149 if (item_icontains(model, "crossbow") || item_icontains(cls, "crossbow"))
2150 return WT_CROSSBOW;
2151 if (item_icontains(model, "rocket") || item_icontains(cls, "rpg"))
2152 return WT_RPG;
2153 if (item_icontains(model, "grenade") || item_icontains(cls, "frag"))
2154 return WT_GRENADE;
2155 if (item_icontains(model, "sniper") || item_icontains(cls, "sniper"))
2156 return WT_SNIPER;
2157 return WT_NONE;
2158}
2159
2107static int classify_item(const char *model, const char *cls, char *label, 2160static int classify_item(const char *model, const char *cls, char *label,
2108 size_t outn) { 2161 size_t outn) {
2109 if (!model && !cls) 2162 if (!model && !cls)
@@ -2291,6 +2344,23 @@ static void items_collect(int local_idx) {
2291 if (hi < 0 || hi > 4096) 2344 if (hi < 0 || hi > 4096)
2292 hi = 4096; 2345 hi = 4096;
2293 int active_idx = local_active_weapon_idx(local_idx); 2346 int active_idx = local_active_weapon_idx(local_idx);
2347
2348 // Which weapon types the local player already carries. A fallen weapon of
2349 // the same type is then just an ammo pickup.
2350 bool owned_weapon[WT_COUNT] = {};
2351 for (int idx = 1; idx <= hi; idx++) {
2352 if (idx == local_idx)
2353 continue;
2354 void *ent = entitylist_get(idx);
2355 if (!ent || item_weapon_owner_idx(ent) != local_idx)
2356 continue;
2357 void *net = entity_networkable(idx);
2358 const char *cls = net ? entity_class_name(net, nullptr) : nullptr;
2359 const char *model = entity_model_name(ent);
2360 int wt = weapon_type_id(model, cls);
2361 if (wt > 0)
2362 owned_weapon[wt] = true;
2363 }
2294 static bool cats_logged = false; 2364 static bool cats_logged = false;
2295 if (!cats_logged) { 2365 if (!cats_logged) {
2296 cats_logged = true; 2366 cats_logged = true;
@@ -2347,6 +2417,13 @@ static void items_collect(int local_idx) {
2347 if (kind == IK_WEAPON) { 2417 if (kind == IK_WEAPON) {
2348 if (idx == active_idx || item_weapon_is_owned(ent)) 2418 if (idx == active_idx || item_weapon_is_owned(ent))
2349 continue; 2419 continue;
2420 int wt = weapon_type_id(model, cls);
2421 if (wt > 0 && owned_weapon[wt]) {
2422 char base_label[160];
2423 snprintf(base_label, sizeof(base_label), "%s", label);
2424 snprintf(label, sizeof(label), "%.100s ammo", base_label);
2425 kind = IK_AMMO;
2426 }
2350 } 2427 }
2351 // Distance applies to every tag; 0 means unlimited. 2428 // Distance applies to every tag; 0 means unlimited.
2352 if (g_item_max_dist > 0 && dist > (float)g_item_max_dist) 2429 if (g_item_max_dist > 0 && dist > (float)g_item_max_dist)
@@ -2511,6 +2588,23 @@ static const char *proj_name(int kind) {
2511 } 2588 }
2512} 2589}
2513 2590
2591// Fallback fuse clock when the server detonate time cannot be read. The stock
2592// frag fuse is 3 s from the first sighting.
2593static double proj_first_seen(int idx, double now) {
2594 static int idxs[32];
2595 static double first[32];
2596 static int n = 0;
2597 for (int i = 0; i < n; i++)
2598 if (idxs[i] == idx)
2599 return first[i];
2600 if (n < 32) {
2601 idxs[n] = idx;
2602 first[n] = now;
2603 n++;
2604 }
2605 return now;
2606}
2607
2514static void projs_collect(int local_idx) { 2608static void projs_collect(int local_idx) {
2515 static double last_scan = 0.0; 2609 static double last_scan = 0.0;
2516 double now = esp_now(); 2610 double now = esp_now();
@@ -2578,19 +2672,24 @@ static void projs_collect(int local_idx) {
2578 p.radius = kind == PK_RPG ? 200.0f : kind == PK_BALL ? 150.0f : 300.0f; 2672 p.radius = kind == PK_RPG ? 200.0f : kind == PK_BALL ? 150.0f : 300.0f;
2579 // m_flDetonateTime is server-only (not in the send table). 2673 // m_flDetonateTime is server-only (not in the send table).
2580 p.fuse = -1.0f; 2674 p.fuse = -1.0f;
2675 float det = -1.0f;
2581 if (kind != PK_RPG) { 2676 if (kind != PK_RPG) {
2582 float det = server_grenade_detonate(idx); 2677 det = server_grenade_detonate(idx);
2583 if (det > 0.0f && curtime > 0.0f) 2678 if (det > 0.0f && curtime > 0.0f)
2584 p.fuse = det - curtime; 2679 p.fuse = det - curtime;
2585 if (!(p.fuse > -2.0f && p.fuse < 30.0f)) 2680 if (!(p.fuse > -2.0f && p.fuse < 30.0f)) {
2586 p.fuse = -1.0f; 2681 p.fuse = 3.0f - (float)(now - proj_first_seen(idx, now));
2682 if (!(p.fuse > -2.0f && p.fuse < 30.0f))
2683 p.fuse = -1.0f;
2684 }
2587 } 2685 }
2588 static int logs = 0; 2686 static int logs = 0;
2589 if (logs < 8) { 2687 if (logs < 8) {
2590 logs++; 2688 logs++;
2591 logf("projectile: idx=%d cls=%s model=%s kind=%s fuse=%.2f " 2689 logf("projectile: idx=%d cls=%s model=%s kind=%s det=%.2f cur=%.2f "
2592 "radius=%.0f", 2690 "fuse=%.2f radius=%.0f",
2593 idx, cls ? cls : "?", model, proj_name(kind), p.fuse, p.radius); 2691 idx, cls ? cls : "?", model, proj_name(kind), det, curtime, p.fuse,
2692 p.radius);
2594 } 2693 }
2595 } 2694 }
2596} 2695}
diff --git a/src/server.cpp b/src/server.cpp
index 40aa850..1313f00 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -455,7 +455,14 @@ float server_grenade_detonate(int idx) {
455 server_entity_health(idx, nullptr, nullptr, &base); 455 server_entity_health(idx, nullptr, nullptr, &base);
456 if (!base) 456 if (!base)
457 return -1.0f; 457 return -1.0f;
458 return *(float *)((uintptr_t)base + g_srv_detonate); 458 float v = *(float *)((uintptr_t)base + g_srv_detonate);
459 static int logs = 0;
460 if (logs < 8) {
461 logs++;
462 logf("grenade fuse: idx=%d base=%p off=%d value=%.2f", idx, base,
463 g_srv_detonate, v);
464 }
465 return v;
459} 466}
460 467
461float server_grenade_radius(int idx) { 468float server_grenade_radius(int idx) {