summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 16:01:36 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 16:01:36 +0100
commit606a5ca110574974861d31d35a10a53419c0e8c1 (patch)
tree20d1bde49f1ee4930bffc97e72ba6075199fe6b4
parentItems: fallen owned weapons count as ammo; fuse diagnostics (diff)
downloadhl2h-606a5ca110574974861d31d35a10a53419c0e8c1.tar.gz
hl2h-606a5ca110574974861d31d35a10a53419c0e8c1.zip
Fix overlay-vanishing projections; ammo rule via held weapons
- world_to_screen now rejects NaN/inf and absurd projections. A point on the view axis (directly below the feet) could produce NaN and corrupt the ISurface clip state, hiding the whole ESP for that frame. - Draw projectile radius rings even when the projectile centre is off-screen or behind the camera. - Owner netvars are -1 on this client, so the "owned weapon -> ammo" rule now accumulates the weapon types the player has actually held (active weapon, server fallback), reset when the local entity changes.
-rw-r--r--CONTEXT.md18
-rw-r--r--src/esp.cpp92
2 files changed, 77 insertions, 33 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index d8fdb97..2cec0ad 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -228,6 +228,14 @@ vocabulary / decisions.
228 a chevron clamped to a 26 px margin; behind-the-camera targets use the 228 a chevron clamped to a 26 px margin; behind-the-camera targets use the
229 dot product with the view right vector. Toggle `ESP -> Off-screen arrows` 229 dot product with the view right vector. Toggle `ESP -> Off-screen arrows`
230 (`arrows=1`). 230 (`arrows=1`).
231- `world_to_screen` rejects non-finite projections and absurd magnitudes
232 (>1e7). A point on the view axis (e.g. directly below the player's feet)
233 can make `IVDebugOverlay::ScreenPosition` return NaN/huge values; casting
234 those to int corrupted the ISurface clip state and made the whole overlay
235 disappear for that frame.
236- Projectile radius rings are drawn independently of the centre marker, so a
237 grenade behind the camera/off-screen still shows its blast circle when any
238 part of the ring projects.
231- Map transitions: see below. 239- Map transitions: see below.
232 240
233## Camera / display 241## Camera / display
@@ -595,10 +603,12 @@ vocabulary / decisions.
595 HL2 `MAX_NORMAL_BATTERY` cap of 100, read from the server 603 HL2 `MAX_NORMAL_BATTERY` cap of 100, read from the server
596 `m_ArmorValue`. If the values cannot be read the filter fails open. 604 `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 605- 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 606 reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Owner handles are
599 from client weapon entities whose owner handle resolves to the local player 607 unreliable on this client (`m_hOwnerEntity`/`m_hOwner` resolve to -1), so
600 index; NPC-held weapons are ignored. Weapons the player does not own stay 608 the inventory mask is accumulated from every weapon type the player has
601 under the Weapons category. 609 held (client `m_hActiveWeapon`, with the server handle as fallback) since
610 the current local entity appeared. NPC-held weapons are therefore ignored.
611 Weapons the player does not own stay under the Weapons category.
602- Scans the client entity list every 100 ms and tags health, ammo, armor/suit, 612- Scans the client entity list every 100 ms and tags health, ammo, armor/suit,
603 world weapon pickups, the two supply-crate models and generic items. 613 world weapon pickups, the two supply-crate models and generic items.
604- Each tag is a colored marker with a name and optional distance, filtered by 614- 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 e273bf6..447bee7 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -62,6 +62,12 @@ bool world_to_screen(const Vector &w, float &sx, float &sy) {
62 vfunc<Fn>(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s); 62 vfunc<Fn>(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s);
63 if (behind) 63 if (behind)
64 return false; 64 return false;
65 // A point on the view axis (e.g. directly below the feet) can produce
66 // NaN/inf or enormous coordinates; drawing those as ints corrupts the
67 // surface clip state and hides the rest of the overlay.
68 if (!std::isfinite(s.x) || !std::isfinite(s.y) || fabsf(s.x) > 1e7f ||
69 fabsf(s.y) > 1e7f)
70 return false;
65 sx = s.x; 71 sx = s.x;
66 sy = s.y; 72 sy = s.y;
67 return true; 73 return true;
@@ -82,7 +88,8 @@ bool world_to_screen(const Vector &w, float &sx, float &sy) {
82 float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f); 88 float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f);
83 sx = (float)g_scr_w * 0.5f + (x / z) * ((float)g_scr_w * 0.5f / tanfov); 89 sx = (float)g_scr_w * 0.5f + (x / z) * ((float)g_scr_w * 0.5f / tanfov);
84 sy = (float)g_scr_h * 0.5f - (y / z) * ((float)g_scr_w * 0.5f / tanfov); 90 sy = (float)g_scr_h * 0.5f - (y / z) * ((float)g_scr_w * 0.5f / tanfov);
85 return true; 91 return std::isfinite(sx) && std::isfinite(sy) && fabsf(sx) < 1e7f &&
92 fabsf(sy) < 1e7f;
86} 93}
87 94
88// --------------------------------------------------------------------------- 95// ---------------------------------------------------------------------------
@@ -2346,8 +2353,30 @@ static void items_collect(int local_idx) {
2346 int active_idx = local_active_weapon_idx(local_idx); 2353 int active_idx = local_active_weapon_idx(local_idx);
2347 2354
2348 // Which weapon types the local player already carries. A fallen weapon of 2355 // Which weapon types the local player already carries. A fallen weapon of
2349 // the same type is then just an ammo pickup. 2356 // the same type is then just an ammo pickup. Owner handles are unreliable on
2350 bool owned_weapon[WT_COUNT] = {}; 2357 // this client (owner/weapon_owner netvars resolve to -1), so remember every
2358 // type the player has held since the current local entity appeared.
2359 static uint32_t owned_mask = 0;
2360 static void *last_local = nullptr;
2361 void *local_ent = entitylist_get(local_idx);
2362 if (local_ent != last_local) {
2363 last_local = local_ent;
2364 owned_mask = 0;
2365 }
2366 int owned_idx = active_idx;
2367 if (owned_idx <= 0 && g_iface.engineserver) {
2368 resolve_server_netvars();
2369 owned_idx = server_active_weapon(local_idx);
2370 }
2371 if (owned_idx > 0) {
2372 void *aw = entitylist_get(owned_idx);
2373 void *anet = entity_networkable(owned_idx);
2374 const char *acls = anet ? entity_class_name(anet, nullptr) : nullptr;
2375 const char *amodel = aw ? entity_model_name(aw) : nullptr;
2376 int at = weapon_type_id(amodel, acls);
2377 if (at > 0)
2378 owned_mask |= 1u << at;
2379 }
2351 for (int idx = 1; idx <= hi; idx++) { 2380 for (int idx = 1; idx <= hi; idx++) {
2352 if (idx == local_idx) 2381 if (idx == local_idx)
2353 continue; 2382 continue;
@@ -2359,8 +2388,11 @@ static void items_collect(int local_idx) {
2359 const char *model = entity_model_name(ent); 2388 const char *model = entity_model_name(ent);
2360 int wt = weapon_type_id(model, cls); 2389 int wt = weapon_type_id(model, cls);
2361 if (wt > 0) 2390 if (wt > 0)
2362 owned_weapon[wt] = true; 2391 owned_mask |= 1u << wt;
2363 } 2392 }
2393 bool owned_weapon[WT_COUNT] = {};
2394 for (int t = 1; t < WT_COUNT; t++)
2395 owned_weapon[t] = (owned_mask & (1u << t)) != 0;
2364 static bool cats_logged = false; 2396 static bool cats_logged = false;
2365 if (!cats_logged) { 2397 if (!cats_logged) {
2366 cats_logged = true; 2398 cats_logged = true;
@@ -2706,12 +2738,6 @@ void esp_draw_projectiles() {
2706 projs_collect(engine_get_local_player()); 2738 projs_collect(engine_get_local_player());
2707 for (int i = 0; i < g_nprojs; i++) { 2739 for (int i = 0; i < g_nprojs; i++) {
2708 const ProjTarget &p = g_projs[i]; 2740 const ProjTarget &p = g_projs[i];
2709 float sx, sy;
2710 if (!world_to_screen(p.origin, sx, sy))
2711 continue;
2712 if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -40 ||
2713 sy > (float)g_scr_h + 40)
2714 continue;
2715 int r = 255, g = 170, b = 60; 2741 int r = 255, g = 170, b = 60;
2716 if (p.kind == PK_RPG) { 2742 if (p.kind == PK_RPG) {
2717 r = 255; g = 120; b = 40; 2743 r = 255; g = 120; b = 40;
@@ -2722,25 +2748,33 @@ void esp_draw_projectiles() {
2722 } else if (p.fuse >= 0.0f && p.fuse < 0.8f) { 2748 } else if (p.fuse >= 0.0f && p.fuse < 0.8f) {
2723 r = 255; g = 60; b = 60; 2749 r = 255; g = 60; b = 60;
2724 } 2750 }
2725 int x = (int)sx, y = (int)sy; 2751 float sx, sy;
2726 surf_color(r, g, b, 255); 2752 bool center_ok = world_to_screen(p.origin, sx, sy) &&
2727 surf_rect(0, x - 4, y - 4, x + 4, y + 4); 2753 sx >= -40.0f && sx <= (float)g_scr_w + 40.0f &&
2728 surf_rect(1, x - 1, y - 1, x + 2, y + 2); 2754 sy >= -40.0f && sy <= (float)g_scr_h + 40.0f;
2729 char text[64]; 2755 if (center_ok) {
2730 if (g_proj_fuse && p.fuse >= 0.0f) 2756 int x = (int)sx, y = (int)sy;
2731 snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse); 2757 surf_color(r, g, b, 255);
2732 else 2758 surf_rect(0, x - 4, y - 4, x + 4, y + 4);
2733 snprintf(text, sizeof(text), "%s", proj_name(p.kind)); 2759 surf_rect(1, x - 1, y - 1, x + 2, y + 2);
2734 int tx = x + 9; 2760 char text[64];
2735 if (tx > g_scr_w - 80) 2761 if (g_proj_fuse && p.fuse >= 0.0f)
2736 tx = x - 9 - (int)strlen(text) * 6; 2762 snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse);
2737 if (tx < 4) 2763 else
2738 tx = 4; 2764 snprintf(text, sizeof(text), "%s", proj_name(p.kind));
2739 int ty = y - 14; 2765 int tx = x + 9;
2740 if (ty < 4) 2766 if (tx > g_scr_w - 80)
2741 ty = 4; 2767 tx = x - 9 - (int)strlen(text) * 6;
2742 draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0); 2768 if (tx < 4)
2743 draw_text_small_col(tx, ty, text, r, g, b); 2769 tx = 4;
2770 int ty = y - 14;
2771 if (ty < 4)
2772 ty = 4;
2773 draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0);
2774 draw_text_small_col(tx, ty, text, r, g, b);
2775 }
2776 // The ring is useful even when the grenade itself is behind the camera or
2777 // off-screen, so it is not gated on center_ok.
2744 if (g_proj_radius && p.radius > 1.0f) { 2778 if (g_proj_radius && p.radius > 1.0f) {
2745 const int SEG = 24; 2779 const int SEG = 24;
2746 float lx = 0.0f, ly = 0.0f; 2780 float lx = 0.0f, ly = 0.0f;