summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 23:26:53 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 23:26:53 +0100
commit074e315c8550712dce0c7b530a90eee6aca29e63 (patch)
treefc5d6ecf6dce582971f0c3878b68f4714b8e6fba
parentChams: working alpha via RenderView blend and a custom VMT (diff)
downloadhl2h-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.md35
-rw-r--r--src/esp.cpp158
-rw-r--r--src/gui.cpp3
-rw-r--r--src/hl2h.h5
-rw-r--r--src/menu.cpp9
-rw-r--r--src/server.cpp18
-rw-r--r--src/vischeck.cpp21
7 files changed, 232 insertions, 17 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 1e860bf..889c3ab 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -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.
1326static 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
1312bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { 1343bool 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.
3090static 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
3048void esp_draw_projectiles() { 3195void 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);
diff --git a/src/hl2h.h b/src/hl2h.h
index 34bf04f..32cee8f 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -230,7 +230,7 @@ struct FeatureState {
230extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, 230extern 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;
233extern bool g_proj_fuse, g_proj_radius; 233extern 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);
319uint32_t server_player_weapon(int idx, int slot); 319uint32_t server_player_weapon(int idx, int slot);
320float server_grenade_detonate(int idx); 320float server_grenade_detonate(int idx);
321float server_grenade_radius(int idx); 321float server_grenade_radius(int idx);
322int server_item_juice(int idx);
322int server_entity_count(); 323int server_entity_count();
323void *server_entity_base(int idx); 324void *server_entity_base(int idx);
324const char *server_entity_classname(int idx); 325const char *server_entity_classname(int idx);
@@ -366,6 +367,8 @@ void set_silent_restore(const QAngle &angles);
366bool is_visible(const Vector &eye, const Vector &point, 367bool is_visible(const Vector &eye, const Vector &point,
367 void *self = nullptr, void *target = nullptr); 368 void *self = nullptr, void *target = nullptr);
368void *trace_entity(const Vector &start, const Vector &end, void *ignore); 369void *trace_entity(const Vector &start, const Vector &end, void *ignore);
370bool trace_solid_segment(const Vector &start, const Vector &end, void *ignore,
371 Vector &out_end, Vector &out_normal);
369float ground_distance(const Vector &origin, void *ignore, float max_dist); 372float ground_distance(const Vector &origin, void *ignore, float max_dist);
370bool point_ground(const Vector &point, void *ignore); 373bool 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
63FeatureState g_projectiles{true, 0}; // grenade / rocket ESP 63FeatureState g_projectiles{true, 0}; // grenade / rocket ESP
64bool g_proj_fuse = true; 64bool g_proj_fuse = true;
65bool g_proj_radius = true; 65bool g_proj_radius = true;
66bool g_proj_traj = true;
66FeatureState g_chams{false, 0}; 67FeatureState g_chams{false, 0};
67ChamsGroup g_chams_hostile{true, 3, {255, 60, 60, 255}, true, 3, 68ChamsGroup 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;
58static int g_srv_armor = -1; 58static int g_srv_armor = -1;
59static int g_srv_detonate = -1; 59static int g_srv_detonate = -1;
60static int g_srv_grenade_radius = -1; 60static int g_srv_grenade_radius = -1;
61static int g_srv_juice = -1;
61static int g_srv_ammo = -1; 62static int g_srv_ammo = -1;
62static int g_srv_myweapons = -1; 63static int g_srv_myweapons = -1;
63static bool g_srv_ready = false; 64static 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.
482int 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.
237bool 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.