diff options
Diffstat (limited to 'src/esp.cpp')
| -rw-r--r-- | src/esp.cpp | 318 |
1 files changed, 253 insertions, 65 deletions
diff --git a/src/esp.cpp b/src/esp.cpp index ca7bae0..d10a2cf 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -1132,71 +1132,6 @@ static void spread_compute() { | |||
| 1132 | g_spread_ready = true; | 1132 | g_spread_ready = true; |
| 1133 | } | 1133 | } |
| 1134 | 1134 | ||
| 1135 | static bool icontains(const char *hay, const char *needle) { | ||
| 1136 | if (!hay || !needle || !*needle) | ||
| 1137 | return false; | ||
| 1138 | size_t n = strlen(needle); | ||
| 1139 | for (const char *p = hay; *p; p++) | ||
| 1140 | if (strncasecmp(p, needle, n) == 0) | ||
| 1141 | return true; | ||
| 1142 | return false; | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | bool entity_is_physics_prop(int idx) { | ||
| 1146 | if (idx <= 0 || idx > 4096) | ||
| 1147 | return false; | ||
| 1148 | void *ent = entitylist_get(idx); | ||
| 1149 | if (!ent) | ||
| 1150 | return false; | ||
| 1151 | int mt = -1; | ||
| 1152 | // The reliable signal: physics objects use MOVETYPE_VPHYSICS. The client | ||
| 1153 | // class is CPhysicsProp / CPhysBox, not the server's prop_physics name. | ||
| 1154 | if (g_net.movetype >= 0) { | ||
| 1155 | mt = *(int *)((uintptr_t)ent + g_net.movetype); | ||
| 1156 | if (mt == MOVETYPE_VPHYSICS) | ||
| 1157 | return true; | ||
| 1158 | } | ||
| 1159 | void *net = entity_networkable(idx); | ||
| 1160 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 1161 | const char *model = entity_model_name(ent); | ||
| 1162 | bool is_prop = icontains(cls, "physicsprop") || icontains(cls, "physbox") || | ||
| 1163 | icontains(cls, "prop_physics") || | ||
| 1164 | icontains(model, "props_junk") || | ||
| 1165 | icontains(model, "props_physics") || | ||
| 1166 | icontains(model, "props_c17") || | ||
| 1167 | icontains(model, "props_interiors") || | ||
| 1168 | icontains(model, "props_lab"); | ||
| 1169 | if (!is_prop) { | ||
| 1170 | static int miss_logs = 0; | ||
| 1171 | if (miss_logs < 6) { | ||
| 1172 | miss_logs++; | ||
| 1173 | logf("stucklaunch prop? idx=%d movetype=%d cls=%s model=%s", idx, mt, | ||
| 1174 | cls ? cls : "?", model ? model : "?"); | ||
| 1175 | } | ||
| 1176 | } | ||
| 1177 | return is_prop; | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | bool active_weapon_is_gravity_gun(void *local_ent) { | ||
| 1181 | if (!local_ent || g_net.activeweapon < 0) | ||
| 1182 | return false; | ||
| 1183 | uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); | ||
| 1184 | if (h == 0xFFFFFFFFu) | ||
| 1185 | return false; | ||
| 1186 | int idx = (int)(h & 0xFFFF); | ||
| 1187 | if (idx <= 0 || idx > 4096) | ||
| 1188 | return false; | ||
| 1189 | void *wep = entitylist_get(idx); | ||
| 1190 | void *net = entity_networkable(idx); | ||
| 1191 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 1192 | const char *model = wep ? entity_model_name(wep) : nullptr; | ||
| 1193 | if (cls && strstr(cls, "physcannon")) | ||
| 1194 | return true; | ||
| 1195 | if (model && (strstr(model, "physcannon") || strstr(model, "w_physics"))) | ||
| 1196 | return true; | ||
| 1197 | return false; | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | static bool active_weapon_is_shotgun(void *local_ent) { | 1135 | static bool active_weapon_is_shotgun(void *local_ent) { |
| 1201 | if (!local_ent || g_net.activeweapon < 0) | 1136 | if (!local_ent || g_net.activeweapon < 0) |
| 1202 | return false; | 1137 | return false; |
| @@ -1402,3 +1337,256 @@ void esp_draw() { | |||
| 1402 | 1337 | ||
| 1403 | draw_speed_graph(); | 1338 | draw_speed_graph(); |
| 1404 | } | 1339 | } |
| 1340 | |||
| 1341 | |||
| 1342 | // --------------------------------------------------------------------------- | ||
| 1343 | // Valuable item tags | ||
| 1344 | // --------------------------------------------------------------------------- | ||
| 1345 | |||
| 1346 | enum ItemKind { IK_HEALTH, IK_AMMO, IK_ARMOR, IK_CRATE, IK_OTHER }; | ||
| 1347 | |||
| 1348 | struct ItemTarget { | ||
| 1349 | int idx; | ||
| 1350 | int kind; | ||
| 1351 | Vector origin; | ||
| 1352 | char label[48]; | ||
| 1353 | }; | ||
| 1354 | |||
| 1355 | static ItemTarget g_item_targets[192]; | ||
| 1356 | static int g_nitems = 0; | ||
| 1357 | static bool g_items_logged = false; | ||
| 1358 | |||
| 1359 | static bool item_icontains(const char *hay, const char *needle) { | ||
| 1360 | if (!hay || !needle || !*needle) | ||
| 1361 | return false; | ||
| 1362 | size_t n = strlen(needle); | ||
| 1363 | for (const char *p = hay; *p; p++) | ||
| 1364 | if (strncasecmp(p, needle, n) == 0) | ||
| 1365 | return true; | ||
| 1366 | return false; | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | static int classify_item(const char *model, const char *cls, char *label, | ||
| 1370 | size_t outn) { | ||
| 1371 | if (!model && !cls) | ||
| 1372 | return -1; | ||
| 1373 | |||
| 1374 | if (item_icontains(model, "healthkit")) { | ||
| 1375 | snprintf(label, outn, "Medkit"); | ||
| 1376 | return IK_HEALTH; | ||
| 1377 | } | ||
| 1378 | if (item_icontains(model, "healthvial")) { | ||
| 1379 | snprintf(label, outn, "Health vial"); | ||
| 1380 | return IK_HEALTH; | ||
| 1381 | } | ||
| 1382 | if (item_icontains(model, "health_charger")) { | ||
| 1383 | snprintf(label, outn, "Health charger"); | ||
| 1384 | return IK_HEALTH; | ||
| 1385 | } | ||
| 1386 | if (item_icontains(model, "battery")) { | ||
| 1387 | snprintf(label, outn, "Battery"); | ||
| 1388 | return IK_ARMOR; | ||
| 1389 | } | ||
| 1390 | if (item_icontains(model, "hevsuit")) { | ||
| 1391 | snprintf(label, outn, "HEV suit"); | ||
| 1392 | return IK_ARMOR; | ||
| 1393 | } | ||
| 1394 | if (item_icontains(model, "w_longjump")) { | ||
| 1395 | snprintf(label, outn, "Long jump"); | ||
| 1396 | return IK_ARMOR; | ||
| 1397 | } | ||
| 1398 | if (item_icontains(model, "suit_charger")) { | ||
| 1399 | snprintf(label, outn, "Suit charger"); | ||
| 1400 | return IK_ARMOR; | ||
| 1401 | } | ||
| 1402 | if (item_icontains(model, "item_item_crate") || | ||
| 1403 | item_icontains(model, "item_beacon_crate")) { | ||
| 1404 | snprintf(label, outn, "Supply crate"); | ||
| 1405 | return IK_CRATE; | ||
| 1406 | } | ||
| 1407 | if (item_icontains(model, "ammocrate")) { | ||
| 1408 | snprintf(label, outn, "Ammo crate"); | ||
| 1409 | return IK_AMMO; | ||
| 1410 | } | ||
| 1411 | if (item_icontains(model, "w_missile_closed")) { | ||
| 1412 | snprintf(label, outn, "RPG ammo"); | ||
| 1413 | return IK_AMMO; | ||
| 1414 | } | ||
| 1415 | if (item_icontains(model, "models/items/")) { | ||
| 1416 | if (item_icontains(model, "crossbowrounds")) | ||
| 1417 | snprintf(label, outn, "Crossbow bolts"); | ||
| 1418 | else if (item_icontains(model, "ar2_grenade")) | ||
| 1419 | snprintf(label, outn, "AR2 grenade"); | ||
| 1420 | else if (item_icontains(model, "357ammo")) | ||
| 1421 | snprintf(label, outn, "357 ammo"); | ||
| 1422 | else if (item_icontains(model, "boxbuckshot")) | ||
| 1423 | snprintf(label, outn, "Buckshot"); | ||
| 1424 | else if (item_icontains(model, "boxsniperrounds")) | ||
| 1425 | snprintf(label, outn, "Sniper ammo"); | ||
| 1426 | else | ||
| 1427 | snprintf(label, outn, "Ammo"); | ||
| 1428 | return IK_AMMO; | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") || | ||
| 1432 | item_icontains(cls, "wallhealth")) { | ||
| 1433 | snprintf(label, outn, "Health"); | ||
| 1434 | return IK_HEALTH; | ||
| 1435 | } | ||
| 1436 | if (item_icontains(cls, "battery") || item_icontains(cls, "itemsuit") || | ||
| 1437 | item_icontains(cls, "recharge")) { | ||
| 1438 | snprintf(label, outn, "Armor"); | ||
| 1439 | return IK_ARMOR; | ||
| 1440 | } | ||
| 1441 | if (item_icontains(cls, "itemcrate") || | ||
| 1442 | item_icontains(cls, "dynamicresupply")) { | ||
| 1443 | snprintf(label, outn, "Supply crate"); | ||
| 1444 | return IK_CRATE; | ||
| 1445 | } | ||
| 1446 | if (item_icontains(cls, "item_ammo") || item_icontains(cls, "itemammo")) { | ||
| 1447 | snprintf(label, outn, "Ammo"); | ||
| 1448 | return IK_AMMO; | ||
| 1449 | } | ||
| 1450 | if (item_icontains(cls, "item") || item_icontains(cls, "pickup")) { | ||
| 1451 | snprintf(label, outn, "Item"); | ||
| 1452 | return IK_OTHER; | ||
| 1453 | } | ||
| 1454 | return -1; | ||
| 1455 | } | ||
| 1456 | |||
| 1457 | static bool item_kind_enabled(int kind) { | ||
| 1458 | switch (kind) { | ||
| 1459 | case IK_HEALTH: return g_item_health; | ||
| 1460 | case IK_AMMO: return g_item_ammo; | ||
| 1461 | case IK_ARMOR: return g_item_armor; | ||
| 1462 | case IK_CRATE: return g_item_crates; | ||
| 1463 | default: return true; | ||
| 1464 | } | ||
| 1465 | } | ||
| 1466 | |||
| 1467 | static void items_collect(int local_idx) { | ||
| 1468 | static double last_scan = 0.0; | ||
| 1469 | double now = esp_now(); | ||
| 1470 | if (now - last_scan < 0.10) | ||
| 1471 | return; | ||
| 1472 | last_scan = now; | ||
| 1473 | g_nitems = 0; | ||
| 1474 | if (!g_iface.entitylist || g_net.origin < 0 || g_net.modelindex < 0) | ||
| 1475 | return; | ||
| 1476 | |||
| 1477 | using Fn = int (*)(void *); | ||
| 1478 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | ||
| 1479 | if (hi < 0 || hi > 4096) | ||
| 1480 | hi = 4096; | ||
| 1481 | |||
| 1482 | static int dbg_lines = 0; | ||
| 1483 | for (int idx = 1; idx <= hi && g_nitems < 192; idx++) { | ||
| 1484 | if (idx == local_idx) | ||
| 1485 | continue; | ||
| 1486 | void *ent = entitylist_get(idx); | ||
| 1487 | if (!ent) | ||
| 1488 | continue; | ||
| 1489 | void *net = entity_networkable(idx); | ||
| 1490 | if (!net) | ||
| 1491 | continue; | ||
| 1492 | const char *cls = entity_class_name(net, nullptr); | ||
| 1493 | const char *model = entity_model_name(ent); | ||
| 1494 | char label[48]; | ||
| 1495 | int kind = classify_item(model, cls, label, sizeof(label)); | ||
| 1496 | if (kind < 0) | ||
| 1497 | continue; | ||
| 1498 | |||
| 1499 | Vector origin{*(float *)((uintptr_t)ent + g_net.origin), | ||
| 1500 | *(float *)((uintptr_t)ent + g_net.origin + 4), | ||
| 1501 | *(float *)((uintptr_t)ent + g_net.origin + 8)}; | ||
| 1502 | float dx = origin.x - g_cam_eye.x; | ||
| 1503 | float dy = origin.y - g_cam_eye.y; | ||
| 1504 | float dz = origin.z - g_cam_eye.z; | ||
| 1505 | float dist = sqrtf(dx * dx + dy * dy + dz * dz); | ||
| 1506 | if (g_item_max_dist > 0 && dist > (float)g_item_max_dist) | ||
| 1507 | continue; | ||
| 1508 | |||
| 1509 | ItemTarget &t = g_item_targets[g_nitems++]; | ||
| 1510 | t.idx = idx; | ||
| 1511 | t.kind = kind; | ||
| 1512 | t.origin = origin; | ||
| 1513 | snprintf(t.label, sizeof(t.label), "%s", label); | ||
| 1514 | if (dbg_lines < 8) { | ||
| 1515 | dbg_lines++; | ||
| 1516 | logf("item: idx=%d kind=%d cls=%s model=%s", idx, kind, | ||
| 1517 | cls ? cls : "?", model ? model : "?"); | ||
| 1518 | } | ||
| 1519 | } | ||
| 1520 | |||
| 1521 | if (!g_items_logged) { | ||
| 1522 | g_items_logged = true; | ||
| 1523 | logf("items: tagging %d valuable entities", g_nitems); | ||
| 1524 | } | ||
| 1525 | } | ||
| 1526 | |||
| 1527 | void esp_draw_items() { | ||
| 1528 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) | ||
| 1529 | return; | ||
| 1530 | if (!g_item_health && !g_item_ammo && !g_item_armor && !g_item_crates) | ||
| 1531 | return; | ||
| 1532 | int local = engine_get_local_player(); | ||
| 1533 | if (local <= 0) | ||
| 1534 | return; | ||
| 1535 | if (!g_scr_w) { | ||
| 1536 | using Fn = void (*)(void *, int &, int &); | ||
| 1537 | vfunc<Fn>(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h); | ||
| 1538 | if (!g_scr_w) | ||
| 1539 | return; | ||
| 1540 | } | ||
| 1541 | items_collect(local); | ||
| 1542 | |||
| 1543 | for (int i = 0; i < g_nitems; i++) { | ||
| 1544 | const ItemTarget &t = g_item_targets[i]; | ||
| 1545 | if (!item_kind_enabled(t.kind)) | ||
| 1546 | continue; | ||
| 1547 | float sx, sy; | ||
| 1548 | if (!world_to_screen(t.origin, sx, sy)) | ||
| 1549 | continue; | ||
| 1550 | if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -20 || | ||
| 1551 | sy > (float)g_scr_h + 20) | ||
| 1552 | continue; | ||
| 1553 | |||
| 1554 | int r = 230, g = 230, b = 230; | ||
| 1555 | switch (t.kind) { | ||
| 1556 | case IK_HEALTH: r = 90; g = 255; b = 130; break; | ||
| 1557 | case IK_AMMO: r = 255; g = 210; b = 80; break; | ||
| 1558 | case IK_ARMOR: r = 90; g = 180; b = 255; break; | ||
| 1559 | case IK_CRATE: r = 220; g = 150; b = 255; break; | ||
| 1560 | default: break; | ||
| 1561 | } | ||
| 1562 | int x = (int)sx, y = (int)sy; | ||
| 1563 | surf_color(r, g, b, 255); | ||
| 1564 | surf_rect(0, x - 4, y - 4, x + 4, y + 4); | ||
| 1565 | surf_line(x - 8, y, x - 5, y); | ||
| 1566 | surf_line(x + 5, y, x + 8, y); | ||
| 1567 | surf_line(x, y - 8, x, y - 5); | ||
| 1568 | surf_line(x, y + 5, x, y + 8); | ||
| 1569 | |||
| 1570 | float dx = t.origin.x - g_cam_eye.x; | ||
| 1571 | float dy = t.origin.y - g_cam_eye.y; | ||
| 1572 | float dz = t.origin.z - g_cam_eye.z; | ||
| 1573 | int dist = (int)sqrtf(dx * dx + dy * dy + dz * dz); | ||
| 1574 | char text[96]; | ||
| 1575 | if (g_item_distance) | ||
| 1576 | snprintf(text, sizeof(text), "%s %du", t.label, dist); | ||
| 1577 | else | ||
| 1578 | snprintf(text, sizeof(text), "%s", t.label); | ||
| 1579 | int tw = (int)strlen(text) * 9; | ||
| 1580 | int tx = x + 10; | ||
| 1581 | if (tx + tw > g_scr_w - 4) | ||
| 1582 | tx = x - 10 - tw; | ||
| 1583 | if (tx < 4) | ||
| 1584 | tx = 4; | ||
| 1585 | int ty = y - 7; | ||
| 1586 | if (ty < 0) | ||
| 1587 | ty = 0; | ||
| 1588 | if (ty + 16 > g_scr_h) | ||
| 1589 | ty = g_scr_h - 16; | ||
| 1590 | draw_text_col(tx, ty, text, r, g, b); | ||
| 1591 | } | ||
| 1592 | } | ||
