From 074e315c8550712dce0c7b530a90eee6aca29e63 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 23:26:53 +0100 Subject: Add grenade trajectory, RPG armour priority, empty chargers, mega gravity gun - Projectiles gets a "Grenade trajectory" toggle. While a frag grenade is held, simulate the weapon_frag throw (1200 u/s forward + 120 z, player velocity, gravity 600) and trace each step with a new trace_solid_segment(); reflect on impact, stop on a slow upward-normal hit, and draw the path, landing cross and a 300-unit blast ring. Verified on :99 (arcs, bounces off the tunnel wall, lands). - With the RPG equipped, armoured NPCs (striders, gunships, dropships, hunter-choppers) sort before normal enemies during target acquisition. Added helicopter/chopper to the ARMORED model list. - Empty health/suit chargers are hidden: read m_iJuice from the server datamap (0 = depleted, -1 = unreadable/fail open). - The citadel supercharged gravity gun (d3_citadel_03, d3_citadel_04, d3_breen_01 via IVEngineClient::GetLevelName) is treated as a normal weapon, so the melee aimbot no longer restricts it to VULNERABLE NPCs. --- src/esp.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 5 deletions(-) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index d32a8bc..594a8fa 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -320,7 +320,8 @@ static int npc_damage_class(int idx, const char *model) { if (td == 0 || td == 1) // DAMAGE_NO / DAMAGE_EVENTS_ONLY return NPC_DMG_IMMUNE; if (model_has(model, "gunship") || model_has(model, "combine_strider") || - model_has(model, "dropship")) + model_has(model, "dropship") || model_has(model, "helicopter") || + model_has(model, "chopper")) return NPC_DMG_ARMORED; if (model_has(model, "headcrab") || model_has(model, "antlion") || model_has(model, "manhack")) @@ -822,6 +823,12 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, *(int *)((uintptr_t)local_ent + g_net.health) <= 0) return false; + // With the RPG equipped, armoured units (striders, gunships, dropships, + // hunter-choppers) are the ones ordinary shots cannot hurt, so prefer them + // over normal NPCs when acquiring. + bool rpg = active_weapon_contains(local_ent, "rocket_launcher") || + active_weapon_contains(local_ent, "rpg"); + auto try_target = [&](int idx, bool enforce_fov, Vector &pt, float &fov) -> bool { if (idx <= 0 || idx == local) @@ -899,6 +906,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, int idx; float fov; Vector approx; + bool armored; + float key; }; Candidate cands[16]; int ncands = 0; @@ -928,6 +937,7 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, continue; if (!npc_can_be_damaged(idx, model)) continue; + bool armored = npc_damage_class(idx, model) == NPC_DMG_ARMORED; float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; if (!(maxz > 4.0f && maxz < 512.0f)) @@ -940,16 +950,17 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, float fov = getfov(view, angle_to(g_cam_eye, approx)); if (fov > max_fov) continue; + float key = (rpg && armored) ? fov - 1000.0f : fov; int pos = ncands; - while (pos > 0 && cands[pos - 1].fov > fov) + while (pos > 0 && cands[pos - 1].key > key) pos--; if (ncands < 16) ncands++; for (int j = ncands - 1; j > pos; j--) cands[j] = cands[j - 1]; if (pos < 16) - cands[pos] = {idx, fov, approx}; + cands[pos] = {idx, fov, approx, armored, key}; } if (ncands == 0) { @@ -1309,6 +1320,26 @@ bool gravity_gun_active(void *local_ent) { (model && strcasestr(model, "physcannon")); } +// The citadel supercharges the gravity gun (d3_citadel_03 onward). It can then +// punt any NPC, so the melee aimbot should not restrict itself to +// physics-vulnerable units. +static bool supercharged_gravity_gun() { + if (!g_iface.engine) + return false; + using Fn = const char *(*)(void *); + const char *m = vfunc(g_iface.engine, 51)(g_iface.engine); + if (!m) + return false; + bool supe = strstr(m, "d3_citadel_03") || strstr(m, "d3_citadel_04") || + strstr(m, "d3_breen_01"); + static const char *logged_map = nullptr; + if (logged_map != m) { + logged_map = m; + logf("melee: gravity gun map=%s supercharged=%d", m, (int)supe); + } + return supe; +} + bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { if (!g_iface.entitylist || g_net.origin < 0) return false; @@ -1324,6 +1355,7 @@ bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { // The punted object can travel much farther than the crowbar. float max_range = gravity ? 1200.0f : MELEE_RANGE; + bool supercharged = gravity && supercharged_gravity_gun(); using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); @@ -1363,8 +1395,10 @@ bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { continue; if (!npc_can_be_damaged(idx, model)) continue; - // The gravity gun is only the tool for physics-vulnerable units. - if (gravity && npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) + // The normal gravity gun is only the tool for physics-vulnerable units; + // the citadel version behaves like a normal weapon. + if (gravity && !supercharged && + npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) continue; Vector origin{*(float *)(e + g_net.origin), @@ -2747,6 +2781,11 @@ static void items_collect(int local_idx) { int kind = classify_item(model, cls, label, sizeof(label)); if (kind < 0) continue; + // Health/suit chargers are dispensers that stay useful after a refill, but + // an empty one cannot give anything. m_iJuice is 0 when depleted (until it + // recharges); only hide the tag once the server value is readable. + if (strcasestr(label, "charger") && server_item_juice(idx) == 0) + continue; Vector origin{*(float *)((uintptr_t)ent + g_net.origin), *(float *)((uintptr_t)ent + g_net.origin + 4), @@ -3045,6 +3084,114 @@ static void projs_collect(int local_idx) { } } +// Frag-grenade path preview: throw velocity from weapon_frag.cpp (eye/right +// offset, forward*1200 + 0.1 z), gravity 600, and a simple bounce model. +// Shows the bounces and the first landing with the blast-radius ring. +static void draw_grenade_trajectory(int local_idx) { + if (!g_proj_traj || !g_iface.entitylist) + return; + 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]}; + } + vel.x += fwd.x * 1200.0f; + vel.y += fwd.y * 1200.0f; + vel.z += fwd.z * 1200.0f + 120.0f; // vForward[2] += 0.1 + + const float gravity = 600.0f; + const float dt = 0.02f; + const float restitution = 0.35f; + const float friction = 0.35f; + + Vector pts[128]; + int n = 0; + Vector pos = src; + pts[n++] = pos; + bool landed = false; + for (int step = 0; step < 240 && n < 127; 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}; + Vector hit_end{}, normal{}; + if (trace_solid_segment(pos, next, le, hit_end, normal)) { + pts[n++] = hit_end; + float vn = vel.x * normal.x + vel.y * normal.y + vel.z * normal.z; + Vector vt{vel.x - vn * normal.x, vel.y - vn * normal.y, + vel.z - vn * normal.z}; + float keep = 1.0f - friction; + vel.x = vt.x * keep - normal.x * vn * restitution; + vel.y = vt.y * keep - normal.y * vn * restitution; + vel.z = vt.z * keep - normal.z * vn * restitution; + float vlen2 = vel.x * vel.x + vel.y * vel.y + vel.z * vel.z; + pos.x = hit_end.x + normal.x * 1.0f; + pos.y = hit_end.y + normal.y * 1.0f; + pos.z = hit_end.z + normal.z * 1.0f; + if (normal.z > 0.6f && (vn > -80.0f || vlen2 < 2500.0f)) { + landed = true; + break; + } + } else { + pos = next; + vel = nv; + pts[n++] = pos; + } + } + + surf_color(255, 210, 90, 200); + for (int i = 0; i + 1 < n; i++) { + int x0, y0, x1, y1; + if (project_segment(pts[i], pts[i + 1], x0, y0, x1, y1)) + surf_line(x0, y0, x1, y1); + } + + Vector land = landed ? pos : pts[n - 1]; + float ssx, ssy; + if (world_to_screen(land, ssx, ssy)) { + int x = (int)ssx, y = (int)ssy; + surf_color(255, 210, 90, 230); + surf_rect(0, x - 3, y - 3, x + 3, y + 3); + surf_line(x - 9, y, x - 4, y); + surf_line(x + 4, y, x + 9, y); + surf_line(x, y - 9, x, y - 4); + surf_line(x, y + 4, x, y + 9); + } + if (landed) { + const int SEG = 24; + const float radius = 300.0f; // CBaseGrenade m_DmgRadius + surf_color(255, 120, 40, 120); + for (int k = 0; k < SEG; k++) { + float a0 = (float)k * 2.0f * PI_F / (float)SEG; + float a1 = (float)(k + 1) * 2.0f * PI_F / (float)SEG; + Vector p0{land.x + cosf(a0) * radius, land.y + sinf(a0) * radius, + land.z + 1.0f}; + Vector p1{land.x + cosf(a1) * radius, land.y + sinf(a1) * radius, + land.z + 1.0f}; + int x0, y0, x1, y1; + if (project_segment(p0, p1, x0, y0, x1, y1)) + surf_line(x0, y0, x1, y1); + } + } +} + void esp_draw_projectiles() { if (!g_surface || !g_iface.entitylist || g_net.origin < 0) return; @@ -3055,6 +3202,7 @@ void esp_draw_projectiles() { return; } projs_collect(engine_get_local_player()); + draw_grenade_trajectory(engine_get_local_player()); for (int i = 0; i < g_nprojs; i++) { const ProjTarget &p = g_projs[i]; int r = 255, g = 170, b = 60; -- cgit v1.2.3