From a224cc451233a487a3d931954b125f6e1736e710 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 23:46:58 +0100 Subject: Grenade trajectory: actual grenade state, full-length path, rounder rings - Prefer the local player's thrown frag over the pre-throw estimate: resolve CBaseGrenade::m_hThrower from the server datamap (client owner handles as fallback), then seed the prediction from the live origin/velocity. The real frag is a spinning VPhysics box with a random Y angular impulse, so bounce points remain approximate; the doc now says so. - Keep 512 path points sampled every other 20 ms step (up to 24 s) instead of 127 points / ~2.5 s, so the line reaches the landing instead of stopping in mid-air. - Radius rings use 36 segments; a segment whose near-plane-clipped endpoint the engine overlay refuses falls back to the local projection, and that projection now uses the configured camera FOV instead of a hardcoded 75 degrees (the wide-FOV ring no longer kinks/drops pieces). --- src/esp.cpp | 95 ++++++++++++++++++++++++++++++++++++++++------------------ src/hl2h.h | 1 + src/server.cpp | 22 ++++++++++++-- 3 files changed, 87 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/esp.cpp b/src/esp.cpp index dbe0051..c358b2f 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -70,7 +70,8 @@ static bool project_manual(const Vector &w, float &sx, float &sy) { return false; float x = dx * right[0] + dy * right[1] + dz * right[2]; float y = dx * up[0] + dy * up[1] + dz * up[2]; - float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f); + float fov = g_fov_camera > 10 ? (float)g_fov_camera : 75.0f; + float tanfov = tanf(fov * 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 std::isfinite(sx) && std::isfinite(sy) && fabsf(sx) < 1e7f && @@ -1986,7 +1987,7 @@ static bool project_segment(const Vector &a, const Vector &b, int &x0, int &y0, Vector fwd{cp * cy, cp * sy, -sp}; Vector da{a.x - g_cam_eye.x, a.y - g_cam_eye.y, a.z - g_cam_eye.z}; Vector db{b.x - g_cam_eye.x, b.y - g_cam_eye.y, b.z - g_cam_eye.z}; - const float nearz = 4.0f; + const float nearz = 5.0f; float za = da.x * fwd.x + da.y * fwd.y + da.z * fwd.z; float zb = db.x * fwd.x + db.y * fwd.y + db.z * fwd.z; if (za <= nearz && zb <= nearz) @@ -2002,7 +2003,9 @@ static bool project_segment(const Vector &a, const Vector &b, int &x0, int &y0, b.z + (a.z - b.z) * t}; } float ax, ay, bx, by; - if (!world_to_screen(ca, ax, ay) || !world_to_screen(cb, bx, by)) + if (!world_to_screen(ca, ax, ay) && !project_manual(ca, ax, ay)) + return false; + if (!world_to_screen(cb, bx, by) && !project_manual(cb, bx, by)) return false; if (!clip_line2d(ax, ay, bx, by, -8.0f, -8.0f, (float)g_scr_w + 8.0f, (float)g_scr_h + 8.0f)) @@ -2973,6 +2976,7 @@ struct ProjTarget { Vector origin; float fuse; // seconds; < 0 = impact / no fuse float radius; + bool owned; }; static ProjTarget g_projs[32]; @@ -3041,6 +3045,9 @@ static void projs_collect(int local_idx) { if (!model) continue; bool owned = item_weapon_is_owned(ent); + if (!owned && local_idx > 0 && + server_grenade_thrower(idx) == local_idx) + owned = true; bool frag_cls = cls && strcasestr(cls, "grenade") && !strcasestr(cls, "weapon"); bool item_cls = cls && (strcasestr(cls, "item") || @@ -3066,6 +3073,7 @@ static void projs_collect(int local_idx) { ProjTarget &p = g_projs[g_nprojs++]; p.idx = idx; p.kind = kind; + p.owned = owned; p.origin = Vector{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), *(float *)(e + g_net.origin + 8)}; @@ -3103,41 +3111,67 @@ static void draw_grenade_trajectory(int local_idx) { void *le = entitylist_get(local_idx); if (!le) return; - if (!active_weapon_contains(le, "frag") && - !active_weapon_contains(le, "w_grenade")) - return; - QAngle va = engine_view_angles(); - float pitch = va.x * PI_F / 180.0f; - float yaw = va.y * PI_F / 180.0f; - float cp = cosf(pitch), sp = sinf(pitch); - float cy = cosf(yaw), sy = sinf(yaw); - Vector fwd{cp * cy, cp * sy, -sp}; - Vector right{sy, -cy, 0.0f}; - - Vector src{g_cam_eye.x + fwd.x * 18.0f + right.x * 8.0f, - g_cam_eye.y + fwd.y * 18.0f + right.y * 8.0f, - g_cam_eye.z + fwd.z * 18.0f}; - Vector vel{}; - if (g_net.velocity >= 0) { - const float *pv = (const float *)((uintptr_t)le + g_net.velocity); - vel = Vector{pv[0], pv[1], pv[2]}; + Vector src{}, vel{}; + bool have = false; + bool actual = false; + // Prefer the local player's thrown frag: the real position and velocity are + // networked, so the preview starts from what is actually flying instead of + // the pre-throw estimate. The bounce contact points still depend on the + // grenade's spin/orientation, so this remains an approximation. + for (int i = 0; i < g_nprojs; i++) { + if (g_projs[i].kind != PK_FRAG || !g_projs[i].owned) + continue; + void *gent = entitylist_get(g_projs[i].idx); + if (!gent) + continue; + src = g_projs[i].origin; + if (g_net.velocity >= 0) { + const float *pv = (const float *)((uintptr_t)gent + g_net.velocity); + vel = Vector{pv[0], pv[1], pv[2]}; + } + have = true; + actual = true; + break; + } + if (!have) { + if (!active_weapon_contains(le, "frag") && + !active_weapon_contains(le, "w_grenade")) + return; + QAngle va = engine_view_angles(); + float pitch = va.x * PI_F / 180.0f; + float yaw = va.y * PI_F / 180.0f; + float cp = cosf(pitch), sp = sinf(pitch); + float cy = cosf(yaw), sy = sinf(yaw); + Vector fwd{cp * cy, cp * sy, -sp}; + Vector right{sy, -cy, 0.0f}; + + src = Vector{g_cam_eye.x + fwd.x * 18.0f + right.x * 8.0f, + g_cam_eye.y + fwd.y * 18.0f + right.y * 8.0f, + g_cam_eye.z + fwd.z * 18.0f}; + if (g_net.velocity >= 0) { + const float *pv = (const float *)((uintptr_t)le + g_net.velocity); + vel = Vector{pv[0], pv[1], pv[2]}; + } + vel.x += fwd.x * 1200.0f; + vel.y += fwd.y * 1200.0f; + vel.z += fwd.z * 1200.0f + 120.0f; // vForward[2] += 0.1 + have = true; } - vel.x += fwd.x * 1200.0f; - vel.y += fwd.y * 1200.0f; - vel.z += fwd.z * 1200.0f + 120.0f; // vForward[2] += 0.1 + if (actual && vel.x * vel.x + vel.y * vel.y + vel.z * vel.z < 100.0f) + return; // resting grenade: no useful continuation const float gravity = 600.0f; const float dt = 0.02f; const float restitution = 0.35f; const float friction = 0.35f; - Vector pts[128]; + Vector pts[512]; int n = 0; Vector pos = src; pts[n++] = pos; bool landed = false; - for (int step = 0; step < 240 && n < 127; step++) { + for (int step = 0; step < 1200 && n < 511; step++) { Vector nv = vel; nv.z -= gravity * dt; Vector next{pos.x + nv.x * dt, pos.y + nv.y * dt, pos.z + nv.z * dt}; @@ -3162,7 +3196,10 @@ static void draw_grenade_trajectory(int local_idx) { } else { pos = next; vel = nv; - pts[n++] = pos; + // One point every other step covers the whole flight without the old + // 127-point / 2.5-second cut-off. + if ((step & 1) == 0 && n < 511) + pts[n++] = pos; } } @@ -3185,7 +3222,7 @@ static void draw_grenade_trajectory(int local_idx) { surf_line(x, y + 4, x, y + 9); } if (landed) { - const int SEG = 24; + const int SEG = 36; const float radius = 300.0f; // CBaseGrenade m_DmgRadius surf_color(255, 120, 40, 120); for (int k = 0; k < SEG; k++) { @@ -3254,7 +3291,7 @@ void esp_draw_projectiles() { // off-screen, so it is not gated on center_ok and each segment is clipped // at the near plane independently. if (g_proj_radius && p.radius > 1.0f) { - const int SEG = 24; + const int SEG = 36; Vector ring[SEG + 1]; for (int k = 0; k <= SEG; k++) { float a = (float)k * 2.0f * PI_F / (float)SEG; diff --git a/src/hl2h.h b/src/hl2h.h index 32cee8f..86573d8 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -319,6 +319,7 @@ int server_player_ammo(int idx, int type); uint32_t server_player_weapon(int idx, int slot); float server_grenade_detonate(int idx); float server_grenade_radius(int idx); +int server_grenade_thrower(int idx); int server_item_juice(int idx); int server_entity_count(); void *server_entity_base(int idx); diff --git a/src/server.cpp b/src/server.cpp index f69ab30..cffc24f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -58,6 +58,7 @@ static int g_srv_mapname = -1; static int g_srv_armor = -1; static int g_srv_detonate = -1; static int g_srv_grenade_radius = -1; +static int g_srv_thrower = -1; static int g_srv_juice_suit = -1; static int g_srv_juice_health = -1; static int g_srv_ammo = -1; @@ -181,6 +182,7 @@ bool resolve_server_netvars() { // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); + g_srv_thrower = datamap_offset("m_hThrower", FIELD_EHANDLE); int juice_offs[8]; int njuice = datamap_offsets("m_iJuice", FIELD_INTEGER, 0x40, 0x4000, juice_offs, 8); @@ -510,8 +512,24 @@ float server_grenade_radius(int idx) { return *(float *)((uintptr_t)base + g_srv_grenade_radius); } -// CWallHealth / CNewRecharge share the same m_iJuice layout (the fields before -// it match), so one datamap offset works for both health and suit chargers. +// CBaseGrenade::m_hThrower, used to recognise the local player's own frag even +// when the client owner handles are unresolved. +int server_grenade_thrower(int idx) { + if (!g_srv_ready || g_srv_thrower < 0) + return -1; + void *base = nullptr; + server_entity_health(idx, nullptr, nullptr, &base); + if (!base) + return -1; + uint32_t h = *(uint32_t *)((uintptr_t)base + g_srv_thrower); + if (h == 0 || h == 0xFFFFFFFFu) + return -1; + return (int)(h & 0x7FF); +} + +// Health chargers (CWallHealth, CBaseToggle) and suit chargers +// (CNewRecharge, CBaseAnimating) have m_iJuice at different offsets; pick by +// the server class name. int server_item_juice(int idx) { if (!g_srv_ready) return -1; -- cgit v1.2.3