From 606a5ca110574974861d31d35a10a53419c0e8c1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 16:01:36 +0100 Subject: 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. --- src/esp.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 29 deletions(-) (limited to 'src') 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) { vfunc(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s); if (behind) return false; + // A point on the view axis (e.g. directly below the feet) can produce + // NaN/inf or enormous coordinates; drawing those as ints corrupts the + // surface clip state and hides the rest of the overlay. + if (!std::isfinite(s.x) || !std::isfinite(s.y) || fabsf(s.x) > 1e7f || + fabsf(s.y) > 1e7f) + return false; sx = s.x; sy = s.y; return true; @@ -82,7 +88,8 @@ bool world_to_screen(const Vector &w, float &sx, float &sy) { float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f); sx = (float)g_scr_w * 0.5f + (x / z) * ((float)g_scr_w * 0.5f / tanfov); sy = (float)g_scr_h * 0.5f - (y / z) * ((float)g_scr_w * 0.5f / tanfov); - return true; + return std::isfinite(sx) && std::isfinite(sy) && fabsf(sx) < 1e7f && + fabsf(sy) < 1e7f; } // --------------------------------------------------------------------------- @@ -2346,8 +2353,30 @@ static void items_collect(int local_idx) { 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] = {}; + // the same type is then just an ammo pickup. Owner handles are unreliable on + // this client (owner/weapon_owner netvars resolve to -1), so remember every + // type the player has held since the current local entity appeared. + static uint32_t owned_mask = 0; + static void *last_local = nullptr; + void *local_ent = entitylist_get(local_idx); + if (local_ent != last_local) { + last_local = local_ent; + owned_mask = 0; + } + int owned_idx = active_idx; + if (owned_idx <= 0 && g_iface.engineserver) { + resolve_server_netvars(); + owned_idx = server_active_weapon(local_idx); + } + if (owned_idx > 0) { + void *aw = entitylist_get(owned_idx); + void *anet = entity_networkable(owned_idx); + const char *acls = anet ? entity_class_name(anet, nullptr) : nullptr; + const char *amodel = aw ? entity_model_name(aw) : nullptr; + int at = weapon_type_id(amodel, acls); + if (at > 0) + owned_mask |= 1u << at; + } for (int idx = 1; idx <= hi; idx++) { if (idx == local_idx) continue; @@ -2359,8 +2388,11 @@ static void items_collect(int local_idx) { const char *model = entity_model_name(ent); int wt = weapon_type_id(model, cls); if (wt > 0) - owned_weapon[wt] = true; + owned_mask |= 1u << wt; } + bool owned_weapon[WT_COUNT] = {}; + for (int t = 1; t < WT_COUNT; t++) + owned_weapon[t] = (owned_mask & (1u << t)) != 0; static bool cats_logged = false; if (!cats_logged) { cats_logged = true; @@ -2706,12 +2738,6 @@ void esp_draw_projectiles() { projs_collect(engine_get_local_player()); for (int i = 0; i < g_nprojs; i++) { const ProjTarget &p = g_projs[i]; - float sx, sy; - if (!world_to_screen(p.origin, sx, sy)) - continue; - if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -40 || - sy > (float)g_scr_h + 40) - continue; int r = 255, g = 170, b = 60; if (p.kind == PK_RPG) { r = 255; g = 120; b = 40; @@ -2722,25 +2748,33 @@ void esp_draw_projectiles() { } else if (p.fuse >= 0.0f && p.fuse < 0.8f) { r = 255; g = 60; b = 60; } - int x = (int)sx, y = (int)sy; - surf_color(r, g, b, 255); - surf_rect(0, x - 4, y - 4, x + 4, y + 4); - surf_rect(1, x - 1, y - 1, x + 2, y + 2); - char text[64]; - if (g_proj_fuse && p.fuse >= 0.0f) - snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse); - else - snprintf(text, sizeof(text), "%s", proj_name(p.kind)); - int tx = x + 9; - if (tx > g_scr_w - 80) - tx = x - 9 - (int)strlen(text) * 6; - if (tx < 4) - tx = 4; - int ty = y - 14; - if (ty < 4) - ty = 4; - draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0); - draw_text_small_col(tx, ty, text, r, g, b); + float sx, sy; + bool center_ok = world_to_screen(p.origin, sx, sy) && + sx >= -40.0f && sx <= (float)g_scr_w + 40.0f && + sy >= -40.0f && sy <= (float)g_scr_h + 40.0f; + if (center_ok) { + int x = (int)sx, y = (int)sy; + surf_color(r, g, b, 255); + surf_rect(0, x - 4, y - 4, x + 4, y + 4); + surf_rect(1, x - 1, y - 1, x + 2, y + 2); + char text[64]; + if (g_proj_fuse && p.fuse >= 0.0f) + snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse); + else + snprintf(text, sizeof(text), "%s", proj_name(p.kind)); + int tx = x + 9; + if (tx > g_scr_w - 80) + tx = x - 9 - (int)strlen(text) * 6; + if (tx < 4) + tx = 4; + int ty = y - 14; + if (ty < 4) + ty = 4; + draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0); + draw_text_small_col(tx, ty, text, r, g, b); + } + // The ring is useful even when the grenade itself is behind the camera or + // off-screen, so it is not gated on center_ok. if (g_proj_radius && p.radius > 1.0f) { const int SEG = 24; float lx = 0.0f, ly = 0.0f; -- cgit v1.2.3