diff options
Diffstat (limited to 'src/esp.cpp')
| -rw-r--r-- | src/esp.cpp | 283 |
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 | ||
| 1178 | bool melee_aimbot_apply(CUserCmd *cmd) { | 1179 | bool 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 | |||
| 1207 | bool 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. | ||
| 2376 | static 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 | |||
| 2334 | void esp_draw_items() { | 2397 | void 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 | |||
| 2484 | enum ProjKind { PK_FRAG, PK_RPG, PK_BOMB, PK_BALL }; | ||
| 2485 | |||
| 2486 | struct ProjTarget { | ||
| 2487 | int idx; | ||
| 2488 | int kind; | ||
| 2489 | Vector origin; | ||
| 2490 | float fuse; // seconds; < 0 = impact / no fuse | ||
| 2491 | float radius; | ||
| 2492 | }; | ||
| 2493 | |||
| 2494 | static ProjTarget g_projs[32]; | ||
| 2495 | static int g_nprojs = 0; | ||
| 2496 | |||
| 2497 | static 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 | |||
| 2504 | static 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 | |||
| 2514 | static 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 | |||
| 2598 | void 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 | } | ||
