// combat.cpp — aimbot, silent aim, melee, spread control, triggerbot. #include "esp_internal.h" #include "hl2h.h" #include #include #include 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; // maxs is optional: the per-target code falls back to 72 units when the // netvar is missing, so it must not disable the whole aimbot here. 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; // With the RPG equipped, armoured units (striders, gunships, dropships, // hunter-choppers) are the ones ordinary shots cannot hurt, so prefer them // over normal NPCs when acquiring. bool rpg = active_weapon_contains(local_ent, "rocket_launcher") || active_weapon_contains(local_ent, "rpg"); // fov_limit <= 0 means "no angular limit" (kept for unlimited acquisition). auto try_target = [&](int idx, float fov_limit, 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 || entity_is_dormant(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 (!npc_can_be_damaged(idx, 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 (fov_limit > 0.0f && f > fov_limit) 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. // The lock uses a wider cone than acquisition (hysteresis), so a target that // drifts slightly off the crosshair is not dropped, but one that swings // behind the player is: an unbounded lock let silent aim fire backwards. if (lock_idx && *lock_idx >= 0) { Vector pt; float f; if (try_target(*lock_idx, max_fov * 1.5f, 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; bool armored; float key; }; 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 || entity_is_dormant(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; if (!npc_can_be_damaged(idx, model)) continue; bool armored = npc_damage_class(idx, model) == NPC_DMG_ARMORED; 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)); // The prefilter samples a single point, but the selected hitbox can be a // whole body-width away at close range. Reject only when the target's // angular radius cannot reach the cone; try_target still enforces the // exact per-hitbox FOV. float ddx = approx.x - g_cam_eye.x; float ddy = approx.y - g_cam_eye.y; float ddz = approx.z - g_cam_eye.z; float dist = sqrtf(ddx * ddx + ddy * ddy + ddz * ddz); float margin = dist > 1.0f ? atan2f(maxz, dist) * 180.0f / PI_F : 90.0f; if (fov - margin > max_fov) continue; float key = (rpg && armored) ? fov - 1000.0f : fov; int pos = ncands; while (pos > 0 && cands[pos - 1].key > key) 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, armored, key}; } 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, max_fov, 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; } 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 && strcasestr(cls, needle)) || (model && strcasestr(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) return false; // NPCs do not network C_BaseEntity::m_vecVelocity (only the player does), so // the client field is stale for exactly the targets the aimbot uses. Read // the listen-server entity when available and fall back to the client value. float v[3] = {0.0f, 0.0f, 0.0f}; Vector srv_vel; bool have_vel = false; if (server_entity_velocity(target_idx, &srv_vel)) { v[0] = srv_vel.x; v[1] = srv_vel.y; v[2] = srv_vel.z; have_vel = true; } else if (g_net.velocity >= 0) { const float *cv = (const float *)((uintptr_t)target + g_net.velocity); v[0] = cv[0]; v[1] = cv[1]; v[2] = cv[2]; have_vel = true; } if (!have_vel) return false; 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; } // A led point is a prediction, not a verified aim point: acquisition checked // the un-led position, so the shift has to pass the same FOV and visibility // tests. On failure the caller keeps the original point instead of firing into // geometry or outside the configured cone. static bool lead_point_ok(int target_idx, const Vector &lead, const QAngle &view, float max_fov) { if (getfov(view, angle_to(g_cam_eye, lead)) > max_fov) return false; if (!aimbot_vischeck_enabled()) return true; void *local_ent = entitylist_get(engine_get_local_player()); void *target_ent = entitylist_get(target_idx); if (!local_ent || !target_ent) return false; return is_visible(g_cam_eye, lead, local_ent, target_ent); } // Semi-auto weapons (pistol, .357, crossbow, shotgun, RPG, gravity gun) only // fire on the IN_ATTACK press edge, so a synthetic button held every tick fires // exactly once per buy/equip. Alternate press/release for those; automatic // weapons keep the button held. static bool weapon_is_semi_auto(void *local_ent) { static const char *names[] = {"pistol", "357", "crossbow", "shotgun", "rocket_launcher", "physcannon", "crowbar", nullptr}; for (int i = 0; names[i]; i++) if (active_weapon_contains(local_ent, names[i])) return true; return false; } static bool fire_edge_ok(CUserCmd *cmd) { void *local_ent = entitylist_get(engine_get_local_player()); if (!local_ent || !weapon_is_semi_auto(local_ent)) return true; static int last_tick = -1; static bool phase = false; if (cmd->tick_count != last_tick) { last_tick = cmd->tick_count; phase = !phase; } return phase; } 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}; float punch_x = 0.0f, punch_y = 0.0f; bool punch = punch_angles(punch_x, punch_y); if (punch) { view.x += punch_x; view.y += punch_y; } int target = -1; Vector point{}; float fov = 0.0f; float max_fov = (float)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) && lead_point_ok(target, lead, view, max_fov)) point = lead; QAngle desired = angle_to(g_cam_eye, point); if (punch) { desired.x -= punch_x; desired.y -= punch_y; } static int want_logs = 0; if (want_logs < 10) { want_logs++; 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}; float punch_x = 0.0f, punch_y = 0.0f; bool punch = punch_angles(punch_x, punch_y); if (punch) { view.x += punch_x; view.y += punch_y; } int target = -1; Vector point{}; float fov = 0.0f; float max_fov = (float)aimbot_fov(); if (max_fov < 1.0f) max_fov = 1.0f; if (max_fov > 180.0f) max_fov = 180.0f; int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr; if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov, "silentaim", lock, target, point, fov)) return false; Vector lead; if (projectile_lead_point(target, point, lead) && lead_point_ok(target, lead, view, max_fov)) point = lead; QAngle desired = angle_to(g_cam_eye, point); if (punch) { desired.x -= punch_x; desired.y -= punch_y; } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; 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); } if (fire_edge_ok(cmd)) 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 gravity_gun_active(void *local_ent) { if (!local_ent) return false; if (active_weapon_contains(local_ent, "physcannon")) return true; // Same server fallback as the crowbar: map transitions can leave the // client activeweapon handle stale. int local = engine_get_local_player(); if (local <= 0 || !g_iface.engineserver) return false; resolve_server_netvars(); int si = server_active_weapon(local); if (si <= 0) return false; void *wep = entitylist_get(si); void *net = entity_networkable(si); const char *cls = net ? entity_class_name(net, nullptr) : nullptr; const char *model = wep ? entity_model_name(wep) : nullptr; if (!model) { void *sbase = nullptr; server_entity_health(si, nullptr, nullptr, &sbase); if (sbase) model = model_name_from_index(server_model_index(sbase)); } return (cls && strcasestr(cls, "physcannon")) || (model && strcasestr(model, "physcannon")); } // The citadel supercharges the gravity gun (d3_citadel_03 onward). It can then // punt any NPC, so the melee aimbot should not restrict itself to // physics-vulnerable units. static bool supercharged_gravity_gun() { if (!g_iface.engine) return false; using Fn = const char *(*)(void *); const char *m = vfunc(g_iface.engine, 51)(g_iface.engine); if (!m) return false; bool supe = strstr(m, "d3_citadel_03") || strstr(m, "d3_citadel_04") || strstr(m, "d3_breen_01"); static const char *logged_map = nullptr; if (logged_map != m) { logged_map = m; logf("melee: gravity gun map=%s supercharged=%d", m, (int)supe); } return supe; } bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { 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; // The punted object can travel much farther than the crowbar. float max_range = gravity ? 1200.0f : MELEE_RANGE; bool supercharged = gravity && supercharged_gravity_gun(); 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 || entity_is_dormant(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; if (!npc_can_be_damaged(idx, model)) continue; // The normal gravity gun is only the tool for physics-vulnerable units; // the citadel version behaves like a normal weapon. if (gravity && !supercharged && npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) 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); } // Reject absurd boxes in both directions; a garbage mins value that is // hugely negative would otherwise produce an AABB spanning the map. if (!(minz > -512.0f && maxz > minz + 4.0f && maxz < 512.0f)) { minz = 0.0f; maxz = 72.0f; } if (!(minx > -512.0f && maxx > minx + 1.0f && maxx < 256.0f)) { minx = -16.0f; maxx = 16.0f; } if (!(miny > -512.0f && 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 > max_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 > max_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 > max_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 > max_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); // The crowbar uses GetAutoaimVector (adds punch); the gravity gun's // launch uses EyeVectors (no punch), so only compensate for melee. float punch_x = 0.0f, punch_y = 0.0f; if (!gravity && punch_angles(punch_x, punch_y)) { desired.x -= punch_x; desired.y -= punch_y; } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; 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); } // Gravity gun primary attack launches the held object (EyeVectors); // secondary would drop it instead. if (fire_edge_ok(cmd)) cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 10) { logs++; logd("melee: %s idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", gravity ? "punt" : "swing", 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", max_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 = mod_engine().path.find_last_of('/'); if (slash != std::string::npos) { std::string path = mod_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}; float punch_x = 0.0f, punch_y = 0.0f; if (punch_angles(punch_x, punch_y)) { view.x += punch_x; view.y += punch_y; } Vector fwd = angle_forward_vector(view); Vector start = g_cam_eye; Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, start.z + fwd.z * 8192.0f}; void *hit = trace_entity(start, end, local_ent); if (!hit) return false; int idx = entptr_lookup(hit); if (idx <= 0) { // Cache miss: ESP is off, or this entity appeared since the last scan. using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); if (hi < 0 || hi > 4096) hi = 4096; for (int i = 1; i <= hi; i++) { if (entitylist_get(i) != hit) continue; idx = i; entptr_store(hit, i); break; } if (idx <= 0) return false; } void *net = entity_networkable(idx); if (entity_is_dormant(net)) return false; 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; if (!npc_can_be_damaged(idx, model)) return false; if (fire_edge_ok(cmd)) cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 5) { logs++; logd("triggerbot: fire idx=%d model=%s", idx, model ? model : "?"); } return true; }