diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 16:11:28 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 16:11:28 +0100 |
| commit | 4019880cf113a2a13460ec8ea5bb30a5c8ed50f3 (patch) | |
| tree | 5c13148671afd7d294273cff431c0ef373d862a3 | |
| parent | Clip transition and radius lines at the near plane (diff) | |
| download | hl2h-4019880cf113a2a13460ec8ea5bb30a5c8ed50f3.tar.gz hl2h-4019880cf113a2a13460ec8ea5bb30a5c8ed50f3.zip | |
Items: hide full ammo pickups; robust close-target projection
- Resolve m_iAmmo and m_hMyWeapons arrays from the client recv tables.
- "Only if usable" now compares m_iAmmo[type] against sk_max_* (stock caps as
fallback: RPG rockets 3, AR2 60, ...). Full ammo hides the pickup, including
RPG rockets and fallen weapons converted to ammo.
- Inventory now comes from m_hMyWeapons directly, so fallen weapons are ammo
before the player equips the matching weapon.
- Projection: fall back to local math when ScreenPosition claims "behind" for
a point with positive view depth, and build NPC boxes from vertical samples
so feet behind the near plane no longer drop the whole target. Log the first
8 near-target projection failures for diagnosis.
| -rw-r--r-- | CONTEXT.md | 18 | ||||
| -rw-r--r-- | src/esp.cpp | 236 | ||||
| -rw-r--r-- | src/hl2h.h | 2 | ||||
| -rw-r--r-- | src/netvars.cpp | 13 |
4 files changed, 233 insertions, 36 deletions
| @@ -232,7 +232,11 @@ vocabulary / decisions. | |||
| 232 | (>1e7). A point on the view axis (e.g. directly below the player's feet) | 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 | 233 | can make `IVDebugOverlay::ScreenPosition` return NaN/huge values; casting |
| 234 | those to int corrupted the ISurface clip state and made the whole overlay | 234 | those to int corrupted the ISurface clip state and made the whole overlay |
| 235 | disappear for that frame. | 235 | disappear for that frame. If the overlay reports "behind" for a point whose |
| 236 | view-space depth is actually positive, a local projection is used instead. | ||
| 237 | - NPC boxes are built from several vertical samples, so a target whose feet | ||
| 238 | are behind the near plane (standing over it) still gets a box from the | ||
| 239 | visible span instead of being dropped. | ||
| 236 | - Projectile radius rings are drawn independently of the centre marker, so a | 240 | - 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 | 241 | grenade behind the camera/off-screen still shows its blast circle when any |
| 238 | part of the ring projects. | 242 | part of the ring projects. |
| @@ -606,10 +610,14 @@ vocabulary / decisions. | |||
| 606 | - A world weapon whose type is already in the local player's inventory is | 610 | - A world weapon whose type is already in the local player's inventory is |
| 607 | reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Owner handles are | 611 | reclassified as ammo (`SMG ammo`, `AR2 ammo`, ...). Owner handles are |
| 608 | unreliable on this client (`m_hOwnerEntity`/`m_hOwner` resolve to -1), so | 612 | unreliable on this client (`m_hOwnerEntity`/`m_hOwner` resolve to -1), so |
| 609 | the inventory mask is accumulated from every weapon type the player has | 613 | the inventory is read directly from the local `m_hMyWeapons[48]` handle |
| 610 | held (client `m_hActiveWeapon`, with the server handle as fallback) since | 614 | array (and remembered active types as a fallback). NPC-held weapons are |
| 611 | the current local entity appeared. NPC-held weapons are therefore ignored. | 615 | therefore ignored. Weapons the player does not own stay under the Weapons |
| 612 | Weapons the player does not own stay under the Weapons category. | 616 | category. |
| 617 | - Ammo pickups also honour "Only if usable": the reserve count from | ||
| 618 | `m_iAmmo[type]` is compared with the `sk_max_*` cvar (stock caps as fallback, | ||
| 619 | e.g. RPG rockets = 3). Full ammo hides the pickup; if either value cannot | ||
| 620 | be read the item stays visible. | ||
| 613 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, | 621 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, |
| 614 | world weapon pickups, the two supply-crate models and generic items. | 622 | world weapon pickups, the two supply-crate models and generic items. |
| 615 | - Each tag is a colored marker with a name and optional distance, filtered by | 623 | - 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 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 | ||
| 55 | bool 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. | 57 | static 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 | ||
| 80 | bool 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 { | |||
| 2088 | struct ItemTarget { | 2142 | struct 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 | ||
| 2293 | static 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). | ||
| 2310 | static 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 | |||
| 2351 | static 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. | ||
| 2364 | static 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 | |||
| 2398 | static 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 | |||
| 2238 | static int classify_item(const char *model, const char *cls, char *label, | 2404 | static 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)) |
| @@ -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 | } |
