diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 15:55:08 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 15:55:08 +0100 |
| commit | 1267387f58d41d1421e3c2122a6930de0b279d12 (patch) | |
| tree | 1d4c5cbfdecb9aa85f16ff4f14bbc189241bf87a | |
| parent | Add usable item filter, projectile ESP and gravity-gun melee (diff) | |
| download | hl2h-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.md | 10 | ||||
| -rw-r--r-- | src/esp.cpp | 111 | ||||
| -rw-r--r-- | src/server.cpp | 9 |
3 files changed, 122 insertions, 8 deletions
| @@ -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. | ||
| 2054 | static 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 | |||
| 2052 | static int local_active_weapon_idx(int local_idx) { | 2067 | static 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. | ||
| 2123 | enum { | ||
| 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 | |||
| 2137 | static 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 | |||
| 2107 | static int classify_item(const char *model, const char *cls, char *label, | 2160 | static 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. | ||
| 2593 | static 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 | |||
| 2514 | static void projs_collect(int local_idx) { | 2608 | static 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 | ||
| 461 | float server_grenade_radius(int idx) { | 468 | float server_grenade_radius(int idx) { |
