// esp.cpp — ESP drawing + shared world_to_screen / entity gather (aimbot reuses these) #include "hl2h.h" #include #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 *model_name_from_index(int modelindex) { if (!g_iface.modelinfo || 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); } 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); return model_name_from_index(modelindex); } // 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) { // Lamarr is Kleiner's pet headcrab: scripted and harmless, and the user // wants her shown neutral rather than hostile. static const char *patterns[] = {"crow", "gull", "pigeon", "seagull", "bird", "Lamarr", nullptr}; if (!m) return false; for (int i = 0; patterns[i]; i++) if (strstr(m, patterns[i])) return true; return false; } static bool model_is_floor_turret(const char *m) { return m && strstr(m, "floor_turret"); } static bool npc_is_friendly(int idx, const char *model, int local_idx, bool &neutral) { int rel = server_npc_relation(idx, local_idx); int state = server_npc_state(idx); int turret = -1; // Citizen-built and Alyx-hacked turrets never add an m_Relationship row; // their Classify() (CLASS_PLAYER_ALLY) comes from server runtime state. if (model_is_floor_turret(model)) turret = server_npc_turret_flags(idx); // Debug-only: relation/state changes are the authoritative signals and the // first thing to check when a friendly NPC still looks hostile. static int rel_cache[2048]; static int state_cache[2048]; static bool cache_init = false; if (!cache_init) { for (int i = 0; i < 2048; i++) rel_cache[i] = state_cache[i] = -999; cache_init = true; } if (idx > 0 && idx < 2048 && (rel_cache[idx] != rel || state_cache[idx] != state)) { rel_cache[idx] = rel; state_cache[idx] = state; logd("npc %d (%s): relation=%d state=%d followtarget=%d turret=%d " "local=%d", idx, model ? model : "?", rel, state, (int)server_npc_has_follow_target(idx), turret, local_idx); } if (rel == 3) { // D_LI neutral = false; return true; } if (turret > 0) { // citizen-built / Alyx-hacked neutral = false; return true; } if (rel == 4) { // D_NU neutral = true; return false; } // NPC_STATE_SCRIPT = 4: cutscene actors and scripted pets are passive even // when their class relationship says hate (Lamarr, scripted Combine, ...). if (state == 4) { neutral = true; return false; } if (rel == 1 || rel == 2) { // D_HT / D_FR neutral = false; return false; } if (turret >= 0) { // ordinary floor turret neutral = false; return false; } // Bugbait-controlled antlions follow a target even when their default // relationship was never rewritten. if (model && strstr(model, "antlion") && server_npc_has_follow_target(idx)) { neutral = false; return true; } bool friendly = model_is_friendly(model); neutral = model_is_neutral(model); return friendly; } 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); logd("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++; logd("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; bool neutral = false; t.friendly = npc_is_friendly(idx, model, local_idx, neutral); t.neutral = neutral; short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } if (dbg) logd("ESP scan: hi=%d npc_alive=%d targets=%d", hi, npc_alive, g_ntargets); if (g_ntargets && !g_esp_logged) { g_esp_logged = true; logd("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++; 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); 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); bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) 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], local_ent, ent)) 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++; logd("%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); bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) 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; logd("%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++; logd("%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; logd("%s: no visible aim point (vischeck=%d)", tag, (int)vischeck); } return false; } static bool active_weapon_contains(void *local_ent, const char *needle) { if (!local_ent || g_net.activeweapon < 0 || !needle) return false; uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); if (h == 0xFFFFFFFFu) return false; int idx = (int)(h & 0x7FF); // networked EHANDLE: 11-bit entity index 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, needle)) || (model && strstr(model, needle)); } // Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the // projectile travel time; the crossbow bolt also has gravity 0.05 (30 u/s^2). static bool projectile_lead_point(int target_idx, const Vector &point, Vector &out) { int local = engine_get_local_player(); if (local <= 0) return false; void *local_ent = entitylist_get(local); if (!local_ent) return false; float speed = 0.0f, gravity = 0.0f; if (active_weapon_contains(local_ent, "crossbow") || active_weapon_contains(local_ent, "Crossbow")) { speed = 2500.0f; gravity = 30.0f; } else if (active_weapon_contains(local_ent, "rocket_launcher") || active_weapon_contains(local_ent, "w_rpg") || active_weapon_contains(local_ent, "RPG")) { speed = 1500.0f; gravity = 0.0f; } else { return false; } void *target = entitylist_get(target_idx); if (!target || g_net.velocity < 0) return false; const float *v = (const float *)((uintptr_t)target + g_net.velocity); float dx = point.x - g_cam_eye.x; float dy = point.y - g_cam_eye.y; float dz = point.z - g_cam_eye.z; float dist = sqrtf(dx * dx + dy * dy + dz * dz); float t = dist / speed; out = point; out.x += v[0] * t; out.y += v[1] * t; out.z += v[2] * t + 0.5f * gravity * t * t; return true; } 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; Vector lead; if (projectile_lead_point(target, point, lead)) point = lead; QAngle desired = angle_to(g_cam_eye, point); static int want_logs = 0; if (want_logs < 10) { want_logs++; logd("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++; logd("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; Vector lead; if (projectile_lead_point(target, point, lead)) point = lead; 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++; logd("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(); if (g_iface.engineserver) resolve_server_netvars(); 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; // Client weapon models can be missing/0 after a map change; recover the // server model index so the crowbar is still recognized. if ((!model || !*model) && g_iface.engineserver) { void *sbase = nullptr; server_entity_health(idx, nullptr, nullptr, &sbase); if (sbase) model = model_name_from_index(server_model_index(sbase)); } *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 & 0x7FF); // 11-bit entity index; upper bits are serial } 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); if (!crowbar) { static bool warned = false; if (!warned) { warned = true; toastf(TOAST_WARN, "Melee killbot: equip the 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); bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) 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], local_ent, tent)) 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, local_ent, tent)) 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++; logd("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; logd("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); logd("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 & 0x7FF); // networked EHANDLE: 11-bit entity index 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++; logd("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); bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) return false; cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 5) { logs++; logd("triggerbot: fire idx=%d model=%s", idx, model ? model : "?"); } return true; } return false; } // Tints dynamic props by writing their client render colour. StudioRender // uses m_clrRender as the per-model colour modulation, which is why changing // the prop MATERIAL colour had no effect. void prop_tint_update() { static bool applied = false; static bool logged_missing = false; static bool logged_touched = false; bool on = feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; if (on) studio_props_init(); if (!on && !applied) return; if (!g_iface.entitylist || g_net.clr_render < 0) { if (on && !logged_missing) { logged_missing = true; logf("prop tint: m_clrRender unavailable"); } return; } static double last = 0.0; double now = esp_now(); if (on && now - last < 0.01) return; last = now; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; uint32_t col = on ? ((uint32_t)g_tint_prop_rgb[0] | ((uint32_t)g_tint_prop_rgb[1] << 8) | ((uint32_t)g_tint_prop_rgb[2] << 16) | (255u << 24)) : 0xFFFFFFFFu; int touched = 0; for (int idx = 1; idx <= hi; idx++) { void *ent = entitylist_get(idx); if (!ent) continue; const char *model = entity_model_name(ent); if (!model || strncmp(model, "models/props", 12) != 0) continue; *(uint32_t *)((uintptr_t)ent + (uintptr_t)g_net.clr_render) = col; touched++; } applied = on; if (on && touched > 0 && !logged_touched) { logged_touched = true; logf("prop tint: %d prop entities", touched); } } 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); logd("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++; logd("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; logd("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(); } // --------------------------------------------------------------------------- // Valuable item tags // --------------------------------------------------------------------------- enum ItemKind { IK_HEALTH, IK_AMMO, IK_ARMOR, IK_WEAPON, IK_CRATE, IK_OTHER }; struct ItemTarget { int idx; int kind; Vector origin; char label[128]; }; static ItemTarget g_item_targets[192]; static int g_nitems = 0; static bool g_items_logged = false; static bool item_icontains(const char *hay, const char *needle) { if (!hay || !needle || !*needle) return false; size_t n = strlen(needle); for (const char *p = hay; *p; p++) if (strncasecmp(p, needle, n) == 0) return true; return false; } static bool item_handle_valid(uint32_t h) { return h != 0 && h != 0xFFFFFFFFu; } // Weapons held by the player or an NPC are not world pickups. static bool item_weapon_is_owned(void *ent) { if (!ent) return false; if (g_net.owner >= 0) { uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.owner); if (item_handle_valid(h)) return true; } if (g_net.weapon_owner >= 0) { uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.weapon_owner); if (item_handle_valid(h)) return true; } return false; } static int local_active_weapon_idx(int local_idx) { if (local_idx <= 0 || g_net.activeweapon < 0) return -1; void *local = entitylist_get(local_idx); if (!local) return -1; uint32_t h = *(uint32_t *)((uintptr_t)local + g_net.activeweapon); if (!item_handle_valid(h)) return -1; return (int)(h & 0x7FF); } static int classify_weapon(const char *model, const char *cls, char *label, size_t outn) { if (!item_icontains(model, "models/weapons/w_") && !item_icontains(cls, "weapon")) return 0; if (item_icontains(model, "w_crowbar")) snprintf(label, outn, "Crowbar"); else if (item_icontains(model, "w_pistol")) snprintf(label, outn, "Pistol"); else if (item_icontains(model, "w_357")) snprintf(label, outn, "Revolver"); else if (item_icontains(model, "w_smg1") || item_icontains(model, "w_smg2")) snprintf(label, outn, "SMG"); else if (item_icontains(model, "w_ar2") || item_icontains(model, "w_irifle")) snprintf(label, outn, "AR2"); else if (item_icontains(model, "w_shotgun")) snprintf(label, outn, "Shotgun"); else if (item_icontains(model, "w_crossbow")) snprintf(label, outn, "Crossbow"); else if (item_icontains(model, "w_rocket_launcher")) snprintf(label, outn, "RPG"); else if (item_icontains(model, "w_frag") || item_icontains(model, "w_grenade")) snprintf(label, outn, "Grenade"); else if (item_icontains(model, "w_physcannon") || item_icontains(model, "w_physics")) snprintf(label, outn, "Gravity gun"); else if (item_icontains(model, "w_stunstick") || item_icontains(model, "w_stunbaton")) snprintf(label, outn, "Stunstick"); else if (item_icontains(model, "w_bugbait")) snprintf(label, outn, "Bugbait"); else if (item_icontains(model, "w_sniper")) snprintf(label, outn, "Sniper"); else if (item_icontains(model, "w_slam")) snprintf(label, outn, "SLAM"); else snprintf(label, outn, "%s", model); return 1; } static int classify_item(const char *model, const char *cls, char *label, size_t outn) { if (!model && !cls) return -1; // Breakable prop chunks are debris, not pickups. if (item_icontains(model, "chunk")) return -1; if (item_icontains(model, "healthkit")) { snprintf(label, outn, "Medkit"); return IK_HEALTH; } if (item_icontains(model, "healthvial")) { snprintf(label, outn, "Health vial"); return IK_HEALTH; } if (item_icontains(model, "health_charger")) { snprintf(label, outn, "Health charger"); return IK_HEALTH; } if (item_icontains(model, "battery")) { snprintf(label, outn, "Battery"); return IK_ARMOR; } if (item_icontains(model, "hevsuit")) { snprintf(label, outn, "HEV suit"); return IK_ARMOR; } if (item_icontains(model, "w_longjump")) { snprintf(label, outn, "Long jump"); return IK_ARMOR; } if (item_icontains(model, "suit_charger")) { snprintf(label, outn, "Suit charger"); return IK_ARMOR; } // Only the two item-crate models are supply crates. Other crates are just // breakable map props and deliberately not tagged. if (item_icontains(model, "item_item_crate") || item_icontains(model, "item_beacon_crate")) { snprintf(label, outn, "Supply crate"); return IK_CRATE; } if (item_icontains(model, "ammocrate_pistol")) { snprintf(label, outn, "Pistol ammo crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_smg1")) { snprintf(label, outn, "SMG ammo crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_ar2")) { snprintf(label, outn, "AR2 ammo crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_rockets")) { snprintf(label, outn, "RPG ammo crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_buckshot")) { snprintf(label, outn, "Shotgun ammo crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_grenade")) { snprintf(label, outn, "Grenade crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate_smg2")) { snprintf(label, outn, "SMG grenade crate"); return IK_AMMO; } if (item_icontains(model, "ammocrate")) { snprintf(label, outn, "Ammo crate"); return IK_AMMO; } if (item_icontains(model, "w_missile_closed")) { snprintf(label, outn, "RPG rocket"); return IK_AMMO; } if (item_icontains(model, "boxsrounds")) { snprintf(label, outn, "Pistol ammo"); return IK_AMMO; } if (item_icontains(model, "boxmrounds")) { snprintf(label, outn, "SMG ammo"); return IK_AMMO; } if (item_icontains(model, "boxlrounds") || item_icontains(model, "combine_rifle_cartridge") || item_icontains(model, "combine_rifle_ammo")) { snprintf(label, outn, "AR2 ammo"); return IK_AMMO; } if (item_icontains(model, "357ammo") || item_icontains(model, "357ammobox")) { snprintf(label, outn, ".357 ammo"); return IK_AMMO; } if (item_icontains(model, "crossbowrounds")) { snprintf(label, outn, "Crossbow bolts"); return IK_AMMO; } if (item_icontains(model, "boxbuckshot")) { snprintf(label, outn, "Shotgun shells"); return IK_AMMO; } if (item_icontains(model, "boxsniperrounds")) { snprintf(label, outn, "Sniper ammo"); return IK_AMMO; } if (item_icontains(model, "boxflares") || item_icontains(model, "models/items/flare")) { snprintf(label, outn, "Flares"); return IK_AMMO; } if (item_icontains(model, "ar2_grenade")) { snprintf(label, outn, "AR2 grenade"); return IK_AMMO; } if (item_icontains(model, "models/items/")) { snprintf(label, outn, "Ammo"); return IK_AMMO; } if (classify_weapon(model, cls, label, outn) == 1) return IK_WEAPON; if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") || item_icontains(cls, "wallhealth")) { snprintf(label, outn, "Health"); return IK_HEALTH; } if (item_icontains(cls, "battery") || item_icontains(cls, "itemsuit") || item_icontains(cls, "recharge")) { snprintf(label, outn, "Armor"); return IK_ARMOR; } if (item_icontains(cls, "itemcrate")) { snprintf(label, outn, "Supply crate"); return IK_CRATE; } if (item_icontains(cls, "item_ammo") || item_icontains(cls, "itemammo")) { snprintf(label, outn, "Ammo"); return IK_AMMO; } if (item_icontains(cls, "weapon")) { snprintf(label, outn, "Weapon"); return IK_WEAPON; } if (item_icontains(cls, "item") || item_icontains(cls, "pickup")) { snprintf(label, outn, "Item"); return IK_OTHER; } return -1; } static bool item_kind_enabled(int kind) { switch (kind) { case IK_HEALTH: return g_item_health; case IK_AMMO: return g_item_ammo; case IK_ARMOR: return g_item_armor; case IK_WEAPON: return g_item_weapons; case IK_CRATE: return g_item_crates; default: return true; } } static void items_collect(int local_idx) { static double last_scan = 0.0; double now = esp_now(); if (now - last_scan < 0.10) return; last_scan = now; g_nitems = 0; if (!g_iface.entitylist || g_net.origin < 0 || g_net.modelindex < 0) return; resolve_server_netvars(); using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; int active_idx = local_active_weapon_idx(local_idx); static bool cats_logged = false; if (!cats_logged) { cats_logged = true; char maxbuf[16]; if (g_item_max_dist == 0) snprintf(maxbuf, sizeof(maxbuf), "All"); else snprintf(maxbuf, sizeof(maxbuf), "%d", g_item_max_dist); logf("items: categories health=%d ammo=%d armor=%d weapons=%d " "crates=%d distance=%d max=%s", (int)g_item_health, (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons, (int)g_item_crates, (int)g_item_distance, maxbuf); } static int dbg_lines = 0; static bool owner_warned = false; if (!owner_warned && g_net.owner < 0 && g_net.weapon_owner < 0) { owner_warned = true; logf("items: weapon owner netvar unavailable; held weapons may tag"); } for (int idx = 1; idx <= hi && g_nitems < 192; 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); const char *model = entity_model_name(ent); // CPhysicsProp's client m_nModelIndex is often 0 (e.g. item_item_crate); // fall back to the server entity's model index, which is set in Spawn(). if ((!model || !*model) && g_iface.engineserver) { void *sbase = nullptr; server_entity_health(idx, nullptr, nullptr, &sbase); if (sbase) model = model_name_from_index(server_model_index(sbase)); } char label[128]; int kind = classify_item(model, cls, label, sizeof(label)); if (kind < 0) continue; Vector origin{*(float *)((uintptr_t)ent + g_net.origin), *(float *)((uintptr_t)ent + g_net.origin + 4), *(float *)((uintptr_t)ent + g_net.origin + 8)}; float dx = origin.x - g_cam_eye.x; float dy = origin.y - g_cam_eye.y; float dz = origin.z - g_cam_eye.z; float dist = sqrtf(dx * dx + dy * dy + dz * dz); if (kind == IK_WEAPON) { if (idx == active_idx || item_weapon_is_owned(ent)) continue; } // Distance applies to every tag; 0 means unlimited. if (g_item_max_dist > 0 && dist > (float)g_item_max_dist) continue; ItemTarget &t = g_item_targets[g_nitems++]; t.idx = idx; t.kind = kind; t.origin = origin; snprintf(t.label, sizeof(t.label), "%s", label); if (dbg_lines < 8) { dbg_lines++; logd("item: idx=%d kind=%d cls=%s model=%s", idx, kind, cls ? cls : "?", model ? model : "?"); } } if (!g_items_logged) { g_items_logged = true; logf("items: tagging %d valuable entities", g_nitems); } } void esp_draw_items() { if (!g_surface || !g_iface.entitylist || g_net.origin < 0) return; if (!g_item_health && !g_item_ammo && !g_item_armor && !g_item_weapons && !g_item_crates) return; int local = engine_get_local_player(); if (local <= 0) return; if (!g_scr_w) { using Fn = void (*)(void *, int &, int &); vfunc(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h); if (!g_scr_w) return; } items_collect(local); for (int i = 0; i < g_nitems; i++) { const ItemTarget &t = g_item_targets[i]; if (!item_kind_enabled(t.kind)) continue; float sx, sy; if (!world_to_screen(t.origin, sx, sy)) continue; if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -20 || sy > (float)g_scr_h + 20) continue; int r = 230, g = 230, b = 230; switch (t.kind) { case IK_HEALTH: r = 90; g = 255; b = 130; break; case IK_AMMO: r = 255; g = 210; b = 80; break; case IK_ARMOR: r = 90; g = 180; b = 255; break; case IK_WEAPON: r = 120; g = 230; b = 255; break; case IK_CRATE: r = 220; g = 150; b = 255; break; default: break; } int x = (int)sx, y = (int)sy; int rad = (t.kind == IK_CRATE) ? 7 : 4; surf_color(r, g, b, 255); surf_rect(0, x - rad, y - rad, x + rad, y + rad); surf_line(x - rad - 4, y, x - rad - 1, y); surf_line(x + rad + 1, y, x + rad + 4, y); surf_line(x, y - rad - 4, x, y - rad - 1); surf_line(x, y + rad + 1, x, y + rad + 4); if (t.kind == IK_CRATE) surf_rect(1, x - 2, y - 2, x + 2, y + 2); float dx = t.origin.x - g_cam_eye.x; float dy = t.origin.y - g_cam_eye.y; float dz = t.origin.z - g_cam_eye.z; int dist = (int)sqrtf(dx * dx + dy * dy + dz * dz); char text[192]; if (g_item_distance) snprintf(text, sizeof(text), "%s %du", t.label, dist); else snprintf(text, sizeof(text), "%s", t.label); int tw = (int)strlen(text) * 9; int tx = x + 10; if (tx + tw > g_scr_w - 4) tx = x - 10 - tw; if (tx < 4) tx = 4; int ty = y - 7; if (ty < 0) ty = 0; if (ty + 16 > g_scr_h) ty = g_scr_h - 16; draw_text_col(tx, ty, text, r, g, b); } }