summaryrefslogtreecommitdiffstats
path: root/src/esp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/esp.cpp')
-rw-r--r--src/esp.cpp283
1 files changed, 273 insertions, 10 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}