// esp.cpp — ESP drawing + shared world_to_screen / entity gather (aimbot reuses these) #include "hl2h.h" #include #include #include #include #include // ISurface slots (binary = SDK header + 4, disassembly-confirmed) enum SurfSlot { SURF_DrawSetColor = 10, SURF_FilledRect = 12, SURF_OutlinedRect = 14, SURF_DrawLine = 15, SURF_GetScreenSize = 38, }; enum { LIFE_ALIVE = 0 }; struct EspTarget { int idx; float sx, sy; // screen pos of feet float h; // box height px float health, maxhealth; bool show_health; bool friendly; bool neutral; char name[48]; }; static EspTarget g_targets[128]; static int g_ntargets = 0; static int g_scr_w = 0, g_scr_h = 0; static bool g_esp_logged = false; // Speed graph: 100 samples at 0.1 s = the last 10 seconds. static constexpr int SPEED_SAMPLES = 100; static float g_speed_samples[SPEED_SAMPLES] = {}; static int g_speed_head = 0; static double g_speed_last_sample = 0.0; static double esp_now() { timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; } bool world_to_screen(const Vector &w, float &sx, float &sy) { // Preferred: the engine's own transform. Uses the real view setup (FOV, // aspect, roll), so boxes do not drift at the screen edges. if (g_iface.debugoverlay) { using Fn = int (*)(void *, const Vector *, Vector *); Vector s; int behind = vfunc(g_iface.debugoverlay, 9)(g_iface.debugoverlay, &w, &s); if (behind) return false; sx = s.x; sy = s.y; return true; } // Fallback: local math with the default HL2 FOV. float pitch = g_cam_view.x * PI_F / 180.0f; float yaw = g_cam_view.y * PI_F / 180.0f; float sp = sinf(pitch), cp = cosf(pitch); float syy = sinf(yaw), cy = cosf(yaw); float fwd[3] = {cp * cy, cp * syy, -sp}; float right[3] = {syy, -cy, 0}; float up[3] = {sp * cy, sp * syy, cp}; float dx = w.x - g_cam_eye.x, dy = w.y - g_cam_eye.y, dz = w.z - g_cam_eye.z; float z = dx * fwd[0] + dy * fwd[1] + dz * fwd[2]; if (z < 1.0f) return false; float x = dx * right[0] + dy * right[1] + dz * right[2]; float y = dx * up[0] + dy * up[1] + dz * up[2]; float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f); sx = (float)g_scr_w * 0.5f + (x / z) * ((float)g_scr_w * 0.5f / tanfov); sy = (float)g_scr_h * 0.5f - (y / z) * ((float)g_scr_w * 0.5f / tanfov); return true; } // --------------------------------------------------------------------------- // entity helpers (IClientEntityList / IClientNetworkable, header-documented) // --------------------------------------------------------------------------- using GetNetFn = void *(*)(void *, int); using GetCCFn = void *(*)(void *); using GetModelFn = const void *(*)(void *, int); using GetModelNameFn = const char *(*)(void *, const void *); // entitylist slot 0 = GetClientNetworkable(entnum). Safer than assuming the // IClientNetworkable subobject offset inside the entity (see projectz). static void *entity_networkable(int idx) { return vfunc(g_iface.entitylist, 0)(g_iface.entitylist, idx); } // IClientNetworkable slot 2 = GetClientClass(); ClientClass::m_pNetworkName @ +8. static const char *entity_class_name(void *net, void **out_cc) { if (!net) return nullptr; void *cc = vfunc(net, 2)(net); if (out_cc) *out_cc = cc; return cc ? *(const char **)((uintptr_t)cc + 8) : nullptr; } // m_nModelIndex -> IVModelInfoClient(006)::GetModel/GetModelName. // Itanium ABI: two dtor slots, so GetModel=2, GetModelName=4. static const char *entity_model_name(void *ent) { if (!g_iface.modelinfo || g_net.modelindex < 0) return nullptr; int modelindex = *(int *)((uintptr_t)ent + g_net.modelindex); if (modelindex <= 0) return nullptr; const void *model = vfunc(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex); if (!model) return nullptr; return vfunc(g_iface.modelinfo, 4)(g_iface.modelinfo, model); } // HL2 SP: generic NPCs (combine, metrocop, zombie, citizen, antlion, ...) all // use the single client class "CAI_BaseNPC" — only special NPCs register their // own class names (CNPC_Manhack, CNPC_Strider, CNPC_CombineGunship, ...). static bool is_npc_class(const char *name) { if (!name) return false; return strstr(name, "NPC") != nullptr || strstr(name, "BaseHelicopter") != nullptr; } static bool model_is_friendly(const char *m) { static const char *patterns[] = { "citizen", "humans/", "male_", "female_", "barney", "alyx", "alex", "scientist", "kleiner", "eli", "mossman", "vortigaunt", "dog", "grigori", "monk", "odessa", "gman", nullptr}; if (!m) return false; for (int i = 0; patterns[i]; i++) if (strstr(m, patterns[i])) return true; return false; } // harmless wildlife that also happens to be CAI_BaseNPC static bool model_is_neutral(const char *m) { static const char *patterns[] = {"crow", "gull", "pigeon", "seagull", "bird", nullptr}; if (!m) return false; for (int i = 0; patterns[i]; i++) if (strstr(m, patterns[i])) return true; return false; } static void short_name(const char *model, char *out, size_t outn) { if (!model) { snprintf(out, outn, "NPC"); return; } const char *base = strrchr(model, '/'); base = base ? base + 1 : model; size_t n = strcspn(base, "."); if (n >= outn) n = outn - 1; memcpy(out, base, n); out[n] = 0; } 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. static double last_scan = 0.0; static double last_dbg = 0.0; double now = esp_now(); if (now - last_scan < 0.01) return; last_scan = now; bool dbg = (now - last_dbg) >= 2.0; if (dbg) last_dbg = now; g_ntargets = 0; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex if (hi < 0 || hi > 4096) hi = 4096; int npc_alive = 0, dbg_lines = 0; bool srv = resolve_server_netvars(); static bool srv_checked = false; static bool srv_trusted = false; for (int idx = 1; idx <= hi && g_ntargets < 128; idx++) { if (idx == local_idx) continue; void *ent = entitylist_get(idx); if (!ent) continue; void *net = entity_networkable(idx); if (!net) continue; const char *cls = entity_class_name(net, nullptr); if (!cls) continue; uintptr_t e = (uintptr_t)ent; int cli_life = g_net.lifestate >= 0 ? (int)*(unsigned char *)(e + g_net.lifestate) : -1; int cli_hp = g_net.health >= 0 ? *(int *)(e + g_net.health) : 0; bool npc = is_npc_class(cls); const char *model = entity_model_name(ent); // NPC health is not networked; read it from the listen-server entity. int hp = cli_hp, maxhp = -1, life = cli_life; if (npc && srv) { int slife = -1, smax = -1; void *sbase = nullptr; int shp = server_entity_health(idx, &smax, &slife, &sbase); if (!srv_checked && sbase) { srv_checked = true; int cli_mi = g_net.modelindex >= 0 ? *(int *)(e + g_net.modelindex) : -1; int srv_mi = server_model_index(sbase); srv_trusted = (cli_mi == srv_mi); logf("ESP server check: idx=%d base=%p cli_model=%d srv_model=%d " "hp=%d maxhp=%d life=%d trusted=%d", idx, sbase, cli_mi, srv_mi, shp, smax, slife, (int)srv_trusted); if (!srv_trusted) logf("ESP: server entity mismatch, ignoring server values"); } if (srv_trusted) { if (shp >= 0) hp = shp; if (smax > 0) maxhp = smax; if (slife >= 0) life = slife; } } if (dbg && npc && dbg_lines < 40) { dbg_lines++; logf("ESP npc %d cls=%s life=%d(cli %d) hp=%d(cli %d) maxhp=%d model=%s", idx, cls, life, cli_life, hp, cli_hp, maxhp, model ? model : "?"); } if (npc) { if (life != LIFE_ALIVE) continue; npc_alive++; } else { // players (HL2DM) are the only entities with a live networked m_iHealth if (!strstr(cls, "Player") || !(hp > 0)) continue; } float minz = g_net.mins >= 0 ? *(float *)(e + g_net.mins + 8) : 0.0f; float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; if (!(maxz > minz + 4.0f && maxz - minz < 512.0f)) { minz = 0.0f; maxz = 72.0f; } Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), *(float *)(e + g_net.origin + 8)}; Vector bottom{origin.x, origin.y, origin.z + minz}; Vector top{origin.x, origin.y, origin.z + maxz}; float sx, sy, tx, ty; if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) continue; float h = sy - ty; if (h < 3.0f || sy < 0 || ty > (float)g_scr_h) continue; // behind / offscreen if (sx < -(float)g_scr_w || sx > 2.0f * (float)g_scr_w) continue; EspTarget &t = g_targets[g_ntargets++]; t.idx = idx; t.sx = sx; t.sy = sy; t.h = h; t.health = hp > 0 ? (float)hp : 0.0f; t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; t.show_health = hp > 0; t.friendly = model_is_friendly(model); t.neutral = model_is_neutral(model); short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } if (dbg) logf("ESP scan: hi=%d npc_alive=%d targets=%d", hi, npc_alive, g_ntargets); if (g_ntargets && !g_esp_logged) { g_esp_logged = true; logf("ESP: first targets, %d", g_ntargets); } } static void surf_rect(int filled, int x0, int y0, int x1, int y1) { using Fn = void (*)(void *, int, int, int, int); vfunc(g_surface, filled ? SURF_FilledRect : SURF_OutlinedRect)(g_surface, x0, y0, x1, y1); } static void surf_color(int r, int g, int b, int a) { using Fn = void (*)(void *, int, int, int, int); vfunc(g_surface, SURF_DrawSetColor)(g_surface, r, g, b, a); } static void surf_line(int x0, int y0, int x1, int y1) { using Fn = void (*)(void *, int, int, int, int); vfunc(g_surface, SURF_DrawLine)(g_surface, x0, y0, x1, y1); } // Screen projection of the aimbot FOV cone boundary, using the engine's own // world->screen transform so it follows the actual aspect/fov. static QAngle engine_view_angles() { QAngle v{g_cam_view.x, g_cam_view.y, g_cam_view.z}; if (g_iface.engine) { using Fn = void (*)(void *, QAngle *); vfunc(g_iface.engine, 19)(g_iface.engine, &v); } return v; } static void draw_fov_circle() { if (!aimbot_fov_circle()) return; float fov = (float)aimbot_fov(); if (fov < 1.0f || fov >= 89.0f) // beyond this the "circle" is behind the eye return; float alpha = fov * PI_F / 180.0f; QAngle view = engine_view_angles(); float yaw = view.y * PI_F / 180.0f; float pitch = view.x * PI_F / 180.0f; Vector fwd{cosf(pitch) * cosf(yaw), cosf(pitch) * sinf(yaw), -sinf(pitch)}; Vector right{sinf(yaw), -cosf(yaw), 0.0f}; Vector up{right.y * fwd.z - right.z * fwd.y, right.z * fwd.x - right.x * fwd.z, right.x * fwd.y - right.y * fwd.x}; constexpr float D = 1024.0f; float ca = cosf(alpha), sa = sinf(alpha); Vector eh{g_cam_eye.x + (fwd.x * ca + right.x * sa) * D, g_cam_eye.y + (fwd.y * ca + right.y * sa) * D, g_cam_eye.z + (fwd.z * ca + right.z * sa) * D}; Vector ev{g_cam_eye.x + (fwd.x * ca + up.x * sa) * D, g_cam_eye.y + (fwd.y * ca + up.y * sa) * D, g_cam_eye.z + (fwd.z * ca + up.z * sa) * D}; float hsx, hsy, vsx, vsy; if (!world_to_screen(eh, hsx, hsy) || !world_to_screen(ev, vsx, vsy)) return; float cx = (float)g_scr_w * 0.5f, cy = (float)g_scr_h * 0.5f; float rx = fabsf(hsx - cx), ry = fabsf(vsy - cy); if (!(rx > 1.0f && ry > 1.0f) || rx > 1e5f || ry > 1e5f) return; constexpr int N = 64; int px = 0, py = 0; surf_color(255, 255, 255, 160); for (int i = 0; i <= N; i++) { float phi = (2.0f * PI_F * i) / N; int x = (int)(cx + rx * cosf(phi)); int y = (int)(cy + ry * sinf(phi)); if (i > 0) surf_line(px, py, x, y); px = x; py = y; } } // Speed graph: 100 samples at 0.1 s = the last 10 seconds; min is 0, max is // the window maximum. Small and centered in the lower half of the screen. static void speed_graph_sample() { // Only while actually playing: IVEngineClient013 slot 26 IsInGame, // slot 84 IsPaused. if (g_iface.engine) { using BoolFn = bool (*)(void *); if (!vfunc(g_iface.engine, 26)(g_iface.engine)) return; if (vfunc(g_iface.engine, 84)(g_iface.engine)) return; } double now = esp_now(); if (now - g_speed_last_sample < 0.1) return; g_speed_last_sample = now; float speed = 0.0f; int local = engine_get_local_player(); if (local > 0 && g_net.velocity >= 0) { void *ent = entitylist_get(local); if (ent) { const float *v = (const float *)((uintptr_t)ent + g_net.velocity); speed = sqrtf(v[0] * v[0] + v[1] * v[1]); } } g_speed_samples[g_speed_head] = speed; g_speed_head = (g_speed_head + 1) % SPEED_SAMPLES; } static void draw_speed_graph() { if (!esp_option(ESP_OPT_SHOW_SPEEDGRAPH)) return; speed_graph_sample(); float maxv = 0.0f; for (int i = 0; i < SPEED_SAMPLES; i++) if (g_speed_samples[i] > maxv) maxv = g_speed_samples[i]; float scale = maxv > 1.0f ? maxv : 1.0f; const int w = 240, h = 64; int x = (g_scr_w - w) / 2; int y = (g_scr_h * 9) / 10 - h / 2; if (x < 0) x = 0; if (y < 0) y = 0; surf_color(0, 0, 0, 120); surf_rect(1, x, y, x + w, y + h); surf_color(80, 80, 80, 180); surf_rect(0, x, y, x + w, y + h); surf_color(0, 255, 200, 220); int px = 0, py = 0; for (int i = 0; i < SPEED_SAMPLES; i++) { int idx = (g_speed_head + i) % SPEED_SAMPLES; // oldest -> newest float v = g_speed_samples[idx]; int sx = x + 1 + (i * (w - 2)) / (SPEED_SAMPLES - 1); int sy = y + h - 2 - (int)((v / scale) * (float)(h - 4)); if (sy < y + 1) sy = y + 1; if (i > 0) surf_line(px, py, sx, sy); px = sx; py = sy; } char label[32]; snprintf(label, sizeof(label), "max %d", (int)maxv); draw_text_col(x + 3, y + 2, label, 200, 200, 200); int cur_idx = (g_speed_head + SPEED_SAMPLES - 1) % SPEED_SAMPLES; char cur[16]; snprintf(cur, sizeof(cur), "%d", (int)g_speed_samples[cur_idx]); int cur_w = (int)strlen(cur) * 8; draw_text_col(x + w - 4 - cur_w, y + 2, cur, 255, 255, 255); } // Draw the parent->child bone links. Bone snapshots are world-space matrices // from SetupBones; project each bone once and reuse for both endpoints. static void draw_skeleton(const EspTarget &t) { int n = bones_count(t.idx); if (n <= 0 || n > 256) return; Vector wp[256]; float xs[256], ys[256]; bool ok[256]; int core_count = 0; for (int i = 0; i < n; i++) { ok[i] = false; if (!bone_is_skeleton_core(t.idx, i)) continue; core_count++; ok[i] = bone_pos(t.idx, i, wp[i]) && world_to_screen(wp[i], xs[i], ys[i]) && std::isfinite(xs[i]) && std::isfinite(ys[i]); } static int logged = 0; if (core_count > 0 && logged < 4) { logged++; logf("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); for (int i = 0; i < n; i++) { if (!ok[i]) continue; int p = bone_nearest_core_parent(t.idx, i); if (p >= 0 && p < n && ok[p]) surf_line((int)xs[p], (int)ys[p], (int)xs[i], (int)ys[i]); } } // --------------------------------------------------------------------------- // aimbot v1: nearest hostile NPC head to the crosshair. // Bounded smoothing: never snap, never overshoot. Each tick moves at most // MAX_STEP degrees toward the target and stops inside DEADZONE. // --------------------------------------------------------------------------- static QAngle angle_to(const Vector &from, const Vector &to) { float dx = to.x - from.x, dy = to.y - from.y, dz = to.z - from.z; float horiz = sqrtf(dx * dx + dy * dy); if (horiz < 0.001f) horiz = 0.001f; QAngle a{}; a.x = -atan2f(dz, horiz) * 180.0f / PI_F; // Source: positive pitch = down a.y = atan2f(dy, dx) * 180.0f / PI_F; a.z = 0.0f; return a; } static int g_aim_lock = -1; static int g_silent_lock = -1; void aimbot_reset_lock() { g_aim_lock = -1; } void silentaim_reset_lock() { g_silent_lock = -1; } // max_fov is the angular radius around the crosshair. If lock_idx is non-null // and valid, that target is kept while it is alive and visible (the "do not // switch while the bind is held" rule); a fresh acquisition is ranked by angle // and the nearest visible selected hitbox point. static bool select_aim_target(const QAngle &view, unsigned bone_mask, bool vischeck, float max_fov, const char *tag, int *lock_idx, int &out_idx, Vector &out_point, float &out_fov) { out_idx = -1; out_point = {}; out_fov = 0.0f; if (!g_iface.entitylist || g_net.origin < 0 || g_net.maxs < 0) return false; int local = engine_get_local_player(); if (local <= 0) return false; void *local_ent = entitylist_get(local); if (!local_ent) return false; if (g_net.health >= 0 && *(int *)((uintptr_t)local_ent + g_net.health) <= 0) return false; auto try_target = [&](int idx, bool enforce_fov, Vector &pt, float &fov) -> bool { if (idx <= 0 || idx == local) return false; void *ent = entitylist_get(idx); if (!ent) return false; void *net = entity_networkable(idx); if (!net) return false; const char *cls = entity_class_name(net, nullptr); if (!cls || !is_npc_class(cls)) return false; uintptr_t e = (uintptr_t)ent; int life = g_net.lifestate >= 0 ? (int)*(unsigned char *)(e + g_net.lifestate) : -1; if (life != LIFE_ALIVE) return false; const char *model = entity_model_name(ent); if (model_is_friendly(model) || model_is_neutral(model)) return false; if (!bones_update(idx, ent)) return false; Vector pts[32]; int npoints = aim_points(idx, bone_mask, pts, 32); float best = 1e9f; Vector best_pt{}; for (int i = 0; i < npoints; i++) { float f = getfov(view, angle_to(g_cam_eye, pts[i])); if (enforce_fov && f > max_fov) continue; if (f >= best) continue; if (vischeck && !is_visible(g_cam_eye, pts[i])) continue; best = f; best_pt = pts[i]; } if (best > 1e8f) return false; pt = best_pt; fov = best; return true; }; // Keep the locked target while it is alive and has a visible selected point. // No FOV limit on a locked target; only acquisition is FOV-limited. if (lock_idx && *lock_idx >= 0) { Vector pt; float f; if (try_target(*lock_idx, false, pt, f)) { out_idx = *lock_idx; out_point = pt; out_fov = f; static int lock_logs = 0; if (lock_logs < 5) { lock_logs++; logf("%s: lock idx=%d fov=%.2f", tag, out_idx, f); } return true; } *lock_idx = -1; } using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; struct Candidate { int idx; float fov; Vector approx; }; Candidate cands[16]; int ncands = 0; for (int idx = 1; idx <= hi; idx++) { if (idx == local) continue; void *ent = entitylist_get(idx); if (!ent) continue; void *net = entity_networkable(idx); if (!net) continue; const char *cls = entity_class_name(net, nullptr); if (!cls || !is_npc_class(cls)) continue; uintptr_t e = (uintptr_t)ent; int life = g_net.lifestate >= 0 ? (int)*(unsigned char *)(e + g_net.lifestate) : -1; if (life != LIFE_ALIVE) continue; const char *model = entity_model_name(ent); if (model_is_friendly(model) || model_is_neutral(model)) continue; float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; if (!(maxz > 4.0f && maxz < 512.0f)) maxz = 72.0f; Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), *(float *)(e + g_net.origin + 8)}; Vector approx{origin.x, origin.y, origin.z + maxz * ((bone_mask & (1u << 1)) ? 0.9f : 0.55f)}; float fov = getfov(view, angle_to(g_cam_eye, approx)); if (fov > max_fov) continue; int pos = ncands; while (pos > 0 && cands[pos - 1].fov > fov) pos--; if (ncands < 16) ncands++; for (int j = ncands - 1; j > pos; j--) cands[j] = cands[j - 1]; if (pos < 16) cands[pos] = {idx, fov, approx}; } if (ncands == 0) { static double last_none = 0.0; double now = esp_now(); if (now - last_none >= 2.0) { last_none = now; logf("%s: no hostile target within %.0f deg", tag, max_fov); } return false; } for (int ci = 0; ci < ncands; ci++) { Vector pt; float f; if (!try_target(cands[ci].idx, true, pt, f)) continue; out_idx = cands[ci].idx; out_point = pt; out_fov = f; if (lock_idx) *lock_idx = out_idx; static int sel_logs = 0; if (sel_logs < 5) { sel_logs++; logf("%s: target idx=%d fov=%.2f vischeck=%d", tag, out_idx, f, (int)vischeck); } return true; } static double last_vis = 0.0; double now = esp_now(); if (now - last_vis >= 2.0) { last_vis = now; logf("%s: no visible aim point (vischeck=%d)", tag, (int)vischeck); } return false; } bool aimbot_apply(CUserCmd *cmd) { unsigned bone_mask = aimbot_bone_mask(); if (!bone_mask) return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; int target = -1; Vector point{}; float fov = 0.0f; 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_AIMBOT) ? &g_aim_lock : nullptr; if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov, "aimbot", lock, target, point, fov)) return false; QAngle desired = angle_to(g_cam_eye, point); static int want_logs = 0; if (want_logs < 10) { want_logs++; logf("aimbot: target=%d aim=(%.1f %.1f %.1f) fov=%.2f want=%.2f/%.2f", target, point.x, point.y, point.z, fov, desired.x, desired.y); } float dp = desired.x - cmd->viewangles[0]; float dy = desired.y - cmd->viewangles[1]; while (dy > 180.0f) dy -= 360.0f; while (dy < -180.0f) dy += 360.0f; constexpr float DEADZONE = 0.05f; float max_step = (float)aimbot_smooth_step(); if (max_step < 1.0f) max_step = 1.0f; float maxd = fmaxf(fabsf(dp), fabsf(dy)); if (maxd <= DEADZONE) return false; float scale = (maxd > max_step) ? (max_step / maxd) : 1.0f; float old_pitch = cmd->viewangles[0]; float old_yaw = cmd->viewangles[1]; float np = cmd->viewangles[0] + dp * scale; float ny = cmd->viewangles[1] + dy * scale; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; while (ny > 180.0f) ny -= 360.0f; while (ny < -180.0f) ny += 360.0f; cmd->viewangles[0] = np; cmd->viewangles[1] = ny; if (g_iface.engine) { QAngle out{np, ny, 0.0f}; using SetViewFn = void (*)(void *, QAngle *); vfunc(g_iface.engine, 20)(g_iface.engine, &out); } static int mod_logs = 0; if (mod_logs < 10) { mod_logs++; logf("aimbot: view %.2f/%.2f -> %.2f/%.2f", old_pitch, old_yaw, np, ny); } return true; } // Keep the world-space movement wish direction unchanged when the usercmd // view angles are temporarily replaced by the silent-aim angles. static void preserve_movement(CUserCmd *cmd, float old_yaw, float new_yaw) { float oy = old_yaw * PI_F / 180.0f; float ay = new_yaw * PI_F / 180.0f; float ofx = cosf(oy), ofy = sinf(oy); float orx = sinf(oy), ory = -cosf(oy); float afx = cosf(ay), afy = sinf(ay); float arx = sinf(ay), ary = -cosf(ay); float wx = ofx * cmd->forwardmove + orx * cmd->sidemove; float wy = ofy * cmd->forwardmove + ory * cmd->sidemove; cmd->forwardmove = wx * afx + wy * afy; cmd->sidemove = wx * arx + wy * ary; } bool silent_aim_apply(CUserCmd *cmd) { unsigned bone_mask = aimbot_bone_mask(); if (!bone_mask) return false; QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle view{orig.x, orig.y, 0.0f}; int target = -1; Vector point{}; float fov = 0.0f; float max_fov = (float)silentaim_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)) return false; QAngle desired = angle_to(g_cam_eye, point); float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; float ny = desired.y; while (ny > 180.0f) ny -= 360.0f; while (ny < -180.0f) ny += 360.0f; preserve_movement(cmd, orig.y, ny); cmd->viewangles[0] = np; cmd->viewangles[1] = ny; cmd->viewangles[2] = orig.z; // Restore now and again at FRAME_RENDER_START (after the engine consumed // the aimed usercmd); the frame-stage restore is the one that sticks. set_silent_restore(orig); if (g_iface.engine) { QAngle restore{orig.x, orig.y, orig.z}; using SetViewFn = void (*)(void *, QAngle *); vfunc(g_iface.engine, 20)(g_iface.engine, &restore); } cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 5) { logs++; logf("silentaim: fire idx=%d fov=%.2f aim=%.2f/%.2f", target, fov, np, ny); } return true; } // --------------------------------------------------------------------------- // melee killbot // --------------------------------------------------------------------------- // // The crowbar's CBaseHLBludgeonWeapon::Swing traces 75 units from // Weapon_ShootPosition along the view forward (CROWBAR_RANGE in // weapon_crowbar.h). It first tries a line, then a +/-16 hull backed off by // ~28 units, and ignores targets more than ~45 degrees off the aim direction. // We therefore only need to point the usercmd at the target for one command; // the rendered view is restored exactly like silent aim. static constexpr float MELEE_RANGE = 75.0f; static float vec_dist(const Vector &a, const Vector &b) { float dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z; return sqrtf(dx * dx + dy * dy + dz * dz); } bool melee_weapon_active(void *local_ent) { if (!local_ent) return false; int local = engine_get_local_player(); auto check = [&](int idx, const char **cls_out, const char **model_out) -> bool { if (idx <= 0 || idx > 4096) return false; void *wep = entitylist_get(idx); void *net = entity_networkable(idx); const char *cls = net ? entity_class_name(net, nullptr) : nullptr; const char *model = wep ? entity_model_name(wep) : nullptr; *cls_out = cls; *model_out = model; return (cls && strstr(cls, "Crowbar")) || (model && strstr(model, "crowbar")); }; int idx = -1; if (g_net.activeweapon >= 0) { uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); if (h != 0xFFFFFFFFu) idx = (int)(h & 0xFFFF); } const char *cls = nullptr, *model = nullptr; bool crowbar = check(idx, &cls, &model); if (!crowbar) { // Fallback: the authoritative server player's m_hActiveWeapon handle. // Also covers a client netvar offset that resolves but does not point at // the weapon the server is actually using. if (local > 0) { resolve_server_netvars(); int si = server_active_weapon(local); if (si > 0 && si != idx) { idx = si; crowbar = check(idx, &cls, &model); } } } static int last_idx = -2; static bool last_crowbar = false; if (idx != last_idx || crowbar != last_crowbar) { last_idx = idx; last_crowbar = crowbar; logf("melee: active idx=%d cls=%s model=%s crowbar=%d", idx, cls ? cls : "?", model ? model : "?", (int)crowbar); } return crowbar; } bool melee_aimbot_apply(CUserCmd *cmd) { if (!g_iface.entitylist || g_net.origin < 0) return false; int local = engine_get_local_player(); if (local <= 0) return false; void *local_ent = entitylist_get(local); if (!local_ent) return false; if (g_net.health >= 0 && *(int *)((uintptr_t)local_ent + g_net.health) <= 0) return false; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; struct Cand { int idx; float dist; Vector closest; Vector center; }; Cand cands[16]; int n = 0; for (int idx = 1; idx <= hi; idx++) { if (idx == local) continue; void *ent = entitylist_get(idx); if (!ent) continue; void *net = entity_networkable(idx); if (!net) continue; const char *cls = entity_class_name(net, nullptr); if (!cls || !is_npc_class(cls)) continue; uintptr_t e = (uintptr_t)ent; int life = g_net.lifestate >= 0 ? (int)*(unsigned char *)(e + g_net.lifestate) : -1; if (life != LIFE_ALIVE) continue; const char *model = entity_model_name(ent); if (model_is_friendly(model) || model_is_neutral(model)) continue; Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), *(float *)(e + g_net.origin + 8)}; float minx = -16.0f, miny = -16.0f, minz = 0.0f; float maxx = 16.0f, maxy = 16.0f, maxz = 72.0f; if (g_net.mins >= 0) { minx = *(float *)(e + g_net.mins); miny = *(float *)(e + g_net.mins + 4); minz = *(float *)(e + g_net.mins + 8); } if (g_net.maxs >= 0) { maxx = *(float *)(e + g_net.maxs); maxy = *(float *)(e + g_net.maxs + 4); maxz = *(float *)(e + g_net.maxs + 8); } if (!(maxz > minz + 4.0f && maxz < 512.0f)) { minz = 0.0f; maxz = 72.0f; } if (!(maxx > minx + 1.0f && maxx < 256.0f)) { minx = -16.0f; maxx = 16.0f; } if (!(maxy > miny + 1.0f && maxy < 256.0f)) { miny = -16.0f; maxy = 16.0f; } Vector lo{origin.x + minx, origin.y + miny, origin.z + minz}; Vector hiV{origin.x + maxx, origin.y + maxy, origin.z + maxz}; Vector closest{g_cam_eye.x, g_cam_eye.y, g_cam_eye.z}; if (closest.x < lo.x) closest.x = lo.x; if (closest.x > hiV.x) closest.x = hiV.x; if (closest.y < lo.y) closest.y = lo.y; if (closest.y > hiV.y) closest.y = hiV.y; if (closest.z < lo.z) closest.z = lo.z; if (closest.z > hiV.z) closest.z = hiV.z; float d = vec_dist(g_cam_eye, closest); if (d > MELEE_RANGE) continue; Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f, (lo.z + hiV.z) * 0.5f}; int pos = n; while (pos > 0 && cands[pos - 1].dist > d) pos--; if (n < 16) n++; else if (pos >= 16) continue; for (int j = n - 1; j > pos; j--) cands[j] = cands[j - 1]; cands[pos] = {idx, d, closest, center}; } for (int ci = 0; ci < n; ci++) { const Cand &c = cands[ci]; Vector aim{}; bool have_aim = false; // Prefer the nearest visible hitbox center. This handles small NPCs // (headcrabs, antlions) whose AABB center can be less useful than the // actual body hitboxes. void *tent = entitylist_get(c.idx); if (tent && bones_update(c.idx, tent)) { Vector pts[32]; int np = aim_points(c.idx, 0xFE, pts, 32); float bestd = 1e9f; for (int i = 0; i < np; i++) { float pd = vec_dist(g_cam_eye, pts[i]); if (pd > MELEE_RANGE || pd >= bestd) continue; if (!is_visible(g_cam_eye, pts[i])) continue; bestd = pd; aim = pts[i]; have_aim = true; } } if (!have_aim) { aim = c.center; float ad = vec_dist(g_cam_eye, aim); if (ad > MELEE_RANGE) { // The body is in range but its center is not: aim just inside the // closest point on the AABB. aim = c.closest; aim.x += (c.center.x - c.closest.x) * 0.25f; aim.y += (c.center.y - c.closest.y) * 0.25f; aim.z += (c.center.z - c.closest.z) * 0.25f; ad = vec_dist(g_cam_eye, aim); if (ad > MELEE_RANGE) aim = c.closest; } if (!is_visible(g_cam_eye, aim)) continue; } QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle desired = angle_to(g_cam_eye, aim); float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; float ny = desired.y; while (ny > 180.0f) ny -= 360.0f; while (ny < -180.0f) ny += 360.0f; preserve_movement(cmd, orig.y, ny); cmd->viewangles[0] = np; cmd->viewangles[1] = ny; cmd->viewangles[2] = orig.z; set_silent_restore(orig); if (g_iface.engine) { QAngle restore{orig.x, orig.y, orig.z}; using SetViewFn = void (*)(void *, QAngle *); vfunc(g_iface.engine, 20)(g_iface.engine, &restore); } cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 10) { logs++; logf("melee: swing idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", c.idx, c.dist, aim.x, aim.y, aim.z); } return true; } static double last_none = 0.0; double now = esp_now(); if (now - last_none >= 2.0) { last_none = now; logf("melee: no visible target within %.0f", MELEE_RANGE); } return false; } // --------------------------------------------------------------------------- // weapon spread control // --------------------------------------------------------------------------- // // HL2's CBaseEntity::FireBullets uses CBaseEntity::GetPredictionRandomSeed() // (the client usercmd's random_seed) unless a weapon opts into // m_bUseServerRandomSeed. HL2 weapons do not, so the client can choose the // 8-bit seed and with it the random cone offset. CShotManipulator::ApplySpread // draws four uniform floats per attempt; we precompute the best seed for one // shot and for the shotgun's pellet windows with the engine's own RNG. static bool g_spread_ready = false; static int g_spread_seed1 = 0; static int g_spread_seed8 = 0; // first pellet exact, 7 spread static int g_spread_seed12 = 0; // all 12 spread int g_spread_pellets = 1; static void spread_compute() { using SeedFn = void (*)(int); using FloatFn = float (*)(float, float); SeedFn seed = (SeedFn)dlsym(RTLD_DEFAULT, "RandomSeed"); FloatFn rf = (FloatFn)dlsym(RTLD_DEFAULT, "RandomFloat"); if (!seed || !rf) { size_t slash = g_engine.path.find_last_of('/'); if (slash != std::string::npos) { std::string path = g_engine.path.substr(0, slash + 1) + "libvstdlib.so"; void *h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); if (!h) h = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL); if (h) { seed = (SeedFn)dlsym(h, "RandomSeed"); rf = (FloatFn)dlsym(h, "RandomFloat"); } } } if (!seed || !rf) { logf("spread: RandomSeed/RandomFloat unavailable"); return; } auto draw = [&](int s, float &x, float &y) { seed(s); int it = 0; do { // CShotManipulator::ApplySpread with the default bias of 1.0: // shotBias = 1.0, flatness = 0.5. x = rf(-1.0f, 1.0f) * 0.5f + rf(-1.0f, 1.0f) * 0.5f; y = rf(-1.0f, 1.0f) * 0.5f + rf(-1.0f, 1.0f) * 0.5f; it++; } while (x * x + y * y > 1.0f && it < 64); }; float best1 = 1e9f; for (int s = 0; s < 256; s++) { float x, y; draw(s, x, y); float r2 = x * x + y * y; if (r2 < best1) { best1 = r2; g_spread_seed1 = s; } } auto window = [&](int n, int first) { int best_s = 0; float best = 1e9f; for (int s = 0; s < 256; s++) { float score = 0.0f; for (int i = first; i < n; i++) { float x, y; draw(s + i, x, y); score += x * x + y * y; } if (score < best) { best = score; best_s = s; } } return best_s; }; g_spread_seed8 = window(8, 1); g_spread_seed12 = window(12, 0); seed(0); logf("spread: seeds single=%d shotgun8=%d shotgun12=%d", g_spread_seed1, g_spread_seed8, g_spread_seed12); g_spread_ready = true; } static bool active_weapon_is_shotgun(void *local_ent) { if (!local_ent || g_net.activeweapon < 0) return false; uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); if (h == 0xFFFFFFFFu) return false; int idx = (int)(h & 0xFFFF); if (idx <= 0 || idx > 4096) return false; void *wep = entitylist_get(idx); void *net = entity_networkable(idx); const char *cls = net ? entity_class_name(net, nullptr) : nullptr; const char *model = wep ? entity_model_name(wep) : nullptr; return (cls && strstr(cls, "Shotgun")) || (model && strstr(model, "shotgun")); } bool spread_control_apply(CUserCmd *cmd, void *local_ent) { if (!feature_active(FEAT_SPREAD) || !spread_no_spread_enabled()) return false; if (!(cmd->buttons & (IN_ATTACK | IN_ATTACK2))) return false; if (!g_spread_ready) { static bool tried = false; if (!tried) { tried = true; spread_compute(); } } if (!g_spread_ready) return false; bool shotgun = active_weapon_is_shotgun(local_ent); int pellets = 1; if (shotgun) pellets = (cmd->buttons & IN_ATTACK2) ? 12 : 8; g_spread_pellets = pellets; int seed = spread_best_seed(pellets); if (cmd->random_seed == seed) return false; cmd->random_seed = seed; static int logs = 0; if (logs < 10) { logs++; logf("spread: seed=%d pellets=%d", seed, pellets); } return true; } int spread_best_seed(int pellets) { if (!g_spread_ready) return 33; if (pellets >= 12) return g_spread_seed12; if (pellets >= 8) return g_spread_seed8; return g_spread_seed1; } bool triggerbot_apply(CUserCmd *cmd) { if (!g_iface.enginetrace || !g_iface.entitylist) return false; int local = engine_get_local_player(); if (local <= 0) return false; void *local_ent = entitylist_get(local); if (!local_ent) return false; if (g_net.health >= 0 && *(int *)((uintptr_t)local_ent + g_net.health) <= 0) return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; 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, start.z + fwd.z * 8192.0f}; void *hit = trace_entity(start, end, local_ent); if (!hit) return false; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; for (int idx = 1; idx <= hi; idx++) { if (entitylist_get(idx) != hit) continue; void *net = entity_networkable(idx); const char *cls = net ? entity_class_name(net, nullptr) : nullptr; if (!cls || !is_npc_class(cls)) return false; uintptr_t e = (uintptr_t)hit; int life = g_net.lifestate >= 0 ? (int)*(unsigned char *)(e + g_net.lifestate) : -1; if (life != LIFE_ALIVE) return false; const char *model = entity_model_name(hit); if (model_is_friendly(model) || model_is_neutral(model)) return false; cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 5) { logs++; logf("triggerbot: fire idx=%d model=%s", idx, model ? model : "?"); } return true; } return false; } void esp_draw() { if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; if (!g_scr_w) { using Fn = void (*)(void *, int &, int &); vfunc(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h); logf("ESP: screen %dx%d", g_scr_w, g_scr_h); if (!g_scr_w) return; } draw_fov_circle(); esp_collect(engine_get_local_player()); for (int i = 0; i < g_ntargets; i++) { const EspTarget &t = g_targets[i]; if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL)) continue; if (esp_option(ESP_OPT_SHOW_SKELETON)) { void *ent = entitylist_get(t.idx); 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); 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); int top = (int)(t.sy - t.h); int bot = (int)t.sy; if (esp_option(ESP_OPT_SHOW_BOX)) surf_rect(0, left, top, right, bot); bool draw_bar = t.show_health && esp_option(ESP_OPT_SHOW_HEALTHBAR); bool draw_num = t.show_health && esp_option(ESP_OPT_SHOW_HEALTHNUM); int num_x = right + 3; if (draw_bar) { float frac = t.health / (t.maxhealth > 1.0f ? t.maxhealth : 100.0f); if (frac > 1) frac = 1; if (frac < 0) frac = 0; // vertical bar just right of the box, HP number beside it int bx0 = right + 2, bx1 = right + 5; surf_color(0, 0, 0, 200); surf_rect(1, bx0, top, bx1, bot); int fill_top = bot - (int)((float)(bot - top) * frac); if (fill_top < top) fill_top = top; if (frac > 0.5f) surf_color(40, 255, 40, 255); else if (frac > 0.25f) surf_color(255, 255, 0, 255); else surf_color(255, 40, 40, 255); if (fill_top < bot) surf_rect(1, bx0, fill_top, bx1, bot); num_x = bx1 + 3; } if (draw_num) { char hptext[16]; snprintf(hptext, sizeof(hptext), "%d", (int)t.health); 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); } // 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; if (bone_debug) { static bool head_logged = false; for (int i = 0; i < g_ntargets; i++) { const EspTarget &t = g_targets[i]; void *ent = entitylist_get(t.idx); if (!ent || !bones_update(t.idx, ent)) continue; Vector head; if (!head_position(t.idx, head)) { static int no_head = 0; if (no_head < 4) { no_head++; logf("head: idx=%d no head hitbox/bone", t.idx); } continue; } float hx, hy; if (!world_to_screen(head, hx, hy)) continue; surf_color(255, 255, 255, 255); surf_rect(1, (int)hx - 2, (int)hy - 2, (int)hx + 3, (int)hy + 3); if (!head_logged) { head_logged = true; logf("bone debug: idx=%d bones=%d head=(%.1f %.1f %.1f)", t.idx, bones_count(t.idx), head.x, head.y, head.z); } } } draw_speed_graph(); }