summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/esp.cpp236
-rw-r--r--src/hl2h.h2
-rw-r--r--src/netvars.cpp13
3 files changed, 220 insertions, 31 deletions
diff --git a/src/esp.cpp b/src/esp.cpp
index 715dbc8..1d4a6a5 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -52,27 +52,11 @@ static double esp_now() {
52 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; 52 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
53} 53}
54 54
55bool world_to_screen(const Vector &w, float &sx, float &sy) { 55// Local perspective projection used when the engine's debug-overlay
56 // Preferred: the engine's own transform. Uses the real view setup (FOV, 56// projection refuses a point that is actually in front of the camera.
57 // aspect, roll), so boxes do not drift at the screen edges. 57static bool project_manual(const Vector &w, float &sx, float &sy) {
58 if (g_iface.debugoverlay) { 58 if (!g_scr_w || !g_scr_h)
59 using Fn = int (*)(void *, const Vector *, Vector *); 59 return false;
60 Vector s;
61 int behind =
62 vfunc<Fn>(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s);
63 if (behind)
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;
71 sx = s.x;
72 sy = s.y;
73 return true;
74 }
75 // Fallback: local math with the default HL2 FOV.
76 float pitch = g_cam_view.x * PI_F / 180.0f; 60 float pitch = g_cam_view.x * PI_F / 180.0f;
77 float yaw = g_cam_view.y * PI_F / 180.0f; 61 float yaw = g_cam_view.y * PI_F / 180.0f;
78 float sp = sinf(pitch), cp = cosf(pitch); 62 float sp = sinf(pitch), cp = cosf(pitch);
@@ -82,7 +66,8 @@ bool world_to_screen(const Vector &w, float &sx, float &sy) {
82 float up[3] = {sp * cy, sp * syy, cp}; 66 float up[3] = {sp * cy, sp * syy, cp};
83 float dx = w.x - g_cam_eye.x, dy = w.y - g_cam_eye.y, dz = w.z - g_cam_eye.z; 67 float dx = w.x - g_cam_eye.x, dy = w.y - g_cam_eye.y, dz = w.z - g_cam_eye.z;
84 float z = dx * fwd[0] + dy * fwd[1] + dz * fwd[2]; 68 float z = dx * fwd[0] + dy * fwd[1] + dz * fwd[2];
85 if (z < 1.0f) return false; 69 if (z < 1.0f)
70 return false;
86 float x = dx * right[0] + dy * right[1] + dz * right[2]; 71 float x = dx * right[0] + dy * right[1] + dz * right[2];
87 float y = dx * up[0] + dy * up[1] + dz * up[2]; 72 float y = dx * up[0] + dy * up[1] + dz * up[2];
88 float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f); 73 float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f);
@@ -92,6 +77,42 @@ bool world_to_screen(const Vector &w, float &sx, float &sy) {
92 fabsf(sy) < 1e7f; 77 fabsf(sy) < 1e7f;
93} 78}
94 79
80bool world_to_screen(const Vector &w, float &sx, float &sy) {
81 // Preferred: the engine's own transform. Uses the real view setup (FOV,
82 // aspect, roll), so boxes do not drift at the screen edges.
83 if (g_iface.debugoverlay) {
84 using Fn = int (*)(void *, const Vector *, Vector *);
85 Vector s;
86 int behind =
87 vfunc<Fn>(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s);
88 if (behind) {
89 // The overlay transform can report "behind" for points that are in
90 // front but near the view axis/extremes (e.g. something directly below
91 // the player). Use the local projection in that case.
92 float pitch = g_cam_view.x * PI_F / 180.0f;
93 float yaw = g_cam_view.y * PI_F / 180.0f;
94 float cp = cosf(pitch), sp = sinf(pitch);
95 float fwd[3] = {cp * cosf(yaw), cp * sinf(yaw), -sp};
96 float z = (w.x - g_cam_eye.x) * fwd[0] +
97 (w.y - g_cam_eye.y) * fwd[1] +
98 (w.z - g_cam_eye.z) * fwd[2];
99 if (z > 4.0f)
100 return project_manual(w, sx, sy);
101 return false;
102 }
103 // A point on the view axis can produce NaN/inf or enormous coordinates;
104 // drawing those as ints corrupts the surface clip state and hides the
105 // rest of the overlay.
106 if (!std::isfinite(s.x) || !std::isfinite(s.y) || fabsf(s.x) > 1e7f ||
107 fabsf(s.y) > 1e7f)
108 return false;
109 sx = s.x;
110 sy = s.y;
111 return true;
112 }
113 return project_manual(w, sx, sy);
114}
115
95// --------------------------------------------------------------------------- 116// ---------------------------------------------------------------------------
96// entity helpers (IClientEntityList / IClientNetworkable, header-documented) 117// entity helpers (IClientEntityList / IClientNetworkable, header-documented)
97// --------------------------------------------------------------------------- 118// ---------------------------------------------------------------------------
@@ -431,8 +452,6 @@ static void esp_collect(int local_idx) {
431 } 452 }
432 Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), 453 Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4),
433 *(float *)(e + g_net.origin + 8)}; 454 *(float *)(e + g_net.origin + 8)};
434 Vector bottom{origin.x, origin.y, origin.z + minz};
435 Vector top{origin.x, origin.y, origin.z + maxz};
436 bool neutral = false; 455 bool neutral = false;
437 bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral); 456 bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral);
438 // Arrows are collected before projection: a hostile behind the camera 457 // Arrows are collected before projection: a hostile behind the camera
@@ -443,12 +462,47 @@ static void esp_collect(int local_idx) {
443 a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f}; 462 a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f};
444 a.neutral = neutral; 463 a.neutral = neutral;
445 } 464 }
446 float sx, sy, tx, ty; 465 // Project several points along the vertical axis. Requiring both feet and
447 if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) 466 // head to project dropped the target when the feet were behind the near
467 // plane (common when standing over/next to something).
468 float min_sy = 1e9f, max_sy = -1e9f, low_x = 0.0f;
469 bool projected = false;
470 const int VSAMPLES = 6;
471 for (int k = 0; k <= VSAMPLES; k++) {
472 float z = origin.z + minz +
473 (maxz - minz) * (float)k / (float)VSAMPLES;
474 float px, py;
475 if (!world_to_screen(Vector{origin.x, origin.y, z}, px, py))
476 continue;
477 if (px < -2.0f * (float)g_scr_w || px > 3.0f * (float)g_scr_w)
478 continue;
479 projected = true;
480 if (py < min_sy)
481 min_sy = py;
482 if (py > max_sy) {
483 max_sy = py;
484 low_x = px;
485 }
486 }
487 if (!projected) {
488 static int fail_logs = 0;
489 float ddx = origin.x - g_cam_eye.x;
490 float ddy = origin.y - g_cam_eye.y;
491 float ddz = origin.z - g_cam_eye.z;
492 if (fail_logs < 8 && ddx * ddx + ddy * ddy + ddz * ddz < 400.0f * 400.0f) {
493 fail_logs++;
494 logf("ESP: close target %d model=%s failed projection at "
495 "(%.1f %.1f %.1f)",
496 idx, model ? model : "?", origin.x, origin.y, origin.z);
497 }
498 continue;
499 }
500 float h = max_sy - min_sy;
501 if (h < 3.0f || max_sy < 0 || min_sy > (float)g_scr_h)
502 continue; // behind / offscreen
503 if (low_x < -(float)g_scr_w || low_x > 2.0f * (float)g_scr_w)
448 continue; 504 continue;
449 float h = sy - ty; 505 float sx = low_x, sy = max_sy;
450 if (h < 3.0f || sy < 0 || ty > (float)g_scr_h) continue; // behind / offscreen
451 if (sx < -(float)g_scr_w || sx > 2.0f * (float)g_scr_w) continue;
452 506
453 EspTarget &t = g_targets[g_ntargets++]; 507 EspTarget &t = g_targets[g_ntargets++];
454 t.idx = idx; 508 t.idx = idx;
@@ -2088,6 +2142,7 @@ enum ItemKind {
2088struct ItemTarget { 2142struct ItemTarget {
2089 int idx; 2143 int idx;
2090 int kind; 2144 int kind;
2145 int ammo_type; // m_iAmmo index for IK_AMMO, -1 otherwise
2091 Vector origin; 2146 Vector origin;
2092 char label[128]; 2147 char label[128];
2093}; 2148};
@@ -2235,6 +2290,117 @@ static int weapon_type_id(const char *model, const char *cls) {
2235 return WT_NONE; 2290 return WT_NONE;
2236} 2291}
2237 2292
2293static int weapon_ammo_type(int wt) {
2294 switch (wt) {
2295 case WT_PISTOL: return 2;
2296 case WT_357: return 4;
2297 case WT_SMG: return 3;
2298 case WT_AR2: return 0;
2299 case WT_SHOTGUN: return 6;
2300 case WT_CROSSBOW: return 5;
2301 case WT_RPG: return 7;
2302 case WT_GRENADE: return 11;
2303 case WT_SNIPER: return 9;
2304 default: return -1;
2305 }
2306}
2307
2308// Ammo type index used by CBasePlayer::m_iAmmo (order of AddAmmoType in
2309// hl2_gamerules.cpp).
2310static int item_ammo_type(const char *model) {
2311 if (!model)
2312 return -1;
2313 if (item_icontains(model, "boxsrounds") ||
2314 item_icontains(model, "ammocrate_pistol") ||
2315 item_icontains(model, "w_pistol"))
2316 return 2;
2317 if (item_icontains(model, "boxmrounds") ||
2318 item_icontains(model, "ammocrate_smg1"))
2319 return 3;
2320 if (item_icontains(model, "boxlrounds") ||
2321 item_icontains(model, "combine_rifle") ||
2322 item_icontains(model, "ammocrate_ar2") ||
2323 item_icontains(model, "w_ar2") || item_icontains(model, "w_irifle"))
2324 return 0;
2325 if (item_icontains(model, "357ammo") ||
2326 item_icontains(model, "357ammobox") ||
2327 item_icontains(model, "w_357"))
2328 return 4;
2329 if (item_icontains(model, "crossbowrounds") ||
2330 item_icontains(model, "w_crossbow"))
2331 return 5;
2332 if (item_icontains(model, "boxbuckshot") ||
2333 item_icontains(model, "ammocrate_buckshot") ||
2334 item_icontains(model, "w_shotgun"))
2335 return 6;
2336 if (item_icontains(model, "w_missile_closed") ||
2337 item_icontains(model, "ammocrate_rockets") ||
2338 item_icontains(model, "w_rocket_launcher"))
2339 return 7;
2340 if (item_icontains(model, "ar2_grenade") ||
2341 item_icontains(model, "ammocrate_smg2"))
2342 return 8;
2343 if (item_icontains(model, "ammocrate_grenade") ||
2344 item_icontains(model, "w_grenade") || item_icontains(model, "w_frag"))
2345 return 11;
2346 if (item_icontains(model, "w_smg1") || item_icontains(model, "w_smg2"))
2347 return 3;
2348 return -1;
2349}
2350
2351static int convar_int(const char *name) {
2352 if (!g_iface.cvar || !name)
2353 return -1;
2354 using FindFn = void *(*)(void *, const char *);
2355 void *v = vfunc<FindFn>(g_iface.cvar, 12)(g_iface.cvar, name);
2356 if (!v)
2357 return -1;
2358 return (int)*(float *)((uintptr_t)v + 0x2c); // ConVar m_Value.f
2359}
2360
2361// Maximum reserve ammo for an m_iAmmo index. Prefers the sk_max_* cvar and
2362// falls back to the stock HL2 cap so the RPG's 3-rocket limit is honoured
2363// even when server cvars are not visible from the client.
2364static int ammo_max(int type) {
2365 static int cache[16];
2366 static bool init = false;
2367 if (type < 0 || type >= 16)
2368 return -1;
2369 if (!init) {
2370 for (int i = 0; i < 16; i++)
2371 cache[i] = -2;
2372 init = true;
2373 }
2374 if (cache[type] != -2)
2375 return cache[type];
2376 const char *name = nullptr;
2377 int stock = -1;
2378 switch (type) {
2379 case 0: name = "sk_max_ar2"; stock = 60; break;
2380 case 2: name = "sk_max_pistol"; stock = 150; break;
2381 case 3: name = "sk_max_smg1"; stock = 225; break;
2382 case 4: name = "sk_max_357"; stock = 12; break;
2383 case 5: name = "sk_max_crossbow"; stock = 10; break;
2384 case 6: name = "sk_max_buckshot"; stock = 30; break;
2385 case 7: name = "sk_max_rpg_round"; stock = 3; break;
2386 case 8: name = "sk_max_smg1_grenade"; stock = 3; break;
2387 case 9: name = "sk_max_sniper_round"; stock = 30; break;
2388 case 11: name = "sk_max_grenade"; stock = 5; break;
2389 default: break;
2390 }
2391 int v = convar_int(name);
2392 if (v <= 0)
2393 v = stock;
2394 cache[type] = v;
2395 return v;
2396}
2397
2398static int local_ammo(void *local_ent, int type) {
2399 if (!local_ent || g_net.ammo < 0 || type < 0 || type >= 32)
2400 return -1;
2401 return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4);
2402}
2403
2238static int classify_item(const char *model, const char *cls, char *label, 2404static int classify_item(const char *model, const char *cls, char *label,
2239 size_t outn) { 2405 size_t outn) {
2240 if (!model && !cls) 2406 if (!model && !cls)
@@ -2535,6 +2701,12 @@ static void items_collect(int local_idx) {
2535 ItemTarget &t = g_item_targets[g_nitems++]; 2701 ItemTarget &t = g_item_targets[g_nitems++];
2536 t.idx = idx; 2702 t.idx = idx;
2537 t.kind = kind; 2703 t.kind = kind;
2704 t.ammo_type = -1;
2705 if (kind == IK_AMMO) {
2706 t.ammo_type = item_ammo_type(model);
2707 if (t.ammo_type < 0)
2708 t.ammo_type = weapon_ammo_type(weapon_type_id(model, cls));
2709 }
2538 t.origin = origin; 2710 t.origin = origin;
2539 snprintf(t.label, sizeof(t.label), "%s", label); 2711 snprintf(t.label, sizeof(t.label), "%s", label);
2540 if (dbg_lines < 8) { 2712 if (dbg_lines < 8) {
@@ -2605,6 +2777,12 @@ void esp_draw_items() {
2605 continue; 2777 continue;
2606 if (t.kind == IK_ARMOR && vit_armor >= 0 && vit_armor >= 100) 2778 if (t.kind == IK_ARMOR && vit_armor >= 0 && vit_armor >= 100)
2607 continue; 2779 continue;
2780 if (t.kind == IK_AMMO && t.ammo_type >= 0) {
2781 int maxa = ammo_max(t.ammo_type);
2782 int cura = local_ammo(entitylist_get(local), t.ammo_type);
2783 if (maxa > 0 && cura >= 0 && cura >= maxa)
2784 continue;
2785 }
2608 } 2786 }
2609 float sx, sy; 2787 float sx, sy;
2610 if (!world_to_screen(t.origin, sx, sy)) 2788 if (!world_to_screen(t.origin, sx, sy))
diff --git a/src/hl2h.h b/src/hl2h.h
index 0c8ffb5..4f3552e 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -100,6 +100,8 @@ struct NetVars {
100 int punch = -1; // client m_vecPunchAngle (aim punch) 100 int punch = -1; // client m_vecPunchAngle (aim punch)
101 int default_fov = -1; // client m_iDefaultFOV (camera fov) 101 int default_fov = -1; // client m_iDefaultFOV (camera fov)
102 int dmg_radius = -1; // client C_BaseGrenade::m_DmgRadius 102 int dmg_radius = -1; // client C_BaseGrenade::m_DmgRadius
103 int my_weapons = -1; // client m_hMyWeapons[48] array base
104 int ammo = -1; // client m_iAmmo[] array base
103 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 105 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72
104 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } 106 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
105 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. 107 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100.
diff --git a/src/netvars.cpp b/src/netvars.cpp
index ef564cd..2747c29 100644
--- a/src/netvars.cpp
+++ b/src/netvars.cpp
@@ -112,6 +112,15 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) {
112 int off = (int)(base_off + (uint32_t)poff); 112 int off = (int)(base_off + (uint32_t)poff);
113 if (off >= 0x20 && off <= 0x2000) 113 if (off >= 0x20 && off <= 0x2000)
114 g_net.dmg_radius = off; 114 g_net.dmg_radius = off;
115 } else if (strcmp(pname2, "m_hMyWeapons") == 0 &&
116 g_net.my_weapons < 0) {
117 int off = (int)(base_off + (uint32_t)poff);
118 if (off >= 0x20 && off <= 0x2000)
119 g_net.my_weapons = off;
120 } else if (strcmp(pname2, "m_iAmmo") == 0 && g_net.ammo < 0) {
121 int off = (int)(base_off + (uint32_t)poff);
122 if (off >= 0x20 && off <= 0x2000)
123 g_net.ammo = off;
115 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { 124 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) {
116 g_net.teamnum = (int)(base_off + (uint32_t)poff); 125 g_net.teamnum = (int)(base_off + (uint32_t)poff);
117 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { 126 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) {
@@ -294,11 +303,11 @@ void resolve_netvars() {
294 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " 303 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d "
295 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " 304 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d "
296 "weapon_owner=%d clr_render=%d punch=%d default_fov=%d " 305 "weapon_owner=%d clr_render=%d punch=%d default_fov=%d "
297 "dmg_radius=%d", 306 "dmg_radius=%d my_weapons=%d ammo=%d",
298 g_net.flags, g_net.velocity, g_net.movetype, g_net.health, 307 g_net.flags, g_net.velocity, g_net.movetype, g_net.health,
299 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, 308 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset,
300 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, 309 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex,
301 g_net.absvelocity, g_net.activeweapon, g_net.owner, 310 g_net.absvelocity, g_net.activeweapon, g_net.owner,
302 g_net.weapon_owner, g_net.clr_render, g_net.punch, 311 g_net.weapon_owner, g_net.clr_render, g_net.punch,
303 g_net.default_fov, g_net.dmg_radius); 312 g_net.default_fov, g_net.dmg_radius, g_net.my_weapons, g_net.ammo);
304} 313}