diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 23:26:53 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 23:26:53 +0100 |
| commit | 074e315c8550712dce0c7b530a90eee6aca29e63 (patch) | |
| tree | fc5d6ecf6dce582971f0c3878b68f4714b8e6fba | |
| parent | Chams: working alpha via RenderView blend and a custom VMT (diff) | |
| download | hl2h-074e315c8550712dce0c7b530a90eee6aca29e63.tar.gz hl2h-074e315c8550712dce0c7b530a90eee6aca29e63.zip | |
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.
| -rw-r--r-- | CONTEXT.md | 35 | ||||
| -rw-r--r-- | src/esp.cpp | 158 | ||||
| -rw-r--r-- | src/gui.cpp | 3 | ||||
| -rw-r--r-- | src/hl2h.h | 5 | ||||
| -rw-r--r-- | src/menu.cpp | 9 | ||||
| -rw-r--r-- | src/server.cpp | 18 | ||||
| -rw-r--r-- | src/vischeck.cpp | 21 |
7 files changed, 232 insertions, 17 deletions
| @@ -201,9 +201,10 @@ vocabulary / decisions. | |||
| 201 | 201 | ||
| 202 | - Every NPC is classified for ESP and target selection: | 202 | - Every NPC is classified for ESP and target selection: |
| 203 | - `IMMUNE` — server `m_takedamage` is `DAMAGE_NO`/`DAMAGE_EVENTS_ONLY`. | 203 | - `IMMUNE` — server `m_takedamage` is `DAMAGE_NO`/`DAMAGE_EVENTS_ONLY`. |
| 204 | - `ARMORED` — gunship, strider, dropship: their `OnTakeDamage` only accepts | 204 | - `ARMORED` — gunship, strider, dropship, hunter-chopper: their |
| 205 | blast damage (`DMG_BLAST`, with a damage threshold on gunships and a | 205 | `OnTakeDamage` only accepts blast damage (`DMG_BLAST`, with a damage |
| 206 | distance check on striders). Only the RPG passes; bullets/grenades do not. | 206 | threshold on gunships and a distance check on striders). Only the RPG |
| 207 | passes; bullets/grenades do not. | ||
| 207 | - `VULNERABLE` — headcrab, antlion, manhack: punting them with the gravity | 208 | - `VULNERABLE` — headcrab, antlion, manhack: punting them with the gravity |
| 208 | gun / physics impact damage kills them. | 209 | gun / physics impact damage kills them. |
| 209 | - `NORMAL` — everything else. | 210 | - `NORMAL` — everything else. |
| @@ -213,7 +214,10 @@ vocabulary / decisions. | |||
| 213 | created at glyph size 10 alongside the 16 px body font (`g_font_small`). | 214 | created at glyph size 10 alongside the 16 px body font (`g_font_small`). |
| 214 | - Aimbot/silent aim/triggerbot/melee skip `IMMUNE` targets always and | 215 | - Aimbot/silent aim/triggerbot/melee skip `IMMUNE` targets always and |
| 215 | `ARMORED` targets unless the active weapon is the RPG | 216 | `ARMORED` targets unless the active weapon is the RPG |
| 216 | (`active_weapon_contains` for `rocket_launcher`/`rpg`). | 217 | (`active_weapon_contains` for `rocket_launcher`/`rpg`). With the RPG |
| 218 | equipped, armoured candidates are sorted before normal enemies (a | ||
| 219 | `fov - 1000` rank key), so a strider/gunship is acquired even when a normal | ||
| 220 | NPC is closer to the crosshair. | ||
| 217 | - Server `m_takedamage` is a `char` in the 2013 SDK | 221 | - Server `m_takedamage` is a `char` in the 2013 SDK |
| 218 | (`CNetworkVarForDerived(char, m_takedamage)`, `FIELD_CHARACTER`), so it is | 222 | (`CNetworkVarForDerived(char, m_takedamage)`, `FIELD_CHARACTER`), so it is |
| 219 | resolved with that type (int fallback) and read as a byte. `0/1` means no | 223 | resolved with that type (int fallback) and read as a byte. `0/1` means no |
| @@ -402,7 +406,11 @@ vocabulary / decisions. | |||
| 402 | `IN_ATTACK`: the primary attack launches a held object along | 406 | `IN_ATTACK`: the primary attack launches a held object along |
| 403 | `EyeVectors()`. No aim-punch compensation is applied there (EyeVectors does | 407 | `EyeVectors()`. No aim-punch compensation is applied there (EyeVectors does |
| 404 | not add punch); the crowbar path keeps it. Without a held object the primary | 408 | not add punch); the crowbar path keeps it. Without a held object the primary |
| 405 | is a short physics punch. | 409 | is a short physics punch. The citadel supercharges the gravity gun |
| 410 | (`d3_citadel_03`, `d3_citadel_04`, `d3_breen_01`; detected through | ||
| 411 | `IVEngineClient::GetLevelName`, slot 51), and there it is treated as a | ||
| 412 | normal weapon: the `VULNERABLE`-only filter is dropped, so any damageable | ||
| 413 | hostile NPC is selected. | ||
| 406 | - Map transitions often switch the active weapon away from the crowbar. The | 414 | - Map transitions often switch the active weapon away from the crowbar. The |
| 407 | state change is logged as `melee: active ... crowbar=0/1` (normal log), and | 415 | state change is logged as `melee: active ... crowbar=0/1` (normal log), and |
| 408 | a one-time toast says to equip the crowbar. If the client weapon model is | 416 | a one-time toast says to equip the crowbar. If the client weapon model is |
| @@ -694,7 +702,10 @@ vocabulary / decisions. | |||
| 694 | Reading at the raw offset shifted every count by one type (e.g. RPG read the | 702 | Reading at the raw offset shifted every count by one type (e.g. RPG read the |
| 695 | buckshot count 30 against a max of 3, permanently hiding rockets). | 703 | buckshot count 30 against a max of 3, permanently hiding rockets). |
| 696 | - Chargers and ammo/supply crates are dispensers and are exempt from the | 704 | - Chargers and ammo/supply crates are dispensers and are exempt from the |
| 697 | fullness filter. | 705 | fullness filter. Empty health/suit chargers are still hidden: both |
| 706 | `CWallHealth` and `CNewRecharge` expose `m_iJuice` (server datamap | ||
| 707 | `FIELD_INTEGER`, resolved at offset 0x47c in this build), and a value of 0 | ||
| 708 | removes the charger tag until it recharges. `-1` (unreadable) fails open. | ||
| 698 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, | 709 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, |
| 699 | world weapon pickups, the two supply-crate models and generic items. | 710 | world weapon pickups, the two supply-crate models and generic items. |
| 700 | - Each tag is a colored marker with a name and optional distance, filtered by | 711 | - Each tag is a colored marker with a name and optional distance, filtered by |
| @@ -733,7 +744,17 @@ vocabulary / decisions. | |||
| 733 | is a 24-segment horizontal circle projected at the projectile height, with | 744 | is a 24-segment horizontal circle projected at the projectile height, with |
| 734 | every segment near-plane/screen clipped independently, so it still draws | 745 | every segment near-plane/screen clipped independently, so it still draws |
| 735 | when the grenade itself is off-screen or behind the camera. | 746 | when the grenade itself is off-screen or behind the camera. |
| 736 | Toggles: Fuse timer, Blast radius. | 747 | Toggles: Fuse timer, Blast radius, Grenade trajectory |
| 748 | (`[projectiles] trajectory=1`). | ||
| 749 | - Grenade trajectory: while the frag weapon is active, simulate the throw | ||
| 750 | from `weapon_frag.cpp` (eye + forward*18 + right*8, player velocity + | ||
| 751 | (forward + 0.1 z)*1200), gravity 600, dt 0.02, restitution 0.35, friction | ||
| 752 | 0.35. Each step traces with `trace_solid_segment` (EngineTraceClient003 slot | ||
| 753 | 4, `MASK_PLAYERSOLID`, local player ignored); hits reflect about the impact | ||
| 754 | normal, and an upward normal with a slow vertical speed marks the landing. | ||
| 755 | The polyline, landing cross and a 300-unit blast-radius ring at the landing | ||
| 756 | are drawn with the same near-plane-clipped projector as the radius ring. | ||
| 757 | Verified on `:99` (the line arcs, bounces off a tunnel wall and lands). | ||
| 737 | 758 | ||
| 738 | ## Roadmap / TODO | 759 | ## Roadmap / TODO |
| 739 | 760 | ||
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) { | |||
| 320 | if (td == 0 || td == 1) // DAMAGE_NO / DAMAGE_EVENTS_ONLY | 320 | if (td == 0 || td == 1) // DAMAGE_NO / DAMAGE_EVENTS_ONLY |
| 321 | return NPC_DMG_IMMUNE; | 321 | return NPC_DMG_IMMUNE; |
| 322 | if (model_has(model, "gunship") || model_has(model, "combine_strider") || | 322 | if (model_has(model, "gunship") || model_has(model, "combine_strider") || |
| 323 | model_has(model, "dropship")) | 323 | model_has(model, "dropship") || model_has(model, "helicopter") || |
| 324 | model_has(model, "chopper")) | ||
| 324 | return NPC_DMG_ARMORED; | 325 | return NPC_DMG_ARMORED; |
| 325 | if (model_has(model, "headcrab") || model_has(model, "antlion") || | 326 | if (model_has(model, "headcrab") || model_has(model, "antlion") || |
| 326 | model_has(model, "manhack")) | 327 | model_has(model, "manhack")) |
| @@ -822,6 +823,12 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, | |||
| 822 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) | 823 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) |
| 823 | return false; | 824 | return false; |
| 824 | 825 | ||
| 826 | // With the RPG equipped, armoured units (striders, gunships, dropships, | ||
| 827 | // hunter-choppers) are the ones ordinary shots cannot hurt, so prefer them | ||
| 828 | // over normal NPCs when acquiring. | ||
| 829 | bool rpg = active_weapon_contains(local_ent, "rocket_launcher") || | ||
| 830 | active_weapon_contains(local_ent, "rpg"); | ||
| 831 | |||
| 825 | auto try_target = [&](int idx, bool enforce_fov, Vector &pt, | 832 | auto try_target = [&](int idx, bool enforce_fov, Vector &pt, |
| 826 | float &fov) -> bool { | 833 | float &fov) -> bool { |
| 827 | if (idx <= 0 || idx == local) | 834 | if (idx <= 0 || idx == local) |
| @@ -899,6 +906,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, | |||
| 899 | int idx; | 906 | int idx; |
| 900 | float fov; | 907 | float fov; |
| 901 | Vector approx; | 908 | Vector approx; |
| 909 | bool armored; | ||
| 910 | float key; | ||
| 902 | }; | 911 | }; |
| 903 | Candidate cands[16]; | 912 | Candidate cands[16]; |
| 904 | int ncands = 0; | 913 | int ncands = 0; |
| @@ -928,6 +937,7 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, | |||
| 928 | continue; | 937 | continue; |
| 929 | if (!npc_can_be_damaged(idx, model)) | 938 | if (!npc_can_be_damaged(idx, model)) |
| 930 | continue; | 939 | continue; |
| 940 | bool armored = npc_damage_class(idx, model) == NPC_DMG_ARMORED; | ||
| 931 | 941 | ||
| 932 | float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; | 942 | float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; |
| 933 | if (!(maxz > 4.0f && maxz < 512.0f)) | 943 | if (!(maxz > 4.0f && maxz < 512.0f)) |
| @@ -940,16 +950,17 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, | |||
| 940 | float fov = getfov(view, angle_to(g_cam_eye, approx)); | 950 | float fov = getfov(view, angle_to(g_cam_eye, approx)); |
| 941 | if (fov > max_fov) | 951 | if (fov > max_fov) |
| 942 | continue; | 952 | continue; |
| 953 | float key = (rpg && armored) ? fov - 1000.0f : fov; | ||
| 943 | 954 | ||
| 944 | int pos = ncands; | 955 | int pos = ncands; |
| 945 | while (pos > 0 && cands[pos - 1].fov > fov) | 956 | while (pos > 0 && cands[pos - 1].key > key) |
| 946 | pos--; | 957 | pos--; |
| 947 | if (ncands < 16) | 958 | if (ncands < 16) |
| 948 | ncands++; | 959 | ncands++; |
| 949 | for (int j = ncands - 1; j > pos; j--) | 960 | for (int j = ncands - 1; j > pos; j--) |
| 950 | cands[j] = cands[j - 1]; | 961 | cands[j] = cands[j - 1]; |
| 951 | if (pos < 16) | 962 | if (pos < 16) |
| 952 | cands[pos] = {idx, fov, approx}; | 963 | cands[pos] = {idx, fov, approx, armored, key}; |
| 953 | } | 964 | } |
| 954 | 965 | ||
| 955 | if (ncands == 0) { | 966 | if (ncands == 0) { |
| @@ -1309,6 +1320,26 @@ bool gravity_gun_active(void *local_ent) { | |||
| 1309 | (model && strcasestr(model, "physcannon")); | 1320 | (model && strcasestr(model, "physcannon")); |
| 1310 | } | 1321 | } |
| 1311 | 1322 | ||
| 1323 | // The citadel supercharges the gravity gun (d3_citadel_03 onward). It can then | ||
| 1324 | // punt any NPC, so the melee aimbot should not restrict itself to | ||
| 1325 | // physics-vulnerable units. | ||
| 1326 | static bool supercharged_gravity_gun() { | ||
| 1327 | if (!g_iface.engine) | ||
| 1328 | return false; | ||
| 1329 | using Fn = const char *(*)(void *); | ||
| 1330 | const char *m = vfunc<Fn>(g_iface.engine, 51)(g_iface.engine); | ||
| 1331 | if (!m) | ||
| 1332 | return false; | ||
| 1333 | bool supe = strstr(m, "d3_citadel_03") || strstr(m, "d3_citadel_04") || | ||
| 1334 | strstr(m, "d3_breen_01"); | ||
| 1335 | static const char *logged_map = nullptr; | ||
| 1336 | if (logged_map != m) { | ||
| 1337 | logged_map = m; | ||
| 1338 | logf("melee: gravity gun map=%s supercharged=%d", m, (int)supe); | ||
| 1339 | } | ||
| 1340 | return supe; | ||
| 1341 | } | ||
| 1342 | |||
| 1312 | bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { | 1343 | bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { |
| 1313 | if (!g_iface.entitylist || g_net.origin < 0) | 1344 | if (!g_iface.entitylist || g_net.origin < 0) |
| 1314 | return false; | 1345 | return false; |
| @@ -1324,6 +1355,7 @@ bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { | |||
| 1324 | 1355 | ||
| 1325 | // The punted object can travel much farther than the crowbar. | 1356 | // The punted object can travel much farther than the crowbar. |
| 1326 | float max_range = gravity ? 1200.0f : MELEE_RANGE; | 1357 | float max_range = gravity ? 1200.0f : MELEE_RANGE; |
| 1358 | bool supercharged = gravity && supercharged_gravity_gun(); | ||
| 1327 | 1359 | ||
| 1328 | using Fn = int (*)(void *); | 1360 | using Fn = int (*)(void *); |
| 1329 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | 1361 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); |
| @@ -1363,8 +1395,10 @@ bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { | |||
| 1363 | continue; | 1395 | continue; |
| 1364 | if (!npc_can_be_damaged(idx, model)) | 1396 | if (!npc_can_be_damaged(idx, model)) |
| 1365 | continue; | 1397 | continue; |
| 1366 | // The gravity gun is only the tool for physics-vulnerable units. | 1398 | // The normal gravity gun is only the tool for physics-vulnerable units; |
| 1367 | if (gravity && npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) | 1399 | // the citadel version behaves like a normal weapon. |
| 1400 | if (gravity && !supercharged && | ||
| 1401 | npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) | ||
| 1368 | continue; | 1402 | continue; |
| 1369 | 1403 | ||
| 1370 | Vector origin{*(float *)(e + g_net.origin), | 1404 | Vector origin{*(float *)(e + g_net.origin), |
| @@ -2747,6 +2781,11 @@ static void items_collect(int local_idx) { | |||
| 2747 | int kind = classify_item(model, cls, label, sizeof(label)); | 2781 | int kind = classify_item(model, cls, label, sizeof(label)); |
| 2748 | if (kind < 0) | 2782 | if (kind < 0) |
| 2749 | continue; | 2783 | continue; |
| 2784 | // Health/suit chargers are dispensers that stay useful after a refill, but | ||
| 2785 | // an empty one cannot give anything. m_iJuice is 0 when depleted (until it | ||
| 2786 | // recharges); only hide the tag once the server value is readable. | ||
| 2787 | if (strcasestr(label, "charger") && server_item_juice(idx) == 0) | ||
| 2788 | continue; | ||
| 2750 | 2789 | ||
| 2751 | Vector origin{*(float *)((uintptr_t)ent + g_net.origin), | 2790 | Vector origin{*(float *)((uintptr_t)ent + g_net.origin), |
| 2752 | *(float *)((uintptr_t)ent + g_net.origin + 4), | 2791 | *(float *)((uintptr_t)ent + g_net.origin + 4), |
| @@ -3045,6 +3084,114 @@ static void projs_collect(int local_idx) { | |||
| 3045 | } | 3084 | } |
| 3046 | } | 3085 | } |
| 3047 | 3086 | ||
| 3087 | // Frag-grenade path preview: throw velocity from weapon_frag.cpp (eye/right | ||
| 3088 | // offset, forward*1200 + 0.1 z), gravity 600, and a simple bounce model. | ||
| 3089 | // Shows the bounces and the first landing with the blast-radius ring. | ||
| 3090 | static void draw_grenade_trajectory(int local_idx) { | ||
| 3091 | if (!g_proj_traj || !g_iface.entitylist) | ||
| 3092 | return; | ||
| 3093 | void *le = entitylist_get(local_idx); | ||
| 3094 | if (!le) | ||
| 3095 | return; | ||
| 3096 | if (!active_weapon_contains(le, "frag") && | ||
| 3097 | !active_weapon_contains(le, "w_grenade")) | ||
| 3098 | return; | ||
| 3099 | |||
| 3100 | QAngle va = engine_view_angles(); | ||
| 3101 | float pitch = va.x * PI_F / 180.0f; | ||
| 3102 | float yaw = va.y * PI_F / 180.0f; | ||
| 3103 | float cp = cosf(pitch), sp = sinf(pitch); | ||
| 3104 | float cy = cosf(yaw), sy = sinf(yaw); | ||
| 3105 | Vector fwd{cp * cy, cp * sy, -sp}; | ||
| 3106 | Vector right{sy, -cy, 0.0f}; | ||
| 3107 | |||
| 3108 | Vector src{g_cam_eye.x + fwd.x * 18.0f + right.x * 8.0f, | ||
| 3109 | g_cam_eye.y + fwd.y * 18.0f + right.y * 8.0f, | ||
| 3110 | g_cam_eye.z + fwd.z * 18.0f}; | ||
| 3111 | Vector vel{}; | ||
| 3112 | if (g_net.velocity >= 0) { | ||
| 3113 | const float *pv = (const float *)((uintptr_t)le + g_net.velocity); | ||
| 3114 | vel = Vector{pv[0], pv[1], pv[2]}; | ||
| 3115 | } | ||
| 3116 | vel.x += fwd.x * 1200.0f; | ||
| 3117 | vel.y += fwd.y * 1200.0f; | ||
| 3118 | vel.z += fwd.z * 1200.0f + 120.0f; // vForward[2] += 0.1 | ||
| 3119 | |||
| 3120 | const float gravity = 600.0f; | ||
| 3121 | const float dt = 0.02f; | ||
| 3122 | const float restitution = 0.35f; | ||
| 3123 | const float friction = 0.35f; | ||
| 3124 | |||
| 3125 | Vector pts[128]; | ||
| 3126 | int n = 0; | ||
| 3127 | Vector pos = src; | ||
| 3128 | pts[n++] = pos; | ||
| 3129 | bool landed = false; | ||
| 3130 | for (int step = 0; step < 240 && n < 127; step++) { | ||
| 3131 | Vector nv = vel; | ||
| 3132 | nv.z -= gravity * dt; | ||
| 3133 | Vector next{pos.x + nv.x * dt, pos.y + nv.y * dt, pos.z + nv.z * dt}; | ||
| 3134 | Vector hit_end{}, normal{}; | ||
| 3135 | if (trace_solid_segment(pos, next, le, hit_end, normal)) { | ||
| 3136 | pts[n++] = hit_end; | ||
| 3137 | float vn = vel.x * normal.x + vel.y * normal.y + vel.z * normal.z; | ||
| 3138 | Vector vt{vel.x - vn * normal.x, vel.y - vn * normal.y, | ||
| 3139 | vel.z - vn * normal.z}; | ||
| 3140 | float keep = 1.0f - friction; | ||
| 3141 | vel.x = vt.x * keep - normal.x * vn * restitution; | ||
| 3142 | vel.y = vt.y * keep - normal.y * vn * restitution; | ||
| 3143 | vel.z = vt.z * keep - normal.z * vn * restitution; | ||
| 3144 | float vlen2 = vel.x * vel.x + vel.y * vel.y + vel.z * vel.z; | ||
| 3145 | pos.x = hit_end.x + normal.x * 1.0f; | ||
| 3146 | pos.y = hit_end.y + normal.y * 1.0f; | ||
| 3147 | pos.z = hit_end.z + normal.z * 1.0f; | ||
| 3148 | if (normal.z > 0.6f && (vn > -80.0f || vlen2 < 2500.0f)) { | ||
| 3149 | landed = true; | ||
| 3150 | break; | ||
| 3151 | } | ||
| 3152 | } else { | ||
| 3153 | pos = next; | ||
| 3154 | vel = nv; | ||
| 3155 | pts[n++] = pos; | ||
| 3156 | } | ||
| 3157 | } | ||
| 3158 | |||
| 3159 | surf_color(255, 210, 90, 200); | ||
| 3160 | for (int i = 0; i + 1 < n; i++) { | ||
| 3161 | int x0, y0, x1, y1; | ||
| 3162 | if (project_segment(pts[i], pts[i + 1], x0, y0, x1, y1)) | ||
| 3163 | surf_line(x0, y0, x1, y1); | ||
| 3164 | } | ||
| 3165 | |||
| 3166 | Vector land = landed ? pos : pts[n - 1]; | ||
| 3167 | float ssx, ssy; | ||
| 3168 | if (world_to_screen(land, ssx, ssy)) { | ||
| 3169 | int x = (int)ssx, y = (int)ssy; | ||
| 3170 | surf_color(255, 210, 90, 230); | ||
| 3171 | surf_rect(0, x - 3, y - 3, x + 3, y + 3); | ||
| 3172 | surf_line(x - 9, y, x - 4, y); | ||
| 3173 | surf_line(x + 4, y, x + 9, y); | ||
| 3174 | surf_line(x, y - 9, x, y - 4); | ||
| 3175 | surf_line(x, y + 4, x, y + 9); | ||
| 3176 | } | ||
| 3177 | if (landed) { | ||
| 3178 | const int SEG = 24; | ||
| 3179 | const float radius = 300.0f; // CBaseGrenade m_DmgRadius | ||
| 3180 | surf_color(255, 120, 40, 120); | ||
| 3181 | for (int k = 0; k < SEG; k++) { | ||
| 3182 | float a0 = (float)k * 2.0f * PI_F / (float)SEG; | ||
| 3183 | float a1 = (float)(k + 1) * 2.0f * PI_F / (float)SEG; | ||
| 3184 | Vector p0{land.x + cosf(a0) * radius, land.y + sinf(a0) * radius, | ||
| 3185 | land.z + 1.0f}; | ||
| 3186 | Vector p1{land.x + cosf(a1) * radius, land.y + sinf(a1) * radius, | ||
| 3187 | land.z + 1.0f}; | ||
| 3188 | int x0, y0, x1, y1; | ||
| 3189 | if (project_segment(p0, p1, x0, y0, x1, y1)) | ||
| 3190 | surf_line(x0, y0, x1, y1); | ||
| 3191 | } | ||
| 3192 | } | ||
| 3193 | } | ||
| 3194 | |||
| 3048 | void esp_draw_projectiles() { | 3195 | void esp_draw_projectiles() { |
| 3049 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) | 3196 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) |
| 3050 | return; | 3197 | return; |
| @@ -3055,6 +3202,7 @@ void esp_draw_projectiles() { | |||
| 3055 | return; | 3202 | return; |
| 3056 | } | 3203 | } |
| 3057 | projs_collect(engine_get_local_player()); | 3204 | projs_collect(engine_get_local_player()); |
| 3205 | draw_grenade_trajectory(engine_get_local_player()); | ||
| 3058 | for (int i = 0; i < g_nprojs; i++) { | 3206 | for (int i = 0; i < g_nprojs; i++) { |
| 3059 | const ProjTarget &p = g_projs[i]; | 3207 | const ProjTarget &p = g_projs[i]; |
| 3060 | int r = 255, g = 170, b = 60; | 3208 | int r = 255, g = 170, b = 60; |
diff --git a/src/gui.cpp b/src/gui.cpp index fe82762..454e2cb 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -454,6 +454,9 @@ static void build_cards() { | |||
| 454 | &g_proj_fuse); | 454 | &g_proj_fuse); |
| 455 | add_toggle(proj, "Blast radius", | 455 | add_toggle(proj, "Blast radius", |
| 456 | "Draw the projectile's damage radius on the ground.", &g_proj_radius); | 456 | "Draw the projectile's damage radius on the ground.", &g_proj_radius); |
| 457 | add_toggle(proj, "Grenade trajectory", | ||
| 458 | "Predict the frag grenade path, bounces and landing point.", | ||
| 459 | &g_proj_traj); | ||
| 457 | 460 | ||
| 458 | int chams = add_card(2, &g_chams, "Chams", | 461 | int chams = add_card(2, &g_chams, "Chams", |
| 459 | "Per-relationship model colours.", true); | 462 | "Per-relationship model colours.", true); |
| @@ -230,7 +230,7 @@ struct FeatureState { | |||
| 230 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, | 230 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, |
| 231 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, | 231 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, |
| 232 | g_world_tint, g_fog, g_skybox, g_projectiles, g_chams; | 232 | g_world_tint, g_fog, g_skybox, g_projectiles, g_chams; |
| 233 | extern bool g_proj_fuse, g_proj_radius; | 233 | extern bool g_proj_fuse, g_proj_radius, g_proj_traj; |
| 234 | 234 | ||
| 235 | // Chams settings per relationship, and separate visible / through-wall | 235 | // Chams settings per relationship, and separate visible / through-wall |
| 236 | // (invisible) material and colour. The viewmodel has a single style. | 236 | // (invisible) material and colour. The viewmodel has a single style. |
| @@ -319,6 +319,7 @@ int server_player_ammo(int idx, int type); | |||
| 319 | uint32_t server_player_weapon(int idx, int slot); | 319 | uint32_t server_player_weapon(int idx, int slot); |
| 320 | float server_grenade_detonate(int idx); | 320 | float server_grenade_detonate(int idx); |
| 321 | float server_grenade_radius(int idx); | 321 | float server_grenade_radius(int idx); |
| 322 | int server_item_juice(int idx); | ||
| 322 | int server_entity_count(); | 323 | int server_entity_count(); |
| 323 | void *server_entity_base(int idx); | 324 | void *server_entity_base(int idx); |
| 324 | const char *server_entity_classname(int idx); | 325 | const char *server_entity_classname(int idx); |
| @@ -366,6 +367,8 @@ void set_silent_restore(const QAngle &angles); | |||
| 366 | bool is_visible(const Vector &eye, const Vector &point, | 367 | bool is_visible(const Vector &eye, const Vector &point, |
| 367 | void *self = nullptr, void *target = nullptr); | 368 | void *self = nullptr, void *target = nullptr); |
| 368 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); | 369 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); |
| 370 | bool trace_solid_segment(const Vector &start, const Vector &end, void *ignore, | ||
| 371 | Vector &out_end, Vector &out_normal); | ||
| 369 | float ground_distance(const Vector &origin, void *ignore, float max_dist); | 372 | float ground_distance(const Vector &origin, void *ignore, float max_dist); |
| 370 | bool point_ground(const Vector &point, void *ignore); | 373 | bool point_ground(const Vector &point, void *ignore); |
| 371 | 374 | ||
diff --git a/src/menu.cpp b/src/menu.cpp index 873f5ed..1064365 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -63,6 +63,7 @@ FeatureState g_skybox{false, 0}; // skybox.png image override | |||
| 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP | 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP |
| 64 | bool g_proj_fuse = true; | 64 | bool g_proj_fuse = true; |
| 65 | bool g_proj_radius = true; | 65 | bool g_proj_radius = true; |
| 66 | bool g_proj_traj = true; | ||
| 66 | FeatureState g_chams{false, 0}; | 67 | FeatureState g_chams{false, 0}; |
| 67 | ChamsGroup g_chams_hostile{true, 3, {255, 60, 60, 255}, true, 3, | 68 | ChamsGroup g_chams_hostile{true, 3, {255, 60, 60, 255}, true, 3, |
| 68 | {255, 60, 60, 255}}; | 69 | {255, 60, 60, 255}}; |
| @@ -241,9 +242,10 @@ void config_save() { | |||
| 241 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); | 242 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); |
| 242 | fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active, | 243 | fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active, |
| 243 | g_skybox.bind); | 244 | g_skybox.bind); |
| 244 | fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", | 245 | fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n" |
| 246 | "trajectory=%d\n", | ||
| 245 | g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, | 247 | g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, |
| 246 | (int)g_proj_radius); | 248 | (int)g_proj_radius, (int)g_proj_traj); |
| 247 | fprintf(f, "[chams]\nactive=%d\nbind=%d\n", g_chams.active, g_chams.bind); | 249 | fprintf(f, "[chams]\nactive=%d\nbind=%d\n", g_chams.active, g_chams.bind); |
| 248 | auto save_chams = [&](const char *name, const ChamsGroup &g) { | 250 | auto save_chams = [&](const char *name, const ChamsGroup &g) { |
| 249 | fprintf(f, "[%s]\nvis=%d\nvis_mat=%d\nvis_r=%d\nvis_g=%d\nvis_b=%d\n" | 251 | fprintf(f, "[%s]\nvis=%d\nvis_mat=%d\nvis_r=%d\nvis_g=%d\nvis_b=%d\n" |
| @@ -425,6 +427,8 @@ static bool config_load() { | |||
| 425 | g_proj_fuse = atoi(val) != 0; | 427 | g_proj_fuse = atoi(val) != 0; |
| 426 | else if (strcmp(key, "radius") == 0) | 428 | else if (strcmp(key, "radius") == 0) |
| 427 | g_proj_radius = atoi(val) != 0; | 429 | g_proj_radius = atoi(val) != 0; |
| 430 | else if (strcmp(key, "trajectory") == 0) | ||
| 431 | g_proj_traj = atoi(val) != 0; | ||
| 428 | } | 432 | } |
| 429 | if (chams_grp) { | 433 | if (chams_grp) { |
| 430 | ChamsGroup &g = *chams_grp; | 434 | ChamsGroup &g = *chams_grp; |
| @@ -702,6 +706,7 @@ void config_reset_defaults() { | |||
| 702 | g_projectiles = {true, 0}; | 706 | g_projectiles = {true, 0}; |
| 703 | g_proj_fuse = true; | 707 | g_proj_fuse = true; |
| 704 | g_proj_radius = true; | 708 | g_proj_radius = true; |
| 709 | g_proj_traj = true; | ||
| 705 | g_chams = {false, 0}; | 710 | g_chams = {false, 0}; |
| 706 | g_chams_hostile = {true, 3, {255, 60, 60, 255}, true, 3, | 711 | g_chams_hostile = {true, 3, {255, 60, 60, 255}, true, 3, |
| 707 | {255, 60, 60, 255}}; | 712 | {255, 60, 60, 255}}; |
diff --git a/src/server.cpp b/src/server.cpp index bae355e..6479b25 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -58,6 +58,7 @@ static int g_srv_mapname = -1; | |||
| 58 | static int g_srv_armor = -1; | 58 | static int g_srv_armor = -1; |
| 59 | static int g_srv_detonate = -1; | 59 | static int g_srv_detonate = -1; |
| 60 | static int g_srv_grenade_radius = -1; | 60 | static int g_srv_grenade_radius = -1; |
| 61 | static int g_srv_juice = -1; | ||
| 61 | static int g_srv_ammo = -1; | 62 | static int g_srv_ammo = -1; |
| 62 | static int g_srv_myweapons = -1; | 63 | static int g_srv_myweapons = -1; |
| 63 | static bool g_srv_ready = false; | 64 | static bool g_srv_ready = false; |
| @@ -162,6 +163,7 @@ bool resolve_server_netvars() { | |||
| 162 | // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). | 163 | // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). |
| 163 | g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); | 164 | g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); |
| 164 | g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); | 165 | g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); |
| 166 | g_srv_juice = datamap_offset("m_iJuice", FIELD_INTEGER); | ||
| 165 | // CBaseCombatCharacter arrays: ammo counts and the weapon handle list. | 167 | // CBaseCombatCharacter arrays: ammo counts and the weapon handle list. |
| 166 | g_srv_ammo = datamap_offset("m_iAmmo", FIELD_INTEGER, 0x40, 0x4000); | 168 | g_srv_ammo = datamap_offset("m_iAmmo", FIELD_INTEGER, 0x40, 0x4000); |
| 167 | g_srv_myweapons = datamap_offset("m_hMyWeapons", FIELD_EHANDLE); | 169 | g_srv_myweapons = datamap_offset("m_hMyWeapons", FIELD_EHANDLE); |
| @@ -171,13 +173,13 @@ bool resolve_server_netvars() { | |||
| 171 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " | 173 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " |
| 172 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " | 174 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " |
| 173 | "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d " | 175 | "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d " |
| 174 | "ammo=%d myweapons=%d", | 176 | "ammo=%d myweapons=%d juice=%d", |
| 175 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, | 177 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, |
| 176 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, | 178 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, |
| 177 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, | 179 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, |
| 178 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, | 180 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, |
| 179 | g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius, | 181 | g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius, |
| 180 | g_srv_ammo, g_srv_myweapons); | 182 | g_srv_ammo, g_srv_myweapons, g_srv_juice); |
| 181 | fail_logged = false; | 183 | fail_logged = false; |
| 182 | } else if (!fail_logged) { | 184 | } else if (!fail_logged) { |
| 183 | fail_logged = true; | 185 | fail_logged = true; |
| @@ -475,6 +477,18 @@ float server_grenade_radius(int idx) { | |||
| 475 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); | 477 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); |
| 476 | } | 478 | } |
| 477 | 479 | ||
| 480 | // CWallHealth / CNewRecharge share the same m_iJuice layout (the fields before | ||
| 481 | // it match), so one datamap offset works for both health and suit chargers. | ||
| 482 | int server_item_juice(int idx) { | ||
| 483 | if (!g_srv_ready || g_srv_juice < 0) | ||
| 484 | return -1; | ||
| 485 | void *base = nullptr; | ||
| 486 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 487 | if (!base) | ||
| 488 | return -1; | ||
| 489 | return *(int *)((uintptr_t)base + g_srv_juice); | ||
| 490 | } | ||
| 491 | |||
| 478 | // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap | 492 | // Server-side CBaseCombatCharacter::m_iAmmo array (reserve ammo). The datamap |
| 479 | // offset points at the CNetworkArray length header; the elements start 4 bytes | 493 | // offset points at the CNetworkArray length header; the elements start 4 bytes |
| 480 | // later (confirmed by the shifted current/max table). | 494 | // later (confirmed by the shifted current/max table). |
diff --git a/src/vischeck.cpp b/src/vischeck.cpp index c8c0942..6b531b9 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp | |||
| @@ -232,6 +232,27 @@ void *trace_entity(const Vector &start, const Vector &end, void *ignore) { | |||
| 232 | return tr.ent; | 232 | return tr.ent; |
| 233 | } | 233 | } |
| 234 | 234 | ||
| 235 | // First solid/world hit along a segment, for grenade bounce prediction. | ||
| 236 | // Returns false on a clear path; otherwise fills the impact point and normal. | ||
| 237 | bool trace_solid_segment(const Vector &start, const Vector &end, void *ignore, | ||
| 238 | Vector &out_end, Vector &out_normal) { | ||
| 239 | if (!g_iface.enginetrace) | ||
| 240 | return false; | ||
| 241 | RayCompat ray; | ||
| 242 | make_ray(ray, start, end); | ||
| 243 | GameTraceCompat tr{}; | ||
| 244 | EntityFilter filter(ignore); | ||
| 245 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 246 | GameTraceCompat *); | ||
| 247 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | ||
| 248 | MASK_PLAYERSOLID, &filter, &tr); | ||
| 249 | if (tr.startsolid || tr.fraction >= 1.0f) | ||
| 250 | return false; | ||
| 251 | out_end = tr.endpos; | ||
| 252 | out_normal = tr.normal; | ||
| 253 | return true; | ||
| 254 | } | ||
| 255 | |||
| 235 | // True when a downward point trace from just above `point` finds a standable | 256 | // True when a downward point trace from just above `point` finds a standable |
| 236 | // surface. Used by edge jump: a ledge has no floor within the probe, slopes and | 257 | // surface. Used by edge jump: a ledge has no floor within the probe, slopes and |
| 237 | // stairs do. | 258 | // stairs do. |
