summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/esp.cpp283
-rw-r--r--src/gui.cpp15
-rw-r--r--src/hl2h.cpp11
-rw-r--r--src/hl2h.h15
-rw-r--r--src/menu.cpp39
-rw-r--r--src/netvars.cpp10
-rw-r--r--src/server.cpp49
7 files changed, 394 insertions, 28 deletions
diff --git a/src/esp.cpp b/src/esp.cpp
index 7baf2c8..c956d1b 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -898,7 +898,8 @@ static bool active_weapon_contains(void *local_ent, const char *needle) {
898 void *net = entity_networkable(idx); 898 void *net = entity_networkable(idx);
899 const char *cls = net ? entity_class_name(net, nullptr) : nullptr; 899 const char *cls = net ? entity_class_name(net, nullptr) : nullptr;
900 const char *model = wep ? entity_model_name(wep) : nullptr; 900 const char *model = wep ? entity_model_name(wep) : nullptr;
901 return (cls && strstr(cls, needle)) || (model && strstr(model, needle)); 901 return (cls && strcasestr(cls, needle)) ||
902 (model && strcasestr(model, needle));
902} 903}
903 904
904// Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the 905// Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the
@@ -1175,7 +1176,35 @@ bool melee_weapon_active(void *local_ent) {
1175 return crowbar; 1176 return crowbar;
1176} 1177}
1177 1178
1178bool melee_aimbot_apply(CUserCmd *cmd) { 1179bool gravity_gun_active(void *local_ent) {
1180 if (!local_ent)
1181 return false;
1182 if (active_weapon_contains(local_ent, "physcannon"))
1183 return true;
1184 // Same server fallback as the crowbar: map transitions can leave the
1185 // client activeweapon handle stale.
1186 int local = engine_get_local_player();
1187 if (local <= 0 || !g_iface.engineserver)
1188 return false;
1189 resolve_server_netvars();
1190 int si = server_active_weapon(local);
1191 if (si <= 0)
1192 return false;
1193 void *wep = entitylist_get(si);
1194 void *net = entity_networkable(si);
1195 const char *cls = net ? entity_class_name(net, nullptr) : nullptr;
1196 const char *model = wep ? entity_model_name(wep) : nullptr;
1197 if (!model) {
1198 void *sbase = nullptr;
1199 server_entity_health(si, nullptr, nullptr, &sbase);
1200 if (sbase)
1201 model = model_name_from_index(server_model_index(sbase));
1202 }
1203 return (cls && strcasestr(cls, "physcannon")) ||
1204 (model && strcasestr(model, "physcannon"));
1205}
1206
1207bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) {
1179 if (!g_iface.entitylist || g_net.origin < 0) 1208 if (!g_iface.entitylist || g_net.origin < 0)
1180 return false; 1209 return false;
1181 int local = engine_get_local_player(); 1210 int local = engine_get_local_player();
@@ -1188,6 +1217,9 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1188 *(int *)((uintptr_t)local_ent + g_net.health) <= 0) 1217 *(int *)((uintptr_t)local_ent + g_net.health) <= 0)
1189 return false; 1218 return false;
1190 1219
1220 // The punted object can travel much farther than the crowbar.
1221 float max_range = gravity ? 1200.0f : MELEE_RANGE;
1222
1191 using Fn = int (*)(void *); 1223 using Fn = int (*)(void *);
1192 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); 1224 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist);
1193 if (hi < 0 || hi > 4096) 1225 if (hi < 0 || hi > 4096)
@@ -1226,6 +1258,9 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1226 continue; 1258 continue;
1227 if (!npc_can_be_damaged(idx, model)) 1259 if (!npc_can_be_damaged(idx, model))
1228 continue; 1260 continue;
1261 // The gravity gun is only the tool for physics-vulnerable units.
1262 if (gravity && npc_damage_class(idx, model) != NPC_DMG_VULNERABLE)
1263 continue;
1229 1264
1230 Vector origin{*(float *)(e + g_net.origin), 1265 Vector origin{*(float *)(e + g_net.origin),
1231 *(float *)(e + g_net.origin + 4), 1266 *(float *)(e + g_net.origin + 4),
@@ -1265,7 +1300,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1265 if (closest.z < lo.z) closest.z = lo.z; 1300 if (closest.z < lo.z) closest.z = lo.z;
1266 if (closest.z > hiV.z) closest.z = hiV.z; 1301 if (closest.z > hiV.z) closest.z = hiV.z;
1267 float d = vec_dist(g_cam_eye, closest); 1302 float d = vec_dist(g_cam_eye, closest);
1268 if (d > MELEE_RANGE) 1303 if (d > max_range)
1269 continue; 1304 continue;
1270 1305
1271 Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f, 1306 Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f,
@@ -1298,7 +1333,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1298 float bestd = 1e9f; 1333 float bestd = 1e9f;
1299 for (int i = 0; i < np; i++) { 1334 for (int i = 0; i < np; i++) {
1300 float pd = vec_dist(g_cam_eye, pts[i]); 1335 float pd = vec_dist(g_cam_eye, pts[i]);
1301 if (pd > MELEE_RANGE || pd >= bestd) 1336 if (pd > max_range || pd >= bestd)
1302 continue; 1337 continue;
1303 if (!is_visible(g_cam_eye, pts[i], local_ent, tent)) 1338 if (!is_visible(g_cam_eye, pts[i], local_ent, tent))
1304 continue; 1339 continue;
@@ -1311,7 +1346,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1311 if (!have_aim) { 1346 if (!have_aim) {
1312 aim = c.center; 1347 aim = c.center;
1313 float ad = vec_dist(g_cam_eye, aim); 1348 float ad = vec_dist(g_cam_eye, aim);
1314 if (ad > MELEE_RANGE) { 1349 if (ad > max_range) {
1315 // The body is in range but its center is not: aim just inside the 1350 // The body is in range but its center is not: aim just inside the
1316 // closest point on the AABB. 1351 // closest point on the AABB.
1317 aim = c.closest; 1352 aim = c.closest;
@@ -1319,7 +1354,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1319 aim.y += (c.center.y - c.closest.y) * 0.25f; 1354 aim.y += (c.center.y - c.closest.y) * 0.25f;
1320 aim.z += (c.center.z - c.closest.z) * 0.25f; 1355 aim.z += (c.center.z - c.closest.z) * 0.25f;
1321 ad = vec_dist(g_cam_eye, aim); 1356 ad = vec_dist(g_cam_eye, aim);
1322 if (ad > MELEE_RANGE) 1357 if (ad > max_range)
1323 aim = c.closest; 1358 aim = c.closest;
1324 } 1359 }
1325 if (!is_visible(g_cam_eye, aim, local_ent, tent)) 1360 if (!is_visible(g_cam_eye, aim, local_ent, tent))
@@ -1328,8 +1363,10 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1328 1363
1329 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; 1364 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
1330 QAngle desired = angle_to(g_cam_eye, aim); 1365 QAngle desired = angle_to(g_cam_eye, aim);
1366 // The crowbar uses GetAutoaimVector (adds punch); the gravity gun's
1367 // launch uses EyeVectors (no punch), so only compensate for melee.
1331 float punch_x = 0.0f, punch_y = 0.0f; 1368 float punch_x = 0.0f, punch_y = 0.0f;
1332 if (punch_angles(punch_x, punch_y)) { 1369 if (!gravity && punch_angles(punch_x, punch_y)) {
1333 desired.x -= punch_x; 1370 desired.x -= punch_x;
1334 desired.y -= punch_y; 1371 desired.y -= punch_y;
1335 } 1372 }
@@ -1350,13 +1387,15 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1350 using SetViewFn = void (*)(void *, QAngle *); 1387 using SetViewFn = void (*)(void *, QAngle *);
1351 vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore); 1388 vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore);
1352 } 1389 }
1390 // Gravity gun primary attack launches the held object (EyeVectors);
1391 // secondary would drop it instead.
1353 cmd->buttons |= IN_ATTACK; 1392 cmd->buttons |= IN_ATTACK;
1354 1393
1355 static int logs = 0; 1394 static int logs = 0;
1356 if (logs < 10) { 1395 if (logs < 10) {
1357 logs++; 1396 logs++;
1358 logd("melee: swing idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", c.idx, 1397 logd("melee: %s idx=%d dist=%.1f aim=(%.1f %.1f %.1f)",
1359 c.dist, aim.x, aim.y, aim.z); 1398 gravity ? "punt" : "swing", c.idx, c.dist, aim.x, aim.y, aim.z);
1360 } 1399 }
1361 return true; 1400 return true;
1362 } 1401 }
@@ -1365,7 +1404,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1365 double now = esp_now(); 1404 double now = esp_now();
1366 if (now - last_none >= 2.0) { 1405 if (now - last_none >= 2.0) {
1367 last_none = now; 1406 last_none = now;
1368 logd("melee: no visible target within %.0f", MELEE_RANGE); 1407 logd("melee: no visible target within %.0f", max_range);
1369 } 1408 }
1370 return false; 1409 return false;
1371} 1410}
@@ -2331,6 +2370,30 @@ static void items_collect(int local_idx) {
2331 } 2370 }
2332} 2371}
2333 2372
2373// Authoritative local health/armor for the "only if usable" item filter.
2374// The health ceiling comes from the server player's m_iMaxHealth, not a
2375// hardcoded 100; when it cannot be read the filter fails open.
2376static void local_vitals(int &hp, int &maxhp, int &armor) {
2377 hp = maxhp = armor = -1;
2378 int local = engine_get_local_player();
2379 if (local <= 0)
2380 return;
2381 void *le = entitylist_get(local);
2382 if (le && g_net.health >= 0)
2383 hp = *(int *)((uintptr_t)le + g_net.health);
2384 if (g_iface.engineserver) {
2385 int smax = -1;
2386 int shp = server_entity_health(local, &smax, nullptr, nullptr);
2387 if (shp >= 0)
2388 hp = shp;
2389 if (smax > 0)
2390 maxhp = smax;
2391 armor = server_player_armor(local);
2392 }
2393 if (maxhp < 0 && le && g_net.maxhealth >= 0)
2394 maxhp = *(int *)((uintptr_t)le + g_net.maxhealth);
2395}
2396
2334void esp_draw_items() { 2397void esp_draw_items() {
2335 if (!g_surface || !g_iface.entitylist || g_net.origin < 0) 2398 if (!g_surface || !g_iface.entitylist || g_net.origin < 0)
2336 return; 2399 return;
@@ -2348,10 +2411,21 @@ void esp_draw_items() {
2348 } 2411 }
2349 items_collect(local); 2412 items_collect(local);
2350 2413
2414 int vit_hp = -1, vit_max = -1, vit_armor = -1;
2415 if (g_item_usable)
2416 local_vitals(vit_hp, vit_max, vit_armor);
2417
2351 for (int i = 0; i < g_nitems; i++) { 2418 for (int i = 0; i < g_nitems; i++) {
2352 const ItemTarget &t = g_item_targets[i]; 2419 const ItemTarget &t = g_item_targets[i];
2353 if (!item_kind_enabled(t.kind)) 2420 if (!item_kind_enabled(t.kind))
2354 continue; 2421 continue;
2422 if (g_item_usable) {
2423 if (t.kind == IK_HEALTH && vit_hp >= 0 && vit_max > 0 &&
2424 vit_hp >= vit_max)
2425 continue;
2426 if (t.kind == IK_ARMOR && vit_armor >= 0 && vit_armor >= 100)
2427 continue;
2428 }
2355 float sx, sy; 2429 float sx, sy;
2356 if (!world_to_screen(t.origin, sx, sy)) 2430 if (!world_to_screen(t.origin, sx, sy))
2357 continue; 2431 continue;
@@ -2402,3 +2476,192 @@ void esp_draw_items() {
2402 draw_text_col(tx, ty, text, r, g, b); 2476 draw_text_col(tx, ty, text, r, g, b);
2403 } 2477 }
2404} 2478}
2479
2480// ---------------------------------------------------------------------------
2481// projectile ESP: grenades, rockets, helicopter bombs, AR2 balls
2482// ---------------------------------------------------------------------------
2483
2484enum ProjKind { PK_FRAG, PK_RPG, PK_BOMB, PK_BALL };
2485
2486struct ProjTarget {
2487 int idx;
2488 int kind;
2489 Vector origin;
2490 float fuse; // seconds; < 0 = impact / no fuse
2491 float radius;
2492};
2493
2494static ProjTarget g_projs[32];
2495static int g_nprojs = 0;
2496
2497static float engine_curtime() {
2498 if (!g_iface.engine)
2499 return 0.0f;
2500 using TimeFn = float (*)(void *);
2501 return vfunc<TimeFn>(g_iface.engine, 14)(g_iface.engine);
2502}
2503
2504static const char *proj_name(int kind) {
2505 switch (kind) {
2506 case PK_FRAG: return "FRAG";
2507 case PK_RPG: return "RPG";
2508 case PK_BOMB: return "BOMB";
2509 case PK_BALL: return "AR2";
2510 default: return "PROJ";
2511 }
2512}
2513
2514static void projs_collect(int local_idx) {
2515 static double last_scan = 0.0;
2516 double now = esp_now();
2517 if (now - last_scan < 0.10)
2518 return;
2519 last_scan = now;
2520 g_nprojs = 0;
2521 if (!g_iface.entitylist || g_net.origin < 0)
2522 return;
2523 resolve_server_netvars();
2524 using Fn = int (*)(void *);
2525 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist);
2526 if (hi < 0 || hi > 4096)
2527 hi = 4096;
2528 float curtime = engine_curtime();
2529 for (int idx = 1; idx <= hi && g_nprojs < 32; idx++) {
2530 if (idx == local_idx)
2531 continue;
2532 void *ent = entitylist_get(idx);
2533 if (!ent)
2534 continue;
2535 void *net = entity_networkable(idx);
2536 if (!net)
2537 continue;
2538 const char *cls = entity_class_name(net, nullptr);
2539 const char *model = entity_model_name(ent);
2540 if (!model)
2541 continue;
2542 bool owned = item_weapon_is_owned(ent);
2543 bool frag_cls = cls && strcasestr(cls, "grenade") &&
2544 !strcasestr(cls, "weapon");
2545 bool missile_cls = cls && strcasestr(cls, "missile") &&
2546 !strcasestr(cls, "weapon");
2547 int kind;
2548 if (strcasestr(model, "w_grenade") &&
2549 (frag_cls || (!cls && owned))) {
2550 kind = PK_FRAG;
2551 } else if ((strcasestr(model, "w_missile") ||
2552 strcasestr(model, "missile_launch") ||
2553 strcasestr(model, "missile_closed")) &&
2554 (missile_cls || (!cls && owned))) {
2555 kind = PK_RPG;
2556 } else if (strcasestr(model, "helicopter_bomb")) {
2557 kind = PK_BOMB;
2558 } else if (strcasestr(model, "combineball")) {
2559 kind = PK_BALL;
2560 } else {
2561 continue;
2562 }
2563 uintptr_t e = (uintptr_t)ent;
2564 ProjTarget &p = g_projs[g_nprojs++];
2565 p.idx = idx;
2566 p.kind = kind;
2567 p.origin = Vector{*(float *)(e + g_net.origin),
2568 *(float *)(e + g_net.origin + 4),
2569 *(float *)(e + g_net.origin + 8)};
2570 // The client m_DmgRadius is networked; fall back to the server field and
2571 // then to the stock radius for this projectile type.
2572 p.radius = -1.0f;
2573 if (g_net.dmg_radius >= 0)
2574 p.radius = *(float *)(e + g_net.dmg_radius);
2575 if (!(p.radius > 1.0f && p.radius < 2048.0f))
2576 p.radius = server_grenade_radius(idx);
2577 if (!(p.radius > 1.0f && p.radius < 2048.0f))
2578 p.radius = kind == PK_RPG ? 200.0f : kind == PK_BALL ? 150.0f : 300.0f;
2579 // m_flDetonateTime is server-only (not in the send table).
2580 p.fuse = -1.0f;
2581 if (kind != PK_RPG) {
2582 float det = server_grenade_detonate(idx);
2583 if (det > 0.0f && curtime > 0.0f)
2584 p.fuse = det - curtime;
2585 if (!(p.fuse > -2.0f && p.fuse < 30.0f))
2586 p.fuse = -1.0f;
2587 }
2588 static int logs = 0;
2589 if (logs < 8) {
2590 logs++;
2591 logf("projectile: idx=%d cls=%s model=%s kind=%s fuse=%.2f "
2592 "radius=%.0f",
2593 idx, cls ? cls : "?", model, proj_name(kind), p.fuse, p.radius);
2594 }
2595 }
2596}
2597
2598void esp_draw_projectiles() {
2599 if (!g_surface || !g_iface.entitylist || g_net.origin < 0)
2600 return;
2601 if (!g_scr_w) {
2602 using Fn = void (*)(void *, int &, int &);
2603 vfunc<Fn>(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h);
2604 if (!g_scr_w)
2605 return;
2606 }
2607 projs_collect(engine_get_local_player());
2608 for (int i = 0; i < g_nprojs; i++) {
2609 const ProjTarget &p = g_projs[i];
2610 float sx, sy;
2611 if (!world_to_screen(p.origin, sx, sy))
2612 continue;
2613 if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -40 ||
2614 sy > (float)g_scr_h + 40)
2615 continue;
2616 int r = 255, g = 170, b = 60;
2617 if (p.kind == PK_RPG) {
2618 r = 255; g = 120; b = 40;
2619 } else if (p.kind == PK_BOMB) {
2620 r = 255; g = 60; b = 60;
2621 } else if (p.kind == PK_BALL) {
2622 r = 120; g = 220; b = 255;
2623 } else if (p.fuse >= 0.0f && p.fuse < 0.8f) {
2624 r = 255; g = 60; b = 60;
2625 }
2626 int x = (int)sx, y = (int)sy;
2627 surf_color(r, g, b, 255);
2628 surf_rect(0, x - 4, y - 4, x + 4, y + 4);
2629 surf_rect(1, x - 1, y - 1, x + 2, y + 2);
2630 char text[64];
2631 if (g_proj_fuse && p.fuse >= 0.0f)
2632 snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse);
2633 else
2634 snprintf(text, sizeof(text), "%s", proj_name(p.kind));
2635 int tx = x + 9;
2636 if (tx > g_scr_w - 80)
2637 tx = x - 9 - (int)strlen(text) * 6;
2638 if (tx < 4)
2639 tx = 4;
2640 int ty = y - 14;
2641 if (ty < 4)
2642 ty = 4;
2643 draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0);
2644 draw_text_small_col(tx, ty, text, r, g, b);
2645 if (g_proj_radius && p.radius > 1.0f) {
2646 const int SEG = 24;
2647 float lx = 0.0f, ly = 0.0f;
2648 bool have = false;
2649 surf_color(r, g, b, 150);
2650 for (int k = 0; k <= SEG; k++) {
2651 float a = (float)k * 2.0f * PI_F / (float)SEG;
2652 Vector w{p.origin.x + cosf(a) * p.radius,
2653 p.origin.y + sinf(a) * p.radius, p.origin.z};
2654 float rx, ry;
2655 if (!world_to_screen(w, rx, ry)) {
2656 have = false;
2657 continue;
2658 }
2659 if (have)
2660 surf_line((int)lx, (int)ly, (int)rx, (int)ry);
2661 lx = rx;
2662 ly = ry;
2663 have = true;
2664 }
2665 }
2666 }
2667}
diff --git a/src/gui.cpp b/src/gui.cpp
index 59c4364..2f832a2 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -364,7 +364,9 @@ static void build_cards() {
364 add_card(1, &g_triggerbot, "Triggerbot", 364 add_card(1, &g_triggerbot, "Triggerbot",
365 "Fire when the crosshair is on a hostile.", true); 365 "Fire when the crosshair is on a hostile.", true);
366 366
367 add_card(1, &g_melee, "Melee killbot", "360-degree crowbar silent aim.", 367 add_card(1, &g_melee, "Melee killbot",
368 "360-degree crowbar swing, or a gravity-gun launch at VULN "
369 "targets while holding an object.",
368 true); 370 true);
369 371
370 int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true); 372 int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true);
@@ -405,11 +407,22 @@ static void build_cards() {
405 &g_item_crates); 407 &g_item_crates);
406 add_toggle(items, "Show distance", "Append the distance in units.", 408 add_toggle(items, "Show distance", "Append the distance in units.",
407 &g_item_distance); 409 &g_item_distance);
410 add_toggle(items, "Only if usable",
411 "Hide health kits at full health and armor at full suit.",
412 &g_item_usable);
408 add_slider(items, "Max distance", 413 add_slider(items, "Max distance",
409 "0 = unlimited; otherwise items farther away are hidden.", 0, 414 "0 = unlimited; otherwise items farther away are hidden.", 0,
410 10000, &g_item_max_dist); 415 10000, &g_item_max_dist);
411 g_ctrls[g_nctrl - 1].zero_unlimited = true; 416 g_ctrls[g_nctrl - 1].zero_unlimited = true;
412 417
418 int proj = add_card(2, &g_projectiles, "Projectiles",
419 "Tag grenades, rockets, bombs and AR2 balls.", true);
420 add_toggle(proj, "Fuse timer",
421 "Show the remaining time until a grenade or ball detonates.",
422 &g_proj_fuse);
423 add_toggle(proj, "Blast radius",
424 "Draw the projectile's damage radius on the ground.", &g_proj_radius);
425
413 int view = add_card(2, nullptr, "Camera / display", 426 int view = add_card(2, nullptr, "Camera / display",
414 "Camera FOV, viewmodel FOV and HDR brightness.", true); 427 "Camera FOV, viewmodel FOV and HDR brightness.", true);
415 add_slider(view, "Camera FOV", 428 add_slider(view, "Camera FOV",
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index ca7ee85..703a582 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -909,15 +909,18 @@ static void do_move(int seq, float frametime) {
909 note_modified(cmd, flags, vel); 909 note_modified(cmd, flags, vel);
910 910
911 bool melee = feature_active(FEAT_MELEE) && melee_weapon_active(ent); 911 bool melee = feature_active(FEAT_MELEE) && melee_weapon_active(ent);
912 bool gravity = feature_active(FEAT_MELEE) && gravity_gun_active(ent);
913 bool melee_mode = melee || gravity;
912 914
913 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT) || melee) 915 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT) ||
916 melee_mode)
914 aimbot_reset_lock(); 917 aimbot_reset_lock();
915 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM) || 918 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM) ||
916 melee) 919 melee_mode)
917 silentaim_reset_lock(); 920 silentaim_reset_lock();
918 921
919 if (melee) { 922 if (melee_mode) {
920 if (melee_aimbot_apply(cmd)) 923 if (melee_aimbot_apply(cmd, gravity && !melee))
921 note_modified(cmd, flags, vel); 924 note_modified(cmd, flags, vel);
922 } else { 925 } else {
923 if (feature_active(FEAT_AIMBOT)) { 926 if (feature_active(FEAT_AIMBOT)) {
diff --git a/src/hl2h.h b/src/hl2h.h
index f1e72be..0c8ffb5 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -99,6 +99,7 @@ struct NetVars {
99 int clr_render = -1; // client m_clrRender (entity render colour) 99 int clr_render = -1; // client m_clrRender (entity render colour)
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 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 103 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72
103 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } 104 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
104 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. 105 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100.
@@ -165,7 +166,8 @@ bool aimbot_apply(CUserCmd *cmd);
165bool triggerbot_apply(CUserCmd *cmd); 166bool triggerbot_apply(CUserCmd *cmd);
166bool silent_aim_apply(CUserCmd *cmd); 167bool silent_aim_apply(CUserCmd *cmd);
167bool melee_weapon_active(void *local_ent); 168bool melee_weapon_active(void *local_ent);
168bool melee_aimbot_apply(CUserCmd *cmd); 169bool gravity_gun_active(void *local_ent);
170bool melee_aimbot_apply(CUserCmd *cmd, bool gravity);
169bool spread_control_apply(CUserCmd *cmd, void *local_ent); 171bool spread_control_apply(CUserCmd *cmd, void *local_ent);
170int spread_best_seed(int pellets); 172int spread_best_seed(int pellets);
171extern int g_spread_pellets; 173extern int g_spread_pellets;
@@ -184,7 +186,8 @@ enum FeatureId {
184 FEAT_WORLD_TINT = 11, 186 FEAT_WORLD_TINT = 11,
185 FEAT_FOG = 12, 187 FEAT_FOG = 12,
186 FEAT_SKYBOX = 13, 188 FEAT_SKYBOX = 13,
187 FEAT_COUNT = 14 189 FEAT_PROJECTILES = 14,
190 FEAT_COUNT = 15
188}; 191};
189enum EspOption { 192enum EspOption {
190 ESP_OPT_SHOW_NEUTRAL = 0, 193 ESP_OPT_SHOW_NEUTRAL = 0,
@@ -221,7 +224,8 @@ struct FeatureState {
221 224
222extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, 225extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp,
223 g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, 226 g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright,
224 g_world_tint, g_fog, g_skybox; 227 g_world_tint, g_fog, g_skybox, g_projectiles;
228extern bool g_proj_fuse, g_proj_radius;
225extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; 229extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on;
226extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; 230extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3];
227extern bool g_fog_skybox, g_fog_disable; 231extern bool g_fog_skybox, g_fog_disable;
@@ -236,6 +240,7 @@ extern bool g_item_armor;
236extern bool g_item_weapons; 240extern bool g_item_weapons;
237extern bool g_item_crates; 241extern bool g_item_crates;
238extern bool g_item_distance; 242extern bool g_item_distance;
243extern bool g_item_usable;
239extern int g_item_max_dist; 244extern int g_item_max_dist;
240extern bool g_aim_bones[AIM_BONE_COUNT]; 245extern bool g_aim_bones[AIM_BONE_COUNT];
241extern int g_aim_smooth; 246extern int g_aim_smooth;
@@ -289,6 +294,9 @@ int server_npc_relation(int idx, int local_idx);
289bool server_npc_has_follow_target(int idx); 294bool server_npc_has_follow_target(int idx);
290int server_npc_state(int idx); 295int server_npc_state(int idx);
291int server_npc_takedamage(int idx); 296int server_npc_takedamage(int idx);
297int server_player_armor(int idx);
298float server_grenade_detonate(int idx);
299float server_grenade_radius(int idx);
292int server_entity_count(); 300int server_entity_count();
293void *server_entity_base(int idx); 301void *server_entity_base(int idx);
294const char *server_entity_classname(int idx); 302const char *server_entity_classname(int idx);
@@ -322,6 +330,7 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b);
322void draw_text_small_col(int x, int y, const char *s, int r, int g, int b); 330void draw_text_small_col(int x, int y, const char *s, int r, int g, int b);
323void esp_draw(); 331void esp_draw();
324void esp_draw_items(); 332void esp_draw_items();
333void esp_draw_projectiles();
325void prop_tint_update(); 334void prop_tint_update();
326void studio_props_init(); 335void studio_props_init();
327bool world_to_screen(const Vector &w, float &sx, float &sy); 336bool world_to_screen(const Vector &w, float &sx, float &sy);
diff --git a/src/menu.cpp b/src/menu.cpp
index 020d2c5..521bf43 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -60,12 +60,16 @@ int g_fog_rgb[3] = {160, 170, 190};
60int g_fog_start = 200; 60int g_fog_start = 200;
61int g_fog_end = 3000; 61int g_fog_end = 3000;
62FeatureState g_skybox{false, 0}; // skybox.png image override 62FeatureState g_skybox{false, 0}; // skybox.png image override
63FeatureState g_projectiles{true, 0}; // grenade / rocket ESP
64bool g_proj_fuse = true;
65bool g_proj_radius = true;
63bool g_item_health = true; 66bool g_item_health = true;
64bool g_item_ammo = true; 67bool g_item_ammo = true;
65bool g_item_armor = true; 68bool g_item_armor = true;
66bool g_item_weapons = true; 69bool g_item_weapons = true;
67bool g_item_crates = true; 70bool g_item_crates = true;
68bool g_item_distance = true; 71bool g_item_distance = true;
72bool g_item_usable = true;
69int g_item_max_dist = 5000; 73int g_item_max_dist = 5000;
70 74
71// Aimbot bone checklist. Head defaults on (v1 behaviour). 75// Aimbot bone checklist. Head defaults on (v1 behaviour).
@@ -227,11 +231,16 @@ void config_save() {
227 g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); 231 g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable);
228 fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active, 232 fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active,
229 g_skybox.bind); 233 g_skybox.bind);
234 fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n",
235 g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse,
236 (int)g_proj_radius);
230 fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" 237 fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n"
231 "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n", 238 "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n"
239 "usable=%d\n",
232 g_item_esp.active, g_item_esp.bind, (int)g_item_health, 240 g_item_esp.active, g_item_esp.bind, (int)g_item_health,
233 (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons, 241 (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons,
234 (int)g_item_crates, (int)g_item_distance, g_item_max_dist); 242 (int)g_item_crates, (int)g_item_distance, g_item_max_dist,
243 (int)g_item_usable);
235 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" 244 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n"
236 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n" 245 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n"
237 "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n" 246 "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n"
@@ -274,7 +283,8 @@ static bool config_load() {
274 FeatureState *cur = nullptr; 283 FeatureState *cur = nullptr;
275 bool in_esp = false, in_bhop = false, in_aim = false, 284 bool in_esp = false, in_bhop = false, in_aim = false,
276 in_jumpbug = false, in_spread = false, in_items = false, 285 in_jumpbug = false, in_spread = false, in_items = false,
277 in_tint = false, in_fog = false, in_view = false, in_movement = false; 286 in_tint = false, in_fog = false, in_view = false, in_movement = false,
287 in_proj = false;
278 while (fgets(line, sizeof(line), f)) { 288 while (fgets(line, sizeof(line), f)) {
279 line[strcspn(line, "\r\n")] = 0; 289 line[strcspn(line, "\r\n")] = 0;
280 if (line[0] == '[') { 290 if (line[0] == '[') {
@@ -300,6 +310,8 @@ static bool config_load() {
300 cur = &g_fog; 310 cur = &g_fog;
301 else if (strstr(line, "[skybox]")) 311 else if (strstr(line, "[skybox]"))
302 cur = &g_skybox; 312 cur = &g_skybox;
313 else if (strstr(line, "[projectiles]"))
314 cur = &g_projectiles;
303 else if (strstr(line, "[aimbot]")) 315 else if (strstr(line, "[aimbot]"))
304 cur = &g_aimbot; 316 cur = &g_aimbot;
305 else if (strstr(line, "[triggerbot]")) 317 else if (strstr(line, "[triggerbot]"))
@@ -322,6 +334,7 @@ static bool config_load() {
322 in_fog = (cur == &g_fog); 334 in_fog = (cur == &g_fog);
323 in_view = strstr(line, "[view]") != nullptr; 335 in_view = strstr(line, "[view]") != nullptr;
324 in_movement = strstr(line, "[movement]") != nullptr; 336 in_movement = strstr(line, "[movement]") != nullptr;
337 in_proj = (cur == &g_projectiles);
325 } else if (cur || in_view || in_movement) { 338 } else if (cur || in_view || in_movement) {
326 char *eq = strchr(line, '='); 339 char *eq = strchr(line, '=');
327 if (!eq) 340 if (!eq)
@@ -353,6 +366,8 @@ static bool config_load() {
353 g_item_crates = atoi(val) != 0; 366 g_item_crates = atoi(val) != 0;
354 else if (strcmp(key, "distance") == 0) 367 else if (strcmp(key, "distance") == 0)
355 g_item_distance = atoi(val) != 0; 368 g_item_distance = atoi(val) != 0;
369 else if (strcmp(key, "usable") == 0)
370 g_item_usable = atoi(val) != 0;
356 else if (strcmp(key, "max_dist") == 0) { 371 else if (strcmp(key, "max_dist") == 0) {
357 int v = atoi(val); 372 int v = atoi(val);
358 if (v < 0) v = 0; // 0 = unlimited 373 if (v < 0) v = 0; // 0 = unlimited
@@ -360,6 +375,12 @@ static bool config_load() {
360 g_item_max_dist = v; 375 g_item_max_dist = v;
361 } 376 }
362 } 377 }
378 if (in_proj) {
379 if (strcmp(key, "fuse") == 0)
380 g_proj_fuse = atoi(val) != 0;
381 else if (strcmp(key, "radius") == 0)
382 g_proj_radius = atoi(val) != 0;
383 }
363 if (in_aim) { 384 if (in_aim) {
364 if (strcmp(key, "smooth") == 0) { 385 if (strcmp(key, "smooth") == 0) {
365 int v = atoi(val); 386 int v = atoi(val);
@@ -581,12 +602,16 @@ void config_reset_defaults() {
581 g_fog_start = 200; 602 g_fog_start = 200;
582 g_fog_end = 3000; 603 g_fog_end = 3000;
583 g_skybox = {false, 0}; 604 g_skybox = {false, 0};
605 g_projectiles = {true, 0};
606 g_proj_fuse = true;
607 g_proj_radius = true;
584 g_item_health = true; 608 g_item_health = true;
585 g_item_ammo = true; 609 g_item_ammo = true;
586 g_item_armor = true; 610 g_item_armor = true;
587 g_item_weapons = true; 611 g_item_weapons = true;
588 g_item_crates = true; 612 g_item_crates = true;
589 g_item_distance = true; 613 g_item_distance = true;
614 g_item_usable = true;
590 g_item_max_dist = 5000; 615 g_item_max_dist = 5000;
591 g_aimbot = {true, 604}; 616 g_aimbot = {true, 604};
592 g_triggerbot = {true, 603}; 617 g_triggerbot = {true, 603};
@@ -713,7 +738,7 @@ static FeatureState *feature_state(int feature) {
713 static FeatureState *feats[FEAT_COUNT] = { 738 static FeatureState *feats[FEAT_COUNT] = {
714 &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, 739 &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot,
715 &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, 740 &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp,
716 &g_fullbright, &g_world_tint, &g_fog, &g_skybox}; 741 &g_fullbright, &g_world_tint, &g_fog, &g_skybox, &g_projectiles};
717 if (feature < 0 || feature >= FEAT_COUNT) 742 if (feature < 0 || feature >= FEAT_COUNT)
718 return nullptr; 743 return nullptr;
719 return feats[feature]; 744 return feats[feature];
@@ -940,9 +965,11 @@ void outer_draw() {
940 static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; 965 static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr;
941 bool esp_on = feature_active(FEAT_ESP); 966 bool esp_on = feature_active(FEAT_ESP);
942 bool items_on = feature_active(FEAT_ITEM_ESP); 967 bool items_on = feature_active(FEAT_ITEM_ESP);
968 bool projs_on = feature_active(FEAT_PROJECTILES);
943 bool toasts = gui_toasts_active(); 969 bool toasts = gui_toasts_active();
944 if (!g_surface || 970 if (!g_surface ||
945 (!gui_is_open() && !draw_test && !esp_on && !items_on && !toasts)) 971 (!gui_is_open() && !draw_test && !esp_on && !items_on && !projs_on &&
972 !toasts))
946 return; 973 return;
947 974
948 // Level/menu -> game transitions can invalidate the VGUI font handle. 975 // Level/menu -> game transitions can invalidate the VGUI font handle.
@@ -1007,6 +1034,8 @@ void outer_draw() {
1007 esp_draw(); 1034 esp_draw();
1008 if (items_on && overlay_font) 1035 if (items_on && overlay_font)
1009 esp_draw_items(); 1036 esp_draw_items();
1037 if (projs_on && overlay_font)
1038 esp_draw_projectiles();
1010 if (draw_test && overlay_font) { 1039 if (draw_test && overlay_font) {
1011 surf_draw_set_color(255, 0, 0, 255); 1040 surf_draw_set_color(255, 0, 0, 255);
1012 surf_draw_filled_rect(20, 20, 180, 60); 1041 surf_draw_filled_rect(20, 20, 180, 60);
diff --git a/src/netvars.cpp b/src/netvars.cpp
index 33fd6a3..ef564cd 100644
--- a/src/netvars.cpp
+++ b/src/netvars.cpp
@@ -107,6 +107,11 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) {
107 int off = (int)(base_off + (uint32_t)poff); 107 int off = (int)(base_off + (uint32_t)poff);
108 if (off >= 0x20 && off <= 0x2000) 108 if (off >= 0x20 && off <= 0x2000)
109 g_net.default_fov = off; 109 g_net.default_fov = off;
110 } else if (strcmp(pname2, "m_DmgRadius") == 0 &&
111 g_net.dmg_radius < 0) {
112 int off = (int)(base_off + (uint32_t)poff);
113 if (off >= 0x20 && off <= 0x2000)
114 g_net.dmg_radius = off;
110 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { 115 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) {
111 g_net.teamnum = (int)(base_off + (uint32_t)poff); 116 g_net.teamnum = (int)(base_off + (uint32_t)poff);
112 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { 117 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) {
@@ -288,11 +293,12 @@ void resolve_netvars() {
288 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " 293 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d "
289 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " 294 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d "
290 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " 295 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d "
291 "weapon_owner=%d clr_render=%d punch=%d default_fov=%d", 296 "weapon_owner=%d clr_render=%d punch=%d default_fov=%d "
297 "dmg_radius=%d",
292 g_net.flags, g_net.velocity, g_net.movetype, g_net.health, 298 g_net.flags, g_net.velocity, g_net.movetype, g_net.health,
293 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, 299 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset,
294 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, 300 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex,
295 g_net.absvelocity, g_net.activeweapon, g_net.owner, 301 g_net.absvelocity, g_net.activeweapon, g_net.owner,
296 g_net.weapon_owner, g_net.clr_render, g_net.punch, 302 g_net.weapon_owner, g_net.clr_render, g_net.punch,
297 g_net.default_fov); 303 g_net.default_fov, g_net.dmg_radius);
298} 304}
diff --git a/src/server.cpp b/src/server.cpp
index 39bb162..40aa850 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -31,7 +31,9 @@ enum {
31 FIELD_CHARACTER = 8, 31 FIELD_CHARACTER = 8,
32 FIELD_EMBEDDED = 10, 32 FIELD_EMBEDDED = 10,
33 FIELD_CUSTOM = 11, 33 FIELD_CUSTOM = 11,
34 FIELD_EHANDLE = 13 34 FIELD_EHANDLE = 13,
35 FIELD_TIME = 16,
36 FIELD_FLOAT = 1
35}; 37};
36 38
37// SF_FLOOR_TURRET_CITIZEN (npc_turret_floor.h). Citizen-built turrets 39// SF_FLOOR_TURRET_CITIZEN (npc_turret_floor.h). Citizen-built turrets
@@ -53,6 +55,9 @@ static int g_srv_hacked = -1;
53static int g_srv_npcstate = -1; 55static int g_srv_npcstate = -1;
54static int g_srv_takedamage = -1; 56static int g_srv_takedamage = -1;
55static int g_srv_mapname = -1; 57static int g_srv_mapname = -1;
58static int g_srv_armor = -1;
59static int g_srv_detonate = -1;
60static int g_srv_grenade_radius = -1;
56static bool g_srv_ready = false; 61static bool g_srv_ready = false;
57static bool g_srv_checked = false; 62static bool g_srv_checked = false;
58 63
@@ -150,17 +155,22 @@ bool resolve_server_netvars() {
150 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); 155 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER);
151 // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel. 156 // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel.
152 g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000); 157 g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000);
158 // CBasePlayer::m_ArmorValue (int).
159 g_srv_armor = datamap_offset("m_ArmorValue", FIELD_INTEGER);
160 // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked).
161 g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME);
162 g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT);
153 g_srv_ready = g_srv_health > 0; 163 g_srv_ready = g_srv_health > 0;
154 if (g_srv_ready) { 164 if (g_srv_ready) {
155 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " 165 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d "
156 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " 166 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d "
157 "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " 167 "followtarget=%d spawnflags=%d hacked=%d npcstate=%d "
158 "takedamage=%d mapname=%d", 168 "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d",
159 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, 169 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate,
160 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, 170 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity,
161 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, 171 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget,
162 g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, 172 g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage,
163 g_srv_mapname); 173 g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius);
164 fail_logged = false; 174 fail_logged = false;
165 } else if (!fail_logged) { 175 } else if (!fail_logged) {
166 fail_logged = true; 176 fail_logged = true;
@@ -425,6 +435,39 @@ bool server_set_velocity_z(int idx, float vz) {
425 return true; 435 return true;
426} 436}
427 437
438// Local player's HEV suit armor from the authoritative server entity.
439int server_player_armor(int idx) {
440 if (!g_srv_ready || g_srv_armor < 0)
441 return -1;
442 void *base = nullptr;
443 server_entity_health(idx, nullptr, nullptr, &base);
444 if (!base)
445 return -1;
446 return *(int *)((uintptr_t)base + g_srv_armor);
447}
448
449// CBaseGrenade fuse time and blast radius from the server entity. Both are
450// non-networked (m_flDetonateTime is commented out of the send table).
451float server_grenade_detonate(int idx) {
452 if (!g_srv_ready || g_srv_detonate < 0)
453 return -1.0f;
454 void *base = nullptr;
455 server_entity_health(idx, nullptr, nullptr, &base);
456 if (!base)
457 return -1.0f;
458 return *(float *)((uintptr_t)base + g_srv_detonate);
459}
460
461float server_grenade_radius(int idx) {
462 if (!g_srv_ready || g_srv_grenade_radius < 0)
463 return -1.0f;
464 void *base = nullptr;
465 server_entity_health(idx, nullptr, nullptr, &base);
466 if (!base)
467 return -1.0f;
468 return *(float *)((uintptr_t)base + g_srv_grenade_radius);
469}
470
428// Entity index of the player's active weapon from the authoritative server 471// Entity index of the player's active weapon from the authoritative server
429// entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. 472// entity. Used as a fallback if the client m_hActiveWeapon netvar is missing.
430int server_active_weapon(int idx) { 473int server_active_weapon(int idx) {