From 12fde50efc7e1f1d123f9fc34ffef53d4387de2d Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 13:53:45 +0100 Subject: Add ESP, aim, view and movement batch - ESP: alert state (IDLE/ALERT/COMBAT/SCRIPT), enemy weapon labels, hostile/ neutral/friendly colour pickers, off-screen direction arrows, and trigger_changelevel volumes with next/previous map labels. - Aim: silent aim reuses the Aimbot FOV, vischeck and bone settings; add optional aim-punch compensation for aimbot, silent aim, triggerbot and melee (server bullets add m_vecPunchAngle to the usercmd angles). - View: always-on Camera / display card with camera FOV (writes the client m_iDefaultFOV netvar), viewmodel FOV and monitor gamma (client cvars). - Movement: Jump assist card with auto crouch-jump and edge jump (point ground probe 0.12 s ahead). - Spread: Engine spread override toggle gates the direct RandomFloat zeroing; off leaves only the seed search. - Server: entity count/base/classname helpers, trigger_changelevel map name and world bounds via IServerUnknown/ICollideable. - Netvars: m_vecPunchAngle and m_iDefaultFOV. --- src/esp.cpp | 361 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 337 insertions(+), 24 deletions(-) (limited to 'src/esp.cpp') 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 { bool friendly; bool neutral; int damage; // NpcDamageClass + int state; // server NPC_STATE_* + char weapon[24]; char name[48]; }; + +struct ArrowTarget { + Vector head; + bool neutral; +}; +static ArrowTarget g_arrows[32]; +static int g_narrows = 0; static EspTarget g_targets[128]; static int g_ntargets = 0; static 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) { out[n] = 0; } +// Enemy weapon label. NPC weapons are server entities, so read the server +// model index first and fall back to the client class name. +static void weapon_short_name(int idx, char *out, size_t outn) { + out[0] = 0; + int wi = server_active_weapon(idx); + if (wi <= 0) + return; + const char *name = nullptr; + void *base = server_entity_base(wi); + int mi = base ? server_model_index(base) : -1; + if (mi > 0) + name = model_name_from_index(mi); + if (!name) { + void *wep = entitylist_get(wi); + if (wep) { + void *net = entity_networkable(wi); + name = entity_class_name(net, nullptr); + } + } + if (!name) + name = server_entity_classname(wi); + if (!name) + return; + static const struct { + const char *needle; + const char *label; + } map[] = { + {"smg1", "SMG1"}, {"irifle", "AR2"}, {"ar2", "AR2"}, + {"shotgun", "Shotgun"}, {"pistol", "Pistol"}, {"357", "357"}, + {"crossbow", "Crossbow"}, {"rocket", "RPG"}, {"rpg", "RPG"}, + {"grenade", "Grenade"}, {"frag", "Grenade"}, {"crowbar", "Crowbar"}, + {"physcannon", "Gravity"}, {"slam", "SLAM"}, {"bugbait", "Bugbait"}, + {"manhack", "Manhack"}, {"turret", "Turret"}, {"annabelle", "Rifle"}, + {"alyxgun", "Alyx gun"}, {"sniper", "Sniper"}, {"smg", "SMG"}, + }; + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) { + if (strcasestr(name, map[i].needle)) { + snprintf(out, outn, "%s", map[i].label); + return; + } + } + // Last resort: the short class/model name. + short_name(name, out, outn); +} + +// Hostile / neutral / friendly colours come from the ESP colour pickers. +static void relation_rgb(const EspTarget &t, int &r, int &g, int &b) { + const int *c = t.friendly ? g_esp_col_friendly + : t.neutral ? g_esp_col_neutral + : g_esp_col_hostile; + r = c[0]; + g = c[1]; + b = c[2]; +} + static void esp_collect(int local_idx) { // outer_draw() runs once per PaintTraverseEx call (several per frame); cache // 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) { if (dbg) last_dbg = now; g_ntargets = 0; + g_narrows = 0; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex if (hi < 0 || hi > 4096) hi = 4096; @@ -361,6 +426,16 @@ static void esp_collect(int local_idx) { *(float *)(e + g_net.origin + 8)}; Vector bottom{origin.x, origin.y, origin.z + minz}; Vector top{origin.x, origin.y, origin.z + maxz}; + bool neutral = false; + bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral); + // Arrows are collected before projection: a hostile behind the camera + // never produces on-screen box coordinates. + if (npc && g_narrows < 32 && !friendly && + (!neutral || esp_option(ESP_OPT_SHOW_NEUTRAL))) { + ArrowTarget &a = g_arrows[g_narrows++]; + a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f}; + a.neutral = neutral; + } float sx, sy, tx, ty; if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) continue; @@ -374,10 +449,13 @@ static void esp_collect(int local_idx) { t.health = hp > 0 ? (float)hp : 0.0f; t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; t.show_health = hp > 0; - bool neutral = false; - t.friendly = npc_is_friendly(idx, model, local_idx, neutral); + t.friendly = friendly; t.neutral = neutral; t.damage = npc_damage_class(idx, model); + t.state = npc ? server_npc_state(idx) : -1; + t.weapon[0] = 0; + if (npc) + weapon_short_name(idx, t.weapon, sizeof(t.weapon)); short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } @@ -562,12 +640,9 @@ static void draw_skeleton(const EspTarget &t) { logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count, (int)bones_is_humanoid(t.idx)); } - if (t.friendly) - surf_color(60, 255, 60, 255); - else if (t.neutral) - surf_color(220, 220, 220, 255); - else - surf_color(255, 60, 60, 255); + int sr, sg, sb; + relation_rgb(t, sr, sg, sb); + surf_color(sr, sg, sb, 255); for (int i = 0; i < n; i++) { if (!ok[i]) continue; @@ -595,6 +670,24 @@ static QAngle angle_to(const Vector &from, const Vector &to) { return a; } +// Server bullet direction adds m_Local.m_vecPunchAngle to the usercmd view +// (GetAutoaimVector), so aim angles have to be pre-subtracted by it. +static bool punch_angles(float &px, float &py) { + px = py = 0.0f; + if (!aim_punch_enabled() || g_net.punch < 0) + return false; + int local = engine_get_local_player(); + if (local <= 0) + return false; + void *le = entitylist_get(local); + if (!le) + return false; + const float *p = (const float *)((uintptr_t)le + g_net.punch); + px = p[0]; + py = p[1]; + return std::isfinite(px) && std::isfinite(py); +} + static int g_aim_lock = -1; static int g_silent_lock = -1; @@ -852,6 +945,12 @@ bool aimbot_apply(CUserCmd *cmd) { if (!bone_mask) return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + bool punch = punch_angles(punch_x, punch_y); + if (punch) { + view.x += punch_x; + view.y += punch_y; + } int target = -1; Vector point{}; float fov = 0.0f; @@ -866,6 +965,10 @@ bool aimbot_apply(CUserCmd *cmd) { if (projectile_lead_point(target, point, lead)) point = lead; QAngle desired = angle_to(g_cam_eye, point); + if (punch) { + desired.x -= punch_x; + desired.y -= punch_y; + } static int want_logs = 0; if (want_logs < 10) { want_logs++; @@ -930,20 +1033,30 @@ bool silent_aim_apply(CUserCmd *cmd) { return false; QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle view{orig.x, orig.y, 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + bool punch = punch_angles(punch_x, punch_y); + if (punch) { + view.x += punch_x; + view.y += punch_y; + } int target = -1; Vector point{}; float fov = 0.0f; - float max_fov = (float)silentaim_fov(); + float max_fov = (float)aimbot_fov(); if (max_fov < 1.0f) max_fov = 1.0f; if (max_fov > 180.0f) max_fov = 180.0f; int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr; - if (!select_aim_target(view, bone_mask, true, max_fov, "silentaim", lock, - target, point, fov)) + if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov, + "silentaim", lock, target, point, fov)) return false; Vector lead; if (projectile_lead_point(target, point, lead)) point = lead; QAngle desired = angle_to(g_cam_eye, point); + if (punch) { + desired.x -= punch_x; + desired.y -= punch_y; + } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; @@ -1215,6 +1328,11 @@ bool melee_aimbot_apply(CUserCmd *cmd) { QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle desired = angle_to(g_cam_eye, aim); + float punch_x = 0.0f, punch_y = 0.0f; + if (punch_angles(punch_x, punch_y)) { + desired.x -= punch_x; + desired.y -= punch_y; + } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; @@ -1415,6 +1533,11 @@ bool triggerbot_apply(CUserCmd *cmd) { return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + if (punch_angles(punch_x, punch_y)) { + view.x += punch_x; + view.y += punch_y; + } Vector fwd = angle_forward_vector(view); Vector start = g_cam_eye; Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, @@ -1513,6 +1636,181 @@ void prop_tint_update() { } } +// Small left-edge label, stacked above the box (10 px font + 1 px shadow). +static void draw_left_tag(int left, int y, const char *text, int r, int g, + int b) { + int tw = (int)strlen(text) * 6; + int tx = left - tw - 3; + if (tx < 2) + tx = 2; + draw_text_small_col(tx + 1, y + 1, text, 0, 0, 0); + draw_text_small_col(tx, y, text, r, g, b); +} + +static const char *alert_tag(int state, int &r, int &g, int &b) { + switch (state) { + case 1: r = 170; g = 180; b = 190; return "IDLE"; + case 2: r = 255; g = 220; b = 80; return "ALERT"; + case 3: r = 255; g = 90; b = 70; return "COMBAT"; + case 4: r = 150; g = 200; b = 255; return "SCRIPT"; + default: return nullptr; + } +} + +// Direction markers for hostiles outside the view. Drawn at the screen edge +// using the engine projection when the target is in front, and the view-right +// dot product when it is behind. +static void draw_edge_arrows() { + if (!g_surface || !g_scr_w || !g_narrows || !esp_option(ESP_OPT_ARROWS)) + return; + float cx = (float)g_scr_w * 0.5f, cy = (float)g_scr_h * 0.5f; + const float margin = 26.0f; + int drawn = 0; + for (int i = 0; i < g_narrows && drawn < 12; i++) { + const ArrowTarget &a = g_arrows[i]; + float sx, sy; + bool proj = world_to_screen(a.head, sx, sy); + if (proj && sx >= 4.0f && sx <= (float)g_scr_w - 4.0f && sy >= 4.0f && + sy <= (float)g_scr_h - 4.0f) + continue; // already visible as a box + float dx, dy; + if (proj) { + dx = sx - cx; + dy = sy - cy; + } else { + float yaw = g_cam_view.y * PI_F / 180.0f; + float wx = a.head.x - g_cam_eye.x; + float wy = a.head.y - g_cam_eye.y; + float right = wx * sinf(yaw) + wy * (-cosf(yaw)); + dx = right >= 0.0f ? 1.0f : -1.0f; + dy = 0.0f; + } + float len = sqrtf(dx * dx + dy * dy); + if (len < 1e-3f) { + dx = 0.0f; + dy = 1.0f; + len = 1.0f; + } + dx /= len; + dy /= len; + float half_w = cx - margin, half_h = cy - margin; + float tx = fabsf(dx) > 1e-4f ? half_w / fabsf(dx) : 1e9f; + float ty = fabsf(dy) > 1e-4f ? half_h / fabsf(dy) : 1e9f; + float d = tx < ty ? tx : ty; + float ex = cx + dx * d, ey = cy + dy * d; + float px = -dy, py = dx; + const int *col = a.neutral ? g_esp_col_neutral : g_esp_col_hostile; + surf_color(col[0], col[1], col[2], 235); + surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f + px * 6.0f), + (int)(ey - dy * 13.0f + py * 6.0f)); + surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f - px * 6.0f), + (int)(ey - dy * 13.0f - py * 6.0f)); + drawn++; + } +} + +// Map transition triggers are server-only brush entities. Draw their world +// bounds and the target map; the previous map is remembered across level +// changes from the worldspawn model name. +struct TransitionBox { + char next[128]; + Vector mins, maxs; +}; +static TransitionBox g_trans[8]; +static int g_ntrans = 0; +static char g_map_last[64]; +static char g_map_prev[64]; + +static void map_name_from_world(char *out, size_t outn) { + out[0] = 0; + void *w = entitylist_get(0); + const char *m = w ? entity_model_name(w) : nullptr; + if (!m) + return; + const char *b = strrchr(m, '/'); + b = b ? b + 1 : m; + size_t n = strcspn(b, "."); + if (n >= outn) + n = outn - 1; + memcpy(out, b, n); + out[n] = 0; +} + +static void transitions_scan() { + char cur[64]; + map_name_from_world(cur, sizeof(cur)); + if (strcmp(cur, g_map_last) != 0) { + if (g_map_last[0]) + snprintf(g_map_prev, sizeof(g_map_prev), "%s", g_map_last); + snprintf(g_map_last, sizeof(g_map_last), "%s", cur); + } + int count = server_entity_count(); + int n = 0; + for (int i = 1; i <= count && n < 8; i++) { + TransitionBox &b = g_trans[n]; + b.mins = Vector{0, 0, 0}; + b.maxs = Vector{0, 0, 0}; + if (!server_trigger_info(i, b.next, sizeof(b.next), b.mins, b.maxs)) + continue; + if (b.mins.x == b.maxs.x && b.mins.y == b.maxs.y && + b.mins.z == b.maxs.z) + continue; + n++; + } + g_ntrans = n; + static int logged = 0; + if (n > 0 && logged < 4) { + logged++; + logf("transitions: %d changelevel trigger(s), next=%s prev=%s", n, + g_trans[0].next, g_map_prev[0] ? g_map_prev : "?"); + } +} + +static void draw_transitions() { + if (!g_surface || !g_ntrans) + return; + for (int i = 0; i < g_ntrans; i++) { + const TransitionBox &b = g_trans[i]; + float minx = 1e9f, miny = 1e9f, maxx = -1e9f, maxy = -1e9f; + int seen = 0; + for (int c = 0; c < 8; c++) { + Vector p{(c & 1) ? b.maxs.x : b.mins.x, (c & 2) ? b.maxs.y : b.mins.y, + (c & 4) ? b.maxs.z : b.mins.z}; + float sx, sy; + if (!world_to_screen(p, sx, sy) || !std::isfinite(sx) || + !std::isfinite(sy)) + continue; + if (sx < minx) minx = sx; + if (sx > maxx) maxx = sx; + if (sy < miny) miny = sy; + if (sy > maxy) maxy = sy; + seen++; + } + if (!seen) + continue; + int x0 = (int)minx, x1 = (int)maxx, y0 = (int)miny, y1 = (int)maxy; + if (x1 < -2000 || x0 > g_scr_w + 2000 || y1 < -2000 || + y0 > g_scr_h + 2000) + continue; + surf_color(255, 170, 60, 200); + surf_rect(0, x0, y0, x1, y1); + char line[128]; + snprintf(line, sizeof(line), "-> %.100s", b.next); + int tx = x0; + if (tx < 4) tx = 4; + if (tx > g_scr_w - 120) tx = g_scr_w - 120; + int ty = y0 - 16; + if (ty < 4) ty = 4; + draw_text_small_col(tx + 1, ty + 1, line, 0, 0, 0); + draw_text_small_col(tx, ty, line, 255, 180, 70); + if (g_map_prev[0]) { + snprintf(line, sizeof(line), "prev %.48s", g_map_prev); + draw_text_small_col(tx + 1, ty + 13, line, 0, 0, 0); + draw_text_small_col(tx, ty + 12, line, 200, 200, 200); + } + } +} + void esp_draw() { if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; if (!g_scr_w) { @@ -1523,6 +1821,13 @@ void esp_draw() { } draw_fov_circle(); esp_collect(engine_get_local_player()); + static double last_trans = 0.0; + double trans_now = esp_now(); + if (trans_now - last_trans >= 0.25) { + last_trans = trans_now; + transitions_scan(); + } + draw_transitions(); for (int i = 0; i < g_ntargets; i++) { const EspTarget &t = g_targets[i]; if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL)) @@ -1532,9 +1837,9 @@ void esp_draw() { if (ent && bones_update(t.idx, ent)) draw_skeleton(t); } - if (t.friendly) surf_color(60, 255, 60, 255); - else if (t.neutral) surf_color(220, 220, 220, 255); - else surf_color(255, 60, 60, 255); + int cr, cg, cb; + relation_rgb(t, cr, cg, cb); + surf_color(cr, cg, cb, 255); float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width int left = (int)(t.sx - w / 2); int right = (int)(t.sx + w / 2); @@ -1568,7 +1873,8 @@ void esp_draw() { draw_text_col(num_x, top, hptext, 255, 255, 255); } if (esp_option(ESP_OPT_SHOW_NAME)) - draw_text_col(left, top - 14, t.name, 255, 255, 255); + draw_text_col(left, top - 14, t.name, cr, cg, cb); + int tag_y = top + 2; if (esp_option(ESP_OPT_DAMAGE_TAGS)) { const char *tag = nullptr; int tr = 255, tg = 255, tb = 255; @@ -1583,19 +1889,26 @@ void esp_draw() { tr = 180; tg = 180; tb = 180; } if (tag) { - // Small label on the left edge of the box, black 1px shadow for - // readability against the world (the 10px ESP font). - int tw = (int)strlen(tag) * 6; - int tx = left - tw - 3; - if (tx < 2) - tx = 2; - int ty = top + 2; - draw_text_small_col(tx + 1, ty + 1, tag, 0, 0, 0); - draw_text_small_col(tx, ty, tag, tr, tg, tb); + draw_left_tag(left, tag_y, tag, tr, tg, tb); + tag_y += 12; } } + if (esp_option(ESP_OPT_ALERT)) { + int ar, ag, ab; + const char *tag = alert_tag(t.state, ar, ag, ab); + if (tag) { + draw_left_tag(left, tag_y, tag, ar, ag, ab); + tag_y += 12; + } + } + if (esp_option(ESP_OPT_WEAPON) && t.weapon[0]) { + draw_left_tag(left, tag_y, t.weapon, 220, 220, 220); + tag_y += 12; + } } + draw_edge_arrows(); + // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's // "head" hitbox center (from SetupBones + studio hitboxes). static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr; -- cgit v1.2.3