summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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.
Diffstat (limited to '')
-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
6 files changed, 204 insertions, 10 deletions
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.