summaryrefslogtreecommitdiffstats
path: root/src/esp.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/esp.cpp361
1 files changed, 337 insertions, 24 deletions
diff --git a/src/esp.cpp b/src/esp.cpp
index 51f46bc..1e67a8a 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -24,8 +24,17 @@ struct EspTarget {
24 bool friendly; 24 bool friendly;
25 bool neutral; 25 bool neutral;
26 int damage; // NpcDamageClass 26 int damage; // NpcDamageClass
27 int state; // server NPC_STATE_*
28 char weapon[24];
27 char name[48]; 29 char name[48];
28}; 30};
31
32struct ArrowTarget {
33 Vector head;
34 bool neutral;
35};
36static ArrowTarget g_arrows[32];
37static int g_narrows = 0;
29static EspTarget g_targets[128]; 38static EspTarget g_targets[128];
30static int g_ntargets = 0; 39static int g_ntargets = 0;
31static int g_scr_w = 0, g_scr_h = 0; 40static int g_scr_w = 0, g_scr_h = 0;
@@ -273,6 +282,61 @@ static void short_name(const char *model, char *out, size_t outn) {
273 out[n] = 0; 282 out[n] = 0;
274} 283}
275 284
285// Enemy weapon label. NPC weapons are server entities, so read the server
286// model index first and fall back to the client class name.
287static void weapon_short_name(int idx, char *out, size_t outn) {
288 out[0] = 0;
289 int wi = server_active_weapon(idx);
290 if (wi <= 0)
291 return;
292 const char *name = nullptr;
293 void *base = server_entity_base(wi);
294 int mi = base ? server_model_index(base) : -1;
295 if (mi > 0)
296 name = model_name_from_index(mi);
297 if (!name) {
298 void *wep = entitylist_get(wi);
299 if (wep) {
300 void *net = entity_networkable(wi);
301 name = entity_class_name(net, nullptr);
302 }
303 }
304 if (!name)
305 name = server_entity_classname(wi);
306 if (!name)
307 return;
308 static const struct {
309 const char *needle;
310 const char *label;
311 } map[] = {
312 {"smg1", "SMG1"}, {"irifle", "AR2"}, {"ar2", "AR2"},
313 {"shotgun", "Shotgun"}, {"pistol", "Pistol"}, {"357", "357"},
314 {"crossbow", "Crossbow"}, {"rocket", "RPG"}, {"rpg", "RPG"},
315 {"grenade", "Grenade"}, {"frag", "Grenade"}, {"crowbar", "Crowbar"},
316 {"physcannon", "Gravity"}, {"slam", "SLAM"}, {"bugbait", "Bugbait"},
317 {"manhack", "Manhack"}, {"turret", "Turret"}, {"annabelle", "Rifle"},
318 {"alyxgun", "Alyx gun"}, {"sniper", "Sniper"}, {"smg", "SMG"},
319 };
320 for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
321 if (strcasestr(name, map[i].needle)) {
322 snprintf(out, outn, "%s", map[i].label);
323 return;
324 }
325 }
326 // Last resort: the short class/model name.
327 short_name(name, out, outn);
328}
329
330// Hostile / neutral / friendly colours come from the ESP colour pickers.
331static void relation_rgb(const EspTarget &t, int &r, int &g, int &b) {
332 const int *c = t.friendly ? g_esp_col_friendly
333 : t.neutral ? g_esp_col_neutral
334 : g_esp_col_hostile;
335 r = c[0];
336 g = c[1];
337 b = c[2];
338}
339
276static void esp_collect(int local_idx) { 340static void esp_collect(int local_idx) {
277 // outer_draw() runs once per PaintTraverseEx call (several per frame); cache 341 // outer_draw() runs once per PaintTraverseEx call (several per frame); cache
278 // the scan briefly so we do not walk the entity list dozens of times a frame. 342 // the scan briefly so we do not walk the entity list dozens of times a frame.
@@ -286,6 +350,7 @@ static void esp_collect(int local_idx) {
286 if (dbg) last_dbg = now; 350 if (dbg) last_dbg = now;
287 351
288 g_ntargets = 0; 352 g_ntargets = 0;
353 g_narrows = 0;
289 using Fn = int (*)(void *); 354 using Fn = int (*)(void *);
290 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex 355 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex
291 if (hi < 0 || hi > 4096) hi = 4096; 356 if (hi < 0 || hi > 4096) hi = 4096;
@@ -361,6 +426,16 @@ static void esp_collect(int local_idx) {
361 *(float *)(e + g_net.origin + 8)}; 426 *(float *)(e + g_net.origin + 8)};
362 Vector bottom{origin.x, origin.y, origin.z + minz}; 427 Vector bottom{origin.x, origin.y, origin.z + minz};
363 Vector top{origin.x, origin.y, origin.z + maxz}; 428 Vector top{origin.x, origin.y, origin.z + maxz};
429 bool neutral = false;
430 bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral);
431 // Arrows are collected before projection: a hostile behind the camera
432 // never produces on-screen box coordinates.
433 if (npc && g_narrows < 32 && !friendly &&
434 (!neutral || esp_option(ESP_OPT_SHOW_NEUTRAL))) {
435 ArrowTarget &a = g_arrows[g_narrows++];
436 a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f};
437 a.neutral = neutral;
438 }
364 float sx, sy, tx, ty; 439 float sx, sy, tx, ty;
365 if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) 440 if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty))
366 continue; 441 continue;
@@ -374,10 +449,13 @@ static void esp_collect(int local_idx) {
374 t.health = hp > 0 ? (float)hp : 0.0f; 449 t.health = hp > 0 ? (float)hp : 0.0f;
375 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; 450 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f;
376 t.show_health = hp > 0; 451 t.show_health = hp > 0;
377 bool neutral = false; 452 t.friendly = friendly;
378 t.friendly = npc_is_friendly(idx, model, local_idx, neutral);
379 t.neutral = neutral; 453 t.neutral = neutral;
380 t.damage = npc_damage_class(idx, model); 454 t.damage = npc_damage_class(idx, model);
455 t.state = npc ? server_npc_state(idx) : -1;
456 t.weapon[0] = 0;
457 if (npc)
458 weapon_short_name(idx, t.weapon, sizeof(t.weapon));
381 short_name(model, t.name, sizeof(t.name)); 459 short_name(model, t.name, sizeof(t.name));
382 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); 460 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC");
383 } 461 }
@@ -562,12 +640,9 @@ static void draw_skeleton(const EspTarget &t) {
562 logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count, 640 logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count,
563 (int)bones_is_humanoid(t.idx)); 641 (int)bones_is_humanoid(t.idx));
564 } 642 }
565 if (t.friendly) 643 int sr, sg, sb;
566 surf_color(60, 255, 60, 255); 644 relation_rgb(t, sr, sg, sb);
567 else if (t.neutral) 645 surf_color(sr, sg, sb, 255);
568 surf_color(220, 220, 220, 255);
569 else
570 surf_color(255, 60, 60, 255);
571 for (int i = 0; i < n; i++) { 646 for (int i = 0; i < n; i++) {
572 if (!ok[i]) 647 if (!ok[i])
573 continue; 648 continue;
@@ -595,6 +670,24 @@ static QAngle angle_to(const Vector &from, const Vector &to) {
595 return a; 670 return a;
596} 671}
597 672
673// Server bullet direction adds m_Local.m_vecPunchAngle to the usercmd view
674// (GetAutoaimVector), so aim angles have to be pre-subtracted by it.
675static bool punch_angles(float &px, float &py) {
676 px = py = 0.0f;
677 if (!aim_punch_enabled() || g_net.punch < 0)
678 return false;
679 int local = engine_get_local_player();
680 if (local <= 0)
681 return false;
682 void *le = entitylist_get(local);
683 if (!le)
684 return false;
685 const float *p = (const float *)((uintptr_t)le + g_net.punch);
686 px = p[0];
687 py = p[1];
688 return std::isfinite(px) && std::isfinite(py);
689}
690
598static int g_aim_lock = -1; 691static int g_aim_lock = -1;
599static int g_silent_lock = -1; 692static int g_silent_lock = -1;
600 693
@@ -852,6 +945,12 @@ bool aimbot_apply(CUserCmd *cmd) {
852 if (!bone_mask) 945 if (!bone_mask)
853 return false; 946 return false;
854 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; 947 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f};
948 float punch_x = 0.0f, punch_y = 0.0f;
949 bool punch = punch_angles(punch_x, punch_y);
950 if (punch) {
951 view.x += punch_x;
952 view.y += punch_y;
953 }
855 int target = -1; 954 int target = -1;
856 Vector point{}; 955 Vector point{};
857 float fov = 0.0f; 956 float fov = 0.0f;
@@ -866,6 +965,10 @@ bool aimbot_apply(CUserCmd *cmd) {
866 if (projectile_lead_point(target, point, lead)) 965 if (projectile_lead_point(target, point, lead))
867 point = lead; 966 point = lead;
868 QAngle desired = angle_to(g_cam_eye, point); 967 QAngle desired = angle_to(g_cam_eye, point);
968 if (punch) {
969 desired.x -= punch_x;
970 desired.y -= punch_y;
971 }
869 static int want_logs = 0; 972 static int want_logs = 0;
870 if (want_logs < 10) { 973 if (want_logs < 10) {
871 want_logs++; 974 want_logs++;
@@ -930,20 +1033,30 @@ bool silent_aim_apply(CUserCmd *cmd) {
930 return false; 1033 return false;
931 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; 1034 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
932 QAngle view{orig.x, orig.y, 0.0f}; 1035 QAngle view{orig.x, orig.y, 0.0f};
1036 float punch_x = 0.0f, punch_y = 0.0f;
1037 bool punch = punch_angles(punch_x, punch_y);
1038 if (punch) {
1039 view.x += punch_x;
1040 view.y += punch_y;
1041 }
933 int target = -1; 1042 int target = -1;
934 Vector point{}; 1043 Vector point{};
935 float fov = 0.0f; 1044 float fov = 0.0f;
936 float max_fov = (float)silentaim_fov(); 1045 float max_fov = (float)aimbot_fov();
937 if (max_fov < 1.0f) max_fov = 1.0f; 1046 if (max_fov < 1.0f) max_fov = 1.0f;
938 if (max_fov > 180.0f) max_fov = 180.0f; 1047 if (max_fov > 180.0f) max_fov = 180.0f;
939 int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr; 1048 int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr;
940 if (!select_aim_target(view, bone_mask, true, max_fov, "silentaim", lock, 1049 if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov,
941 target, point, fov)) 1050 "silentaim", lock, target, point, fov))
942 return false; 1051 return false;
943 Vector lead; 1052 Vector lead;
944 if (projectile_lead_point(target, point, lead)) 1053 if (projectile_lead_point(target, point, lead))
945 point = lead; 1054 point = lead;
946 QAngle desired = angle_to(g_cam_eye, point); 1055 QAngle desired = angle_to(g_cam_eye, point);
1056 if (punch) {
1057 desired.x -= punch_x;
1058 desired.y -= punch_y;
1059 }
947 float np = desired.x; 1060 float np = desired.x;
948 if (np > 89.0f) np = 89.0f; 1061 if (np > 89.0f) np = 89.0f;
949 if (np < -89.0f) np = -89.0f; 1062 if (np < -89.0f) np = -89.0f;
@@ -1215,6 +1328,11 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1215 1328
1216 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; 1329 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
1217 QAngle desired = angle_to(g_cam_eye, aim); 1330 QAngle desired = angle_to(g_cam_eye, aim);
1331 float punch_x = 0.0f, punch_y = 0.0f;
1332 if (punch_angles(punch_x, punch_y)) {
1333 desired.x -= punch_x;
1334 desired.y -= punch_y;
1335 }
1218 float np = desired.x; 1336 float np = desired.x;
1219 if (np > 89.0f) np = 89.0f; 1337 if (np > 89.0f) np = 89.0f;
1220 if (np < -89.0f) np = -89.0f; 1338 if (np < -89.0f) np = -89.0f;
@@ -1415,6 +1533,11 @@ bool triggerbot_apply(CUserCmd *cmd) {
1415 return false; 1533 return false;
1416 1534
1417 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; 1535 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f};
1536 float punch_x = 0.0f, punch_y = 0.0f;
1537 if (punch_angles(punch_x, punch_y)) {
1538 view.x += punch_x;
1539 view.y += punch_y;
1540 }
1418 Vector fwd = angle_forward_vector(view); 1541 Vector fwd = angle_forward_vector(view);
1419 Vector start = g_cam_eye; 1542 Vector start = g_cam_eye;
1420 Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, 1543 Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f,
@@ -1513,6 +1636,181 @@ void prop_tint_update() {
1513 } 1636 }
1514} 1637}
1515 1638
1639// Small left-edge label, stacked above the box (10 px font + 1 px shadow).
1640static void draw_left_tag(int left, int y, const char *text, int r, int g,
1641 int b) {
1642 int tw = (int)strlen(text) * 6;
1643 int tx = left - tw - 3;
1644 if (tx < 2)
1645 tx = 2;
1646 draw_text_small_col(tx + 1, y + 1, text, 0, 0, 0);
1647 draw_text_small_col(tx, y, text, r, g, b);
1648}
1649
1650static const char *alert_tag(int state, int &r, int &g, int &b) {
1651 switch (state) {
1652 case 1: r = 170; g = 180; b = 190; return "IDLE";
1653 case 2: r = 255; g = 220; b = 80; return "ALERT";
1654 case 3: r = 255; g = 90; b = 70; return "COMBAT";
1655 case 4: r = 150; g = 200; b = 255; return "SCRIPT";
1656 default: return nullptr;
1657 }
1658}
1659
1660// Direction markers for hostiles outside the view. Drawn at the screen edge
1661// using the engine projection when the target is in front, and the view-right
1662// dot product when it is behind.
1663static void draw_edge_arrows() {
1664 if (!g_surface || !g_scr_w || !g_narrows || !esp_option(ESP_OPT_ARROWS))
1665 return;
1666 float cx = (float)g_scr_w * 0.5f, cy = (float)g_scr_h * 0.5f;
1667 const float margin = 26.0f;
1668 int drawn = 0;
1669 for (int i = 0; i < g_narrows && drawn < 12; i++) {
1670 const ArrowTarget &a = g_arrows[i];
1671 float sx, sy;
1672 bool proj = world_to_screen(a.head, sx, sy);
1673 if (proj && sx >= 4.0f && sx <= (float)g_scr_w - 4.0f && sy >= 4.0f &&
1674 sy <= (float)g_scr_h - 4.0f)
1675 continue; // already visible as a box
1676 float dx, dy;
1677 if (proj) {
1678 dx = sx - cx;
1679 dy = sy - cy;
1680 } else {
1681 float yaw = g_cam_view.y * PI_F / 180.0f;
1682 float wx = a.head.x - g_cam_eye.x;
1683 float wy = a.head.y - g_cam_eye.y;
1684 float right = wx * sinf(yaw) + wy * (-cosf(yaw));
1685 dx = right >= 0.0f ? 1.0f : -1.0f;
1686 dy = 0.0f;
1687 }
1688 float len = sqrtf(dx * dx + dy * dy);
1689 if (len < 1e-3f) {
1690 dx = 0.0f;
1691 dy = 1.0f;
1692 len = 1.0f;
1693 }
1694 dx /= len;
1695 dy /= len;
1696 float half_w = cx - margin, half_h = cy - margin;
1697 float tx = fabsf(dx) > 1e-4f ? half_w / fabsf(dx) : 1e9f;
1698 float ty = fabsf(dy) > 1e-4f ? half_h / fabsf(dy) : 1e9f;
1699 float d = tx < ty ? tx : ty;
1700 float ex = cx + dx * d, ey = cy + dy * d;
1701 float px = -dy, py = dx;
1702 const int *col = a.neutral ? g_esp_col_neutral : g_esp_col_hostile;
1703 surf_color(col[0], col[1], col[2], 235);
1704 surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f + px * 6.0f),
1705 (int)(ey - dy * 13.0f + py * 6.0f));
1706 surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f - px * 6.0f),
1707 (int)(ey - dy * 13.0f - py * 6.0f));
1708 drawn++;
1709 }
1710}
1711
1712// Map transition triggers are server-only brush entities. Draw their world
1713// bounds and the target map; the previous map is remembered across level
1714// changes from the worldspawn model name.
1715struct TransitionBox {
1716 char next[128];
1717 Vector mins, maxs;
1718};
1719static TransitionBox g_trans[8];
1720static int g_ntrans = 0;
1721static char g_map_last[64];
1722static char g_map_prev[64];
1723
1724static void map_name_from_world(char *out, size_t outn) {
1725 out[0] = 0;
1726 void *w = entitylist_get(0);
1727 const char *m = w ? entity_model_name(w) : nullptr;
1728 if (!m)
1729 return;
1730 const char *b = strrchr(m, '/');
1731 b = b ? b + 1 : m;
1732 size_t n = strcspn(b, ".");
1733 if (n >= outn)
1734 n = outn - 1;
1735 memcpy(out, b, n);
1736 out[n] = 0;
1737}
1738
1739static void transitions_scan() {
1740 char cur[64];
1741 map_name_from_world(cur, sizeof(cur));
1742 if (strcmp(cur, g_map_last) != 0) {
1743 if (g_map_last[0])
1744 snprintf(g_map_prev, sizeof(g_map_prev), "%s", g_map_last);
1745 snprintf(g_map_last, sizeof(g_map_last), "%s", cur);
1746 }
1747 int count = server_entity_count();
1748 int n = 0;
1749 for (int i = 1; i <= count && n < 8; i++) {
1750 TransitionBox &b = g_trans[n];
1751 b.mins = Vector{0, 0, 0};
1752 b.maxs = Vector{0, 0, 0};
1753 if (!server_trigger_info(i, b.next, sizeof(b.next), b.mins, b.maxs))
1754 continue;
1755 if (b.mins.x == b.maxs.x && b.mins.y == b.maxs.y &&
1756 b.mins.z == b.maxs.z)
1757 continue;
1758 n++;
1759 }
1760 g_ntrans = n;
1761 static int logged = 0;
1762 if (n > 0 && logged < 4) {
1763 logged++;
1764 logf("transitions: %d changelevel trigger(s), next=%s prev=%s", n,
1765 g_trans[0].next, g_map_prev[0] ? g_map_prev : "?");
1766 }
1767}
1768
1769static void draw_transitions() {
1770 if (!g_surface || !g_ntrans)
1771 return;
1772 for (int i = 0; i < g_ntrans; i++) {
1773 const TransitionBox &b = g_trans[i];
1774 float minx = 1e9f, miny = 1e9f, maxx = -1e9f, maxy = -1e9f;
1775 int seen = 0;
1776 for (int c = 0; c < 8; c++) {
1777 Vector p{(c & 1) ? b.maxs.x : b.mins.x, (c & 2) ? b.maxs.y : b.mins.y,
1778 (c & 4) ? b.maxs.z : b.mins.z};
1779 float sx, sy;
1780 if (!world_to_screen(p, sx, sy) || !std::isfinite(sx) ||
1781 !std::isfinite(sy))
1782 continue;
1783 if (sx < minx) minx = sx;
1784 if (sx > maxx) maxx = sx;
1785 if (sy < miny) miny = sy;
1786 if (sy > maxy) maxy = sy;
1787 seen++;
1788 }
1789 if (!seen)
1790 continue;
1791 int x0 = (int)minx, x1 = (int)maxx, y0 = (int)miny, y1 = (int)maxy;
1792 if (x1 < -2000 || x0 > g_scr_w + 2000 || y1 < -2000 ||
1793 y0 > g_scr_h + 2000)
1794 continue;
1795 surf_color(255, 170, 60, 200);
1796 surf_rect(0, x0, y0, x1, y1);
1797 char line[128];
1798 snprintf(line, sizeof(line), "-> %.100s", b.next);
1799 int tx = x0;
1800 if (tx < 4) tx = 4;
1801 if (tx > g_scr_w - 120) tx = g_scr_w - 120;
1802 int ty = y0 - 16;
1803 if (ty < 4) ty = 4;
1804 draw_text_small_col(tx + 1, ty + 1, line, 0, 0, 0);
1805 draw_text_small_col(tx, ty, line, 255, 180, 70);
1806 if (g_map_prev[0]) {
1807 snprintf(line, sizeof(line), "prev %.48s", g_map_prev);
1808 draw_text_small_col(tx + 1, ty + 13, line, 0, 0, 0);
1809 draw_text_small_col(tx, ty + 12, line, 200, 200, 200);
1810 }
1811 }
1812}
1813
1516void esp_draw() { 1814void esp_draw() {
1517 if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; 1815 if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return;
1518 if (!g_scr_w) { 1816 if (!g_scr_w) {
@@ -1523,6 +1821,13 @@ void esp_draw() {
1523 } 1821 }
1524 draw_fov_circle(); 1822 draw_fov_circle();
1525 esp_collect(engine_get_local_player()); 1823 esp_collect(engine_get_local_player());
1824 static double last_trans = 0.0;
1825 double trans_now = esp_now();
1826 if (trans_now - last_trans >= 0.25) {
1827 last_trans = trans_now;
1828 transitions_scan();
1829 }
1830 draw_transitions();
1526 for (int i = 0; i < g_ntargets; i++) { 1831 for (int i = 0; i < g_ntargets; i++) {
1527 const EspTarget &t = g_targets[i]; 1832 const EspTarget &t = g_targets[i];
1528 if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL)) 1833 if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL))
@@ -1532,9 +1837,9 @@ void esp_draw() {
1532 if (ent && bones_update(t.idx, ent)) 1837 if (ent && bones_update(t.idx, ent))
1533 draw_skeleton(t); 1838 draw_skeleton(t);
1534 } 1839 }
1535 if (t.friendly) surf_color(60, 255, 60, 255); 1840 int cr, cg, cb;
1536 else if (t.neutral) surf_color(220, 220, 220, 255); 1841 relation_rgb(t, cr, cg, cb);
1537 else surf_color(255, 60, 60, 255); 1842 surf_color(cr, cg, cb, 255);
1538 float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width 1843 float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width
1539 int left = (int)(t.sx - w / 2); 1844 int left = (int)(t.sx - w / 2);
1540 int right = (int)(t.sx + w / 2); 1845 int right = (int)(t.sx + w / 2);
@@ -1568,7 +1873,8 @@ void esp_draw() {
1568 draw_text_col(num_x, top, hptext, 255, 255, 255); 1873 draw_text_col(num_x, top, hptext, 255, 255, 255);
1569 } 1874 }
1570 if (esp_option(ESP_OPT_SHOW_NAME)) 1875 if (esp_option(ESP_OPT_SHOW_NAME))
1571 draw_text_col(left, top - 14, t.name, 255, 255, 255); 1876 draw_text_col(left, top - 14, t.name, cr, cg, cb);
1877 int tag_y = top + 2;
1572 if (esp_option(ESP_OPT_DAMAGE_TAGS)) { 1878 if (esp_option(ESP_OPT_DAMAGE_TAGS)) {
1573 const char *tag = nullptr; 1879 const char *tag = nullptr;
1574 int tr = 255, tg = 255, tb = 255; 1880 int tr = 255, tg = 255, tb = 255;
@@ -1583,19 +1889,26 @@ void esp_draw() {
1583 tr = 180; tg = 180; tb = 180; 1889 tr = 180; tg = 180; tb = 180;
1584 } 1890 }
1585 if (tag) { 1891 if (tag) {
1586 // Small label on the left edge of the box, black 1px shadow for 1892 draw_left_tag(left, tag_y, tag, tr, tg, tb);
1587 // readability against the world (the 10px ESP font). 1893 tag_y += 12;
1588 int tw = (int)strlen(tag) * 6;
1589 int tx = left - tw - 3;
1590 if (tx < 2)
1591 tx = 2;
1592 int ty = top + 2;
1593 draw_text_small_col(tx + 1, ty + 1, tag, 0, 0, 0);
1594 draw_text_small_col(tx, ty, tag, tr, tg, tb);
1595 } 1894 }
1596 } 1895 }
1896 if (esp_option(ESP_OPT_ALERT)) {
1897 int ar, ag, ab;
1898 const char *tag = alert_tag(t.state, ar, ag, ab);
1899 if (tag) {
1900 draw_left_tag(left, tag_y, tag, ar, ag, ab);
1901 tag_y += 12;
1902 }
1903 }
1904 if (esp_option(ESP_OPT_WEAPON) && t.weapon[0]) {
1905 draw_left_tag(left, tag_y, t.weapon, 220, 220, 220);
1906 tag_y += 12;
1907 }
1597 } 1908 }
1598 1909
1910 draw_edge_arrows();
1911
1599 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's 1912 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's
1600 // "head" hitbox center (from SetupBones + studio hitboxes). 1913 // "head" hitbox center (from SetupBones + studio hitboxes).
1601 static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr; 1914 static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr;