diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-27 14:52:00 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-27 14:52:24 +0100 |
| commit | 293eda2bdce9d732fad168b5b6838bd89ce59fe8 (patch) | |
| tree | 9f345d4a19d1957ccfd7764012221d62eb2df328 /src/esp.cpp | |
| parent | Menu: latch the Insert key and expand the root on open (diff) | |
| download | hl2h-293eda2bdce9d732fad168b5b6838bd89ce59fe8.tar.gz hl2h-293eda2bdce9d732fad168b5b6838bd89ce59fe8.zip | |
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=...
Diffstat (limited to '')
| -rw-r--r-- | src/esp.cpp | 258 |
1 files changed, 258 insertions, 0 deletions
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) { | |||
| 784 | return true; | 784 | return true; |
| 785 | } | 785 | } |
| 786 | 786 | ||
| 787 | // --------------------------------------------------------------------------- | ||
| 788 | // melee killbot | ||
| 789 | // --------------------------------------------------------------------------- | ||
| 790 | // | ||
| 791 | // The crowbar's CBaseHLBludgeonWeapon::Swing traces 75 units from | ||
| 792 | // Weapon_ShootPosition along the view forward (CROWBAR_RANGE in | ||
| 793 | // weapon_crowbar.h). It first tries a line, then a +/-16 hull backed off by | ||
| 794 | // ~28 units, and ignores targets more than ~45 degrees off the aim direction. | ||
| 795 | // We therefore only need to point the usercmd at the target for one command; | ||
| 796 | // the rendered view is restored exactly like silent aim. | ||
| 797 | |||
| 798 | static constexpr float MELEE_RANGE = 75.0f; | ||
| 799 | |||
| 800 | static float vec_dist(const Vector &a, const Vector &b) { | ||
| 801 | float dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z; | ||
| 802 | return sqrtf(dx * dx + dy * dy + dz * dz); | ||
| 803 | } | ||
| 804 | |||
| 805 | bool melee_weapon_active(void *local_ent) { | ||
| 806 | if (!local_ent) | ||
| 807 | return false; | ||
| 808 | int local = engine_get_local_player(); | ||
| 809 | |||
| 810 | auto check = [&](int idx, const char **cls_out, | ||
| 811 | const char **model_out) -> bool { | ||
| 812 | if (idx <= 0 || idx > 4096) | ||
| 813 | return false; | ||
| 814 | void *wep = entitylist_get(idx); | ||
| 815 | void *net = entity_networkable(idx); | ||
| 816 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 817 | const char *model = wep ? entity_model_name(wep) : nullptr; | ||
| 818 | *cls_out = cls; | ||
| 819 | *model_out = model; | ||
| 820 | return (cls && strstr(cls, "Crowbar")) || | ||
| 821 | (model && strstr(model, "crowbar")); | ||
| 822 | }; | ||
| 823 | |||
| 824 | int idx = -1; | ||
| 825 | if (g_net.activeweapon >= 0) { | ||
| 826 | uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon); | ||
| 827 | if (h != 0xFFFFFFFFu) | ||
| 828 | idx = (int)(h & 0xFFFF); | ||
| 829 | } | ||
| 830 | const char *cls = nullptr, *model = nullptr; | ||
| 831 | bool crowbar = check(idx, &cls, &model); | ||
| 832 | |||
| 833 | if (!crowbar) { | ||
| 834 | // Fallback: the authoritative server player's m_hActiveWeapon handle. | ||
| 835 | // Also covers a client netvar offset that resolves but does not point at | ||
| 836 | // the weapon the server is actually using. | ||
| 837 | if (local > 0) { | ||
| 838 | resolve_server_netvars(); | ||
| 839 | int si = server_active_weapon(local); | ||
| 840 | if (si > 0 && si != idx) { | ||
| 841 | idx = si; | ||
| 842 | crowbar = check(idx, &cls, &model); | ||
| 843 | } | ||
| 844 | } | ||
| 845 | } | ||
| 846 | |||
| 847 | static int last_idx = -2; | ||
| 848 | static bool last_crowbar = false; | ||
| 849 | if (idx != last_idx || crowbar != last_crowbar) { | ||
| 850 | last_idx = idx; | ||
| 851 | last_crowbar = crowbar; | ||
| 852 | logf("melee: active idx=%d cls=%s model=%s crowbar=%d", idx, | ||
| 853 | cls ? cls : "?", model ? model : "?", (int)crowbar); | ||
| 854 | } | ||
| 855 | return crowbar; | ||
| 856 | } | ||
| 857 | |||
| 858 | bool melee_aimbot_apply(CUserCmd *cmd) { | ||
| 859 | if (!g_iface.entitylist || g_net.origin < 0) | ||
| 860 | return false; | ||
| 861 | int local = engine_get_local_player(); | ||
| 862 | if (local <= 0) | ||
| 863 | return false; | ||
| 864 | void *local_ent = entitylist_get(local); | ||
| 865 | if (!local_ent) | ||
| 866 | return false; | ||
| 867 | if (g_net.health >= 0 && | ||
| 868 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) | ||
| 869 | return false; | ||
| 870 | |||
| 871 | using Fn = int (*)(void *); | ||
| 872 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | ||
| 873 | if (hi < 0 || hi > 4096) | ||
| 874 | hi = 4096; | ||
| 875 | |||
| 876 | struct Cand { | ||
| 877 | int idx; | ||
| 878 | float dist; | ||
| 879 | Vector closest; | ||
| 880 | Vector center; | ||
| 881 | }; | ||
| 882 | Cand cands[16]; | ||
| 883 | int n = 0; | ||
| 884 | |||
| 885 | for (int idx = 1; idx <= hi; idx++) { | ||
| 886 | if (idx == local) | ||
| 887 | continue; | ||
| 888 | void *ent = entitylist_get(idx); | ||
| 889 | if (!ent) | ||
| 890 | continue; | ||
| 891 | void *net = entity_networkable(idx); | ||
| 892 | if (!net) | ||
| 893 | continue; | ||
| 894 | const char *cls = entity_class_name(net, nullptr); | ||
| 895 | if (!cls || !is_npc_class(cls)) | ||
| 896 | continue; | ||
| 897 | uintptr_t e = (uintptr_t)ent; | ||
| 898 | int life = g_net.lifestate >= 0 | ||
| 899 | ? (int)*(unsigned char *)(e + g_net.lifestate) | ||
| 900 | : -1; | ||
| 901 | if (life != LIFE_ALIVE) | ||
| 902 | continue; | ||
| 903 | const char *model = entity_model_name(ent); | ||
| 904 | if (model_is_friendly(model) || model_is_neutral(model)) | ||
| 905 | continue; | ||
| 906 | |||
| 907 | Vector origin{*(float *)(e + g_net.origin), | ||
| 908 | *(float *)(e + g_net.origin + 4), | ||
| 909 | *(float *)(e + g_net.origin + 8)}; | ||
| 910 | float minx = -16.0f, miny = -16.0f, minz = 0.0f; | ||
| 911 | float maxx = 16.0f, maxy = 16.0f, maxz = 72.0f; | ||
| 912 | if (g_net.mins >= 0) { | ||
| 913 | minx = *(float *)(e + g_net.mins); | ||
| 914 | miny = *(float *)(e + g_net.mins + 4); | ||
| 915 | minz = *(float *)(e + g_net.mins + 8); | ||
| 916 | } | ||
| 917 | if (g_net.maxs >= 0) { | ||
| 918 | maxx = *(float *)(e + g_net.maxs); | ||
| 919 | maxy = *(float *)(e + g_net.maxs + 4); | ||
| 920 | maxz = *(float *)(e + g_net.maxs + 8); | ||
| 921 | } | ||
| 922 | if (!(maxz > minz + 4.0f && maxz < 512.0f)) { | ||
| 923 | minz = 0.0f; | ||
| 924 | maxz = 72.0f; | ||
| 925 | } | ||
| 926 | if (!(maxx > minx + 1.0f && maxx < 256.0f)) { | ||
| 927 | minx = -16.0f; | ||
| 928 | maxx = 16.0f; | ||
| 929 | } | ||
| 930 | if (!(maxy > miny + 1.0f && maxy < 256.0f)) { | ||
| 931 | miny = -16.0f; | ||
| 932 | maxy = 16.0f; | ||
| 933 | } | ||
| 934 | |||
| 935 | Vector lo{origin.x + minx, origin.y + miny, origin.z + minz}; | ||
| 936 | Vector hiV{origin.x + maxx, origin.y + maxy, origin.z + maxz}; | ||
| 937 | Vector closest{g_cam_eye.x, g_cam_eye.y, g_cam_eye.z}; | ||
| 938 | if (closest.x < lo.x) closest.x = lo.x; | ||
| 939 | if (closest.x > hiV.x) closest.x = hiV.x; | ||
| 940 | if (closest.y < lo.y) closest.y = lo.y; | ||
| 941 | if (closest.y > hiV.y) closest.y = hiV.y; | ||
| 942 | if (closest.z < lo.z) closest.z = lo.z; | ||
| 943 | if (closest.z > hiV.z) closest.z = hiV.z; | ||
| 944 | float d = vec_dist(g_cam_eye, closest); | ||
| 945 | if (d > MELEE_RANGE) | ||
| 946 | continue; | ||
| 947 | |||
| 948 | Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f, | ||
| 949 | (lo.z + hiV.z) * 0.5f}; | ||
| 950 | |||
| 951 | int pos = n; | ||
| 952 | while (pos > 0 && cands[pos - 1].dist > d) | ||
| 953 | pos--; | ||
| 954 | if (n < 16) | ||
| 955 | n++; | ||
| 956 | else if (pos >= 16) | ||
| 957 | continue; | ||
| 958 | for (int j = n - 1; j > pos; j--) | ||
| 959 | cands[j] = cands[j - 1]; | ||
| 960 | cands[pos] = {idx, d, closest, center}; | ||
| 961 | } | ||
| 962 | |||
| 963 | for (int ci = 0; ci < n; ci++) { | ||
| 964 | const Cand &c = cands[ci]; | ||
| 965 | Vector aim{}; | ||
| 966 | bool have_aim = false; | ||
| 967 | |||
| 968 | // Prefer the nearest visible hitbox center. This handles small NPCs | ||
| 969 | // (headcrabs, antlions) whose AABB center can be less useful than the | ||
| 970 | // actual body hitboxes. | ||
| 971 | void *tent = entitylist_get(c.idx); | ||
| 972 | if (tent && bones_update(c.idx, tent)) { | ||
| 973 | Vector pts[32]; | ||
| 974 | int np = aim_points(c.idx, 0xFE, pts, 32); | ||
| 975 | float bestd = 1e9f; | ||
| 976 | for (int i = 0; i < np; i++) { | ||
| 977 | float pd = vec_dist(g_cam_eye, pts[i]); | ||
| 978 | if (pd > MELEE_RANGE || pd >= bestd) | ||
| 979 | continue; | ||
| 980 | if (!is_visible(g_cam_eye, pts[i])) | ||
| 981 | continue; | ||
| 982 | bestd = pd; | ||
| 983 | aim = pts[i]; | ||
| 984 | have_aim = true; | ||
| 985 | } | ||
| 986 | } | ||
| 987 | |||
| 988 | if (!have_aim) { | ||
| 989 | aim = c.center; | ||
| 990 | float ad = vec_dist(g_cam_eye, aim); | ||
| 991 | if (ad > MELEE_RANGE) { | ||
| 992 | // The body is in range but its center is not: aim just inside the | ||
| 993 | // closest point on the AABB. | ||
| 994 | aim = c.closest; | ||
| 995 | aim.x += (c.center.x - c.closest.x) * 0.25f; | ||
| 996 | aim.y += (c.center.y - c.closest.y) * 0.25f; | ||
| 997 | aim.z += (c.center.z - c.closest.z) * 0.25f; | ||
| 998 | ad = vec_dist(g_cam_eye, aim); | ||
| 999 | if (ad > MELEE_RANGE) | ||
| 1000 | aim = c.closest; | ||
| 1001 | } | ||
| 1002 | if (!is_visible(g_cam_eye, aim)) | ||
| 1003 | continue; | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; | ||
| 1007 | QAngle desired = angle_to(g_cam_eye, aim); | ||
| 1008 | float np = desired.x; | ||
| 1009 | if (np > 89.0f) np = 89.0f; | ||
| 1010 | if (np < -89.0f) np = -89.0f; | ||
| 1011 | float ny = desired.y; | ||
| 1012 | while (ny > 180.0f) ny -= 360.0f; | ||
| 1013 | while (ny < -180.0f) ny += 360.0f; | ||
| 1014 | |||
| 1015 | preserve_movement(cmd, orig.y, ny); | ||
| 1016 | cmd->viewangles[0] = np; | ||
| 1017 | cmd->viewangles[1] = ny; | ||
| 1018 | cmd->viewangles[2] = orig.z; | ||
| 1019 | set_silent_restore(orig); | ||
| 1020 | if (g_iface.engine) { | ||
| 1021 | QAngle restore{orig.x, orig.y, orig.z}; | ||
| 1022 | using SetViewFn = void (*)(void *, QAngle *); | ||
| 1023 | vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore); | ||
| 1024 | } | ||
| 1025 | cmd->buttons |= IN_ATTACK; | ||
| 1026 | |||
| 1027 | static int logs = 0; | ||
| 1028 | if (logs < 10) { | ||
| 1029 | logs++; | ||
| 1030 | logf("melee: swing idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", c.idx, | ||
| 1031 | c.dist, aim.x, aim.y, aim.z); | ||
| 1032 | } | ||
| 1033 | return true; | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | static double last_none = 0.0; | ||
| 1037 | double now = esp_now(); | ||
| 1038 | if (now - last_none >= 2.0) { | ||
| 1039 | last_none = now; | ||
| 1040 | logf("melee: no visible target within %.0f", MELEE_RANGE); | ||
| 1041 | } | ||
| 1042 | return false; | ||
| 1043 | } | ||
| 1044 | |||
| 787 | bool triggerbot_apply(CUserCmd *cmd) { | 1045 | bool triggerbot_apply(CUserCmd *cmd) { |
| 788 | if (!g_iface.enginetrace || !g_iface.entitylist) | 1046 | if (!g_iface.enginetrace || !g_iface.entitylist) |
| 789 | return false; | 1047 | return false; |
