From 293eda2bdce9d732fad168b5b6838bd89ce59fe8 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 14:52:00 +0100 Subject: Melee killbot: 360-degree crowbar silent aim - New feature (default on): while the local player's active weapon is the crowbar, pick the nearest visible hostile NPC within the 75-unit crowbar range, sorted by distance to its collision AABB, and attack it through the normal silent-aim path (usercmd view + IN_ATTACK, movement preserved, rendered view restored at FRAME_RENDER_START). - Prefer the nearest visible hitbox center; fall back to the AABB center / closest point when bones are unavailable. Reuses the existing friendly / neutral and life-state filters and the world-only vischeck. - Detect the active weapon from the client m_hActiveWeapon handle, with the authoritative server m_hActiveWeapon handle as a fallback (client entity index from the low 16 bits). - While the crowbar is active and this feature is on, skip the gun aimbot, triggerbot and silent aim so they do not fight over the usercmd view. - Menu: Melee killbot -> Active; config [melee] active=... --- src/esp.cpp | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index 1e00a92..47df57d 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -784,6 +784,264 @@ bool silent_aim_apply(CUserCmd *cmd) { 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; +} + bool triggerbot_apply(CUserCmd *cmd) { if (!g_iface.enginetrace || !g_iface.entitylist) return false; -- cgit v1.2.3