diff options
| -rw-r--r-- | CONTEXT.md | 11 | ||||
| -rw-r--r-- | src/esp.cpp | 6 | ||||
| -rw-r--r-- | src/gui.cpp | 106 | ||||
| -rw-r--r-- | src/hl2h.h | 3 | ||||
| -rw-r--r-- | src/vischeck.cpp | 113 |
5 files changed, 184 insertions, 55 deletions
| @@ -288,10 +288,17 @@ vocabulary / decisions. | |||
| 288 | mask. This caused active-weapon lookups to produce bogus indices after a | 288 | mask. This caused active-weapon lookups to produce bogus indices after a |
| 289 | map change once the weapon serial was nonzero (melee killbot looked dead). | 289 | map change once the weapon serial was nonzero (melee killbot looked dead). |
| 290 | - `ISurface` binary slot = SDK header slot + 4; see `src/ISURFACE_STATUS.md`. | 290 | - `ISurface` binary slot = SDK header slot + 4; see `src/ISURFACE_STATUS.md`. |
| 291 | - GUI font handles are validated with `GetTextSize` and recreated when the | ||
| 292 | local player entity or UI scale changes; if the scaled size fails, 16 px is | ||
| 293 | used as a fallback. VGUI can silently invalidate font handles across level | ||
| 294 | transitions. | ||
| 291 | - Silent aim and ABH temporarily replace the usercmd view; silent aim preserves | 295 | - Silent aim and ABH temporarily replace the usercmd view; silent aim preserves |
| 292 | movement, ABH intentionally zeroes it for the jump tick. | 296 | movement, ABH intentionally zeroes it for the jump tick. |
| 293 | - Aimbot vischeck (`MASK_VISIBLE`/world-only) differs from triggerbot | 297 | - Aimbot/silent/melee vischeck now traces everything bullets care about |
| 294 | (`MASK_SHOT`/all entities): glass and static props can behave differently. | 298 | (`MASK_SHOT`) while ignoring the local player and target. Static props, |
| 299 | crates and other NPCs block. Non-solid glass and breakable-glass materials | ||
| 300 | are skipped; solid non-break glass (bulletproof) blocks. Triggerbot still | ||
| 301 | uses the same `MASK_SHOT` trace and will not fire through glass. | ||
| 295 | 302 | ||
| 296 | ## Valuable items | 303 | ## Valuable items |
| 297 | 304 | ||
diff --git a/src/esp.cpp b/src/esp.cpp index 3db25e6..5683704 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -541,7 +541,7 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, | |||
| 541 | continue; | 541 | continue; |
| 542 | if (f >= best) | 542 | if (f >= best) |
| 543 | continue; | 543 | continue; |
| 544 | if (vischeck && !is_visible(g_cam_eye, pts[i])) | 544 | if (vischeck && !is_visible(g_cam_eye, pts[i], local_ent, ent)) |
| 545 | continue; | 545 | continue; |
| 546 | best = f; | 546 | best = f; |
| 547 | best_pt = pts[i]; | 547 | best_pt = pts[i]; |
| @@ -1061,7 +1061,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1061 | float pd = vec_dist(g_cam_eye, pts[i]); | 1061 | float pd = vec_dist(g_cam_eye, pts[i]); |
| 1062 | if (pd > MELEE_RANGE || pd >= bestd) | 1062 | if (pd > MELEE_RANGE || pd >= bestd) |
| 1063 | continue; | 1063 | continue; |
| 1064 | if (!is_visible(g_cam_eye, pts[i])) | 1064 | if (!is_visible(g_cam_eye, pts[i], local_ent, tent)) |
| 1065 | continue; | 1065 | continue; |
| 1066 | bestd = pd; | 1066 | bestd = pd; |
| 1067 | aim = pts[i]; | 1067 | aim = pts[i]; |
| @@ -1083,7 +1083,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1083 | if (ad > MELEE_RANGE) | 1083 | if (ad > MELEE_RANGE) |
| 1084 | aim = c.closest; | 1084 | aim = c.closest; |
| 1085 | } | 1085 | } |
| 1086 | if (!is_visible(g_cam_eye, aim)) | 1086 | if (!is_visible(g_cam_eye, aim, local_ent, tent)) |
| 1087 | continue; | 1087 | continue; |
| 1088 | } | 1088 | } |
| 1089 | 1089 | ||
diff --git a/src/gui.cpp b/src/gui.cpp index b512ef3..417860f 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -39,6 +39,8 @@ static unsigned long g_font_title = 0; | |||
| 39 | static int g_body_h = 15; | 39 | static int g_body_h = 15; |
| 40 | static int g_title_h = 20; | 40 | static int g_title_h = 20; |
| 41 | static float g_font_scale = 0.0f; | 41 | static float g_font_scale = 0.0f; |
| 42 | static void *g_font_local = (void *)-1; | ||
| 43 | static bool g_font_fail_logged = false; | ||
| 42 | static int g_scr_w = 0, g_scr_h = 0; | 44 | static int g_scr_w = 0, g_scr_h = 0; |
| 43 | 45 | ||
| 44 | static void s_color(int r, int g, int b, int a) { | 46 | static void s_color(int r, int g, int b, int a) { |
| @@ -485,41 +487,97 @@ bool gui_toasts_active() { | |||
| 485 | return active; | 487 | return active; |
| 486 | } | 488 | } |
| 487 | 489 | ||
| 488 | static void ensure_fonts() { | 490 | static bool font_valid(unsigned long f) { |
| 489 | if (g_font_body && fabsf(g_font_scale - g_scale) < 0.001f) | 491 | if (!f || !g_surface) |
| 490 | return; | 492 | return false; |
| 491 | g_font_scale = g_scale; | 493 | int w = 0, h = 0; |
| 492 | g_body_h = (int)(15.0f * g_scale + 0.5f); | 494 | s_text_size(f, L"M", w, h); |
| 493 | g_title_h = (int)(20.0f * g_scale + 0.5f); | 495 | return w > 0 && h > 0; |
| 494 | if (g_body_h < 11) g_body_h = 11; | 496 | } |
| 495 | if (g_title_h < 15) g_title_h = 15; | 497 | |
| 498 | static void gui_reset_fonts() { | ||
| 499 | g_font_body = 0; | ||
| 500 | g_font_title = 0; | ||
| 501 | g_font_scale = 0.0f; | ||
| 502 | } | ||
| 503 | |||
| 504 | static unsigned long create_gui_font(int tall) { | ||
| 496 | static const char *families[] = {"Liberation Sans", "DejaVu Sans", | 505 | static const char *families[] = {"Liberation Sans", "DejaVu Sans", |
| 497 | "Noto Sans", "Tahoma", "Verdana", "Arial", | 506 | "Noto Sans", "Tahoma", "Verdana", "Arial", |
| 498 | nullptr}; | 507 | nullptr}; |
| 508 | for (int i = 0; families[i]; i++) { | ||
| 509 | unsigned long f = s_create_font(); | ||
| 510 | if (!f) | ||
| 511 | break; | ||
| 512 | if (s_set_font(f, families[i], tall, 400, 0x400, 0x20, 0xFF) && | ||
| 513 | font_valid(f)) | ||
| 514 | return f; | ||
| 515 | } | ||
| 516 | return 0; | ||
| 517 | } | ||
| 518 | |||
| 519 | static void ensure_fonts() { | ||
| 520 | void *le = nullptr; | ||
| 521 | if (g_iface.engine && g_iface.entitylist) { | ||
| 522 | int local = engine_get_local_player(); | ||
| 523 | if (local > 0) | ||
| 524 | le = entitylist_get(local); | ||
| 525 | } | ||
| 526 | if (le != g_font_local) { | ||
| 527 | g_font_local = le; | ||
| 528 | gui_reset_fonts(); | ||
| 529 | } | ||
| 530 | |||
| 531 | bool scale_changed = fabsf(g_font_scale - g_scale) >= 0.001f; | ||
| 532 | if (g_font_body && g_font_title && !scale_changed && | ||
| 533 | font_valid(g_font_body) && font_valid(g_font_title)) | ||
| 534 | return; | ||
| 535 | // Level transitions can silently invalidate VGUI font handles. Recreate | ||
| 536 | // them instead of drawing invisible text. | ||
| 537 | if ((g_font_body && !font_valid(g_font_body)) || | ||
| 538 | (g_font_title && !font_valid(g_font_title))) | ||
| 539 | gui_reset_fonts(); | ||
| 540 | if (scale_changed) | ||
| 541 | gui_reset_fonts(); | ||
| 542 | |||
| 543 | g_font_scale = g_scale; | ||
| 544 | int req_body = (int)(15.0f * g_scale + 0.5f); | ||
| 545 | int req_title = (int)(20.0f * g_scale + 0.5f); | ||
| 546 | if (req_body < 11) req_body = 11; | ||
| 547 | if (req_title < 15) req_title = 15; | ||
| 548 | g_body_h = req_body; | ||
| 549 | g_title_h = req_title; | ||
| 550 | |||
| 551 | if (!g_font_body) | ||
| 552 | g_font_body = create_gui_font(g_body_h); | ||
| 553 | if (!g_font_title) | ||
| 554 | g_font_title = create_gui_font(g_title_h); | ||
| 555 | |||
| 556 | // Small scaled sizes can fail in the font manager; 16 px is the proven ESP | ||
| 557 | // size, so use it as a readable fallback. | ||
| 499 | if (!g_font_body) { | 558 | if (!g_font_body) { |
| 500 | for (int i = 0; families[i] && !g_font_body; i++) { | 559 | g_body_h = 16; |
| 501 | unsigned long f = s_create_font(); | 560 | g_font_body = create_gui_font(g_body_h); |
| 502 | if (!f) | ||
| 503 | break; | ||
| 504 | if (s_set_font(f, families[i], g_body_h, 400, 0x400, 0x20, 0xFF)) | ||
| 505 | g_font_body = f; | ||
| 506 | } | ||
| 507 | } | 561 | } |
| 508 | if (!g_font_title) { | 562 | if (!g_font_title) { |
| 509 | for (int i = 0; families[i] && !g_font_title; i++) { | 563 | g_title_h = 16; |
| 510 | unsigned long f = s_create_font(); | 564 | g_font_title = create_gui_font(g_title_h); |
| 511 | if (!f) | ||
| 512 | break; | ||
| 513 | if (s_set_font(f, families[i], g_title_h, 400, 0x400, 0x20, 0xFF)) | ||
| 514 | g_font_title = f; | ||
| 515 | } | ||
| 516 | } | 565 | } |
| 517 | if (!g_font_title) | 566 | if (!g_font_title) |
| 518 | g_font_title = g_font_body; | 567 | g_font_title = g_font_body; |
| 568 | |||
| 519 | if (!g_font_body) { | 569 | if (!g_font_body) { |
| 520 | logf("gui: no usable body font"); | 570 | if (!g_font_fail_logged) { |
| 521 | toastf(TOAST_ERROR, "No usable GUI font"); | 571 | g_font_fail_logged = true; |
| 572 | logf("gui: no usable body font"); | ||
| 573 | toastf(TOAST_ERROR, "No usable GUI font"); | ||
| 574 | } | ||
| 575 | return; | ||
| 522 | } | 576 | } |
| 577 | g_font_fail_logged = false; | ||
| 578 | if (g_body_h != req_body || g_title_h != req_title) | ||
| 579 | logd("gui: font fallback body=%d title=%d (requested %d/%d)", g_body_h, | ||
| 580 | g_title_h, req_body, req_title); | ||
| 523 | } | 581 | } |
| 524 | 582 | ||
| 525 | static int text_w(const char *s, int tall) { | 583 | static int text_w(const char *s, int tall) { |
| @@ -272,7 +272,8 @@ void esp_draw(); | |||
| 272 | void esp_draw_items(); | 272 | void esp_draw_items(); |
| 273 | bool world_to_screen(const Vector &w, float &sx, float &sy); | 273 | bool world_to_screen(const Vector &w, float &sx, float &sy); |
| 274 | void set_silent_restore(const QAngle &angles); | 274 | void set_silent_restore(const QAngle &angles); |
| 275 | bool is_visible(const Vector &eye, const Vector &point); | 275 | bool is_visible(const Vector &eye, const Vector &point, |
| 276 | void *self = nullptr, void *target = nullptr); | ||
| 276 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); | 277 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); |
| 277 | float ground_distance(const Vector &origin, void *ignore, float max_dist); | 278 | float ground_distance(const Vector &origin, void *ignore, float max_dist); |
| 278 | 279 | ||
diff --git a/src/vischeck.cpp b/src/vischeck.cpp index dcb5c3c..a568f31 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp | |||
| @@ -1,14 +1,18 @@ | |||
| 1 | // vischeck.cpp — world-only line-of-sight test via EngineTraceClient003. | 1 | // vischeck.cpp — line-of-sight test via EngineTraceClient003. |
| 2 | // | 2 | // |
| 3 | // IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the | 3 | // IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the |
| 4 | // aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows | 4 | // aim point with TRACE_EVERYTHING/MASK_SHOT, ignoring the local player and the |
| 5 | // (you can see through them) while still hitting opaque world geometry. | 5 | // target entity. This catches world geometry, static props and temporary |
| 6 | // Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the | 6 | // objects such as crates/NPCs. Glass is skipped only when it is non-solid or |
| 7 | // deliberate trade-off for this build. | 7 | // its material name marks it breakable; solid non-break glass (bulletproof) |
| 8 | // blocks the check. | ||
| 8 | 9 | ||
| 9 | #include "hl2h.h" | 10 | #include "hl2h.h" |
| 11 | #include <cmath> | ||
| 10 | #include <cstddef> | 12 | #include <cstddef> |
| 11 | #include <cstdint> | 13 | #include <cstdint> |
| 14 | #include <cstring> | ||
| 15 | #include <strings.h> | ||
| 12 | 16 | ||
| 13 | namespace { | 17 | namespace { |
| 14 | 18 | ||
| @@ -75,11 +79,6 @@ struct ITraceFilterCompat { | |||
| 75 | virtual int GetTraceType() const = 0; | 79 | virtual int GetTraceType() const = 0; |
| 76 | }; | 80 | }; |
| 77 | 81 | ||
| 78 | struct WorldOnlyFilter : ITraceFilterCompat { | ||
| 79 | bool ShouldHitEntity(void *, int) override { return false; } | ||
| 80 | int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY | ||
| 81 | }; | ||
| 82 | |||
| 83 | struct EntityFilter : ITraceFilterCompat { | 82 | struct EntityFilter : ITraceFilterCompat { |
| 84 | void *ignore; | 83 | void *ignore; |
| 85 | explicit EntityFilter(void *ig) : ignore(ig) {} | 84 | explicit EntityFilter(void *ig) : ignore(ig) {} |
| @@ -89,15 +88,24 @@ struct EntityFilter : ITraceFilterCompat { | |||
| 89 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | 88 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING |
| 90 | }; | 89 | }; |
| 91 | 90 | ||
| 92 | // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | | 91 | struct VisibilityFilter : ITraceFilterCompat { |
| 93 | // CONTENTS_IGNORE_NODRAW_OPAQUE | 92 | void *self; |
| 94 | constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; | 93 | void *target; |
| 94 | VisibilityFilter(void *s, void *t) : self(s), target(t) {} | ||
| 95 | bool ShouldHitEntity(void *pEntity, int) override { | ||
| 96 | return pEntity != self && pEntity != target; | ||
| 97 | } | ||
| 98 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | ||
| 99 | }; | ||
| 100 | |||
| 101 | constexpr unsigned CONTENTS_SOLID = 0x1u; | ||
| 102 | constexpr unsigned CONTENTS_WINDOW = 0x2u; | ||
| 95 | // MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX | 103 | // MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX |
| 96 | constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u | | 104 | constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x2u | |
| 97 | 0x4000000u | 0x40000000u; | 105 | 0x4000000u | 0x40000000u; |
| 98 | // MASK_PLAYERSOLID = SOLID|MOVEABLE|PLAYERCLIP|WINDOW|MONSTER|GRATE | 106 | // MASK_PLAYERSOLID = SOLID|MOVEABLE|PLAYERCLIP|WINDOW|MONSTER|GRATE |
| 99 | constexpr unsigned MASK_PLAYERSOLID = | 107 | constexpr unsigned MASK_PLAYERSOLID = |
| 100 | 0x1u | 0x4000u | 0x10000u | 0x4u | 0x2000000u | 0x8u; | 108 | 0x1u | 0x4000u | 0x10000u | 0x2u | 0x2000000u | 0x8u; |
| 101 | 109 | ||
| 102 | static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { | 110 | static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { |
| 103 | float dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z; | 111 | float dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z; |
| @@ -127,7 +135,26 @@ static void make_standing_hull_ray(RayCompat &ray, const Vector &origin, | |||
| 127 | 135 | ||
| 128 | } // namespace | 136 | } // namespace |
| 129 | 137 | ||
| 130 | bool is_visible(const Vector &eye, const Vector &point) { | 138 | static bool surface_contains(const SurfaceCompat &surf, const char *needle) { |
| 139 | if (!surf.name || !needle) | ||
| 140 | return false; | ||
| 141 | size_t n = strlen(needle); | ||
| 142 | for (const char *p = surf.name; *p; p++) | ||
| 143 | if (strncasecmp(p, needle, n) == 0) | ||
| 144 | return true; | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | |||
| 148 | static bool surface_is_breakable_glass(const SurfaceCompat &surf) { | ||
| 149 | return surface_contains(surf, "break"); | ||
| 150 | } | ||
| 151 | |||
| 152 | static bool surface_is_glass(const SurfaceCompat &surf) { | ||
| 153 | return surface_contains(surf, "glass") || surface_contains(surf, "window"); | ||
| 154 | } | ||
| 155 | |||
| 156 | bool is_visible(const Vector &eye, const Vector &point, void *self, | ||
| 157 | void *target) { | ||
| 131 | static bool warned = false; | 158 | static bool warned = false; |
| 132 | if (!g_iface.enginetrace) { | 159 | if (!g_iface.enginetrace) { |
| 133 | if (!warned) { | 160 | if (!warned) { |
| @@ -137,15 +164,51 @@ bool is_visible(const Vector &eye, const Vector &point) { | |||
| 137 | return true; | 164 | return true; |
| 138 | } | 165 | } |
| 139 | 166 | ||
| 140 | RayCompat ray; | 167 | float dx = point.x - eye.x; |
| 141 | make_ray(ray, eye, point); | 168 | float dy = point.y - eye.y; |
| 142 | GameTraceCompat tr{}; | 169 | float dz = point.z - eye.z; |
| 143 | WorldOnlyFilter filter; | 170 | float len2 = dx * dx + dy * dy + dz * dz; |
| 144 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | 171 | if (len2 < 0.0001f) |
| 145 | GameTraceCompat *); | 172 | return true; |
| 146 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | 173 | float inv_len = 1.0f / sqrtf(len2); |
| 147 | MASK_VISIBLE, &filter, &tr); | 174 | Vector dir{dx * inv_len, dy * inv_len, dz * inv_len}; |
| 148 | return tr.fraction > 0.97f && !tr.startsolid; | 175 | |
| 176 | // Trace everything that bullets care about: world, static props, crates and | ||
| 177 | // NPCs. Non-solid glass and glass whose material name marks it breakable is | ||
| 178 | // skipped; solid non-break glass (bulletproof) blocks. | ||
| 179 | Vector start = eye; | ||
| 180 | for (int pass = 0; pass < 5; pass++) { | ||
| 181 | RayCompat ray; | ||
| 182 | make_ray(ray, start, point); | ||
| 183 | GameTraceCompat tr{}; | ||
| 184 | VisibilityFilter filter(self, target); | ||
| 185 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 186 | GameTraceCompat *); | ||
| 187 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | ||
| 188 | MASK_SHOT, &filter, &tr); | ||
| 189 | if (tr.startsolid || tr.allsolid) | ||
| 190 | return false; | ||
| 191 | if (tr.fraction >= 1.0f) | ||
| 192 | return true; | ||
| 193 | |||
| 194 | bool glass = (tr.contents & CONTENTS_WINDOW) != 0 || | ||
| 195 | surface_is_glass(tr.surface); | ||
| 196 | bool solid = (tr.contents & CONTENTS_SOLID) != 0; | ||
| 197 | bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); | ||
| 198 | if (glass && (!solid || breakable_glass)) { | ||
| 199 | // Step just past the pane and look for the next obstruction. | ||
| 200 | start = tr.endpos; | ||
| 201 | start.x += dir.x * 0.5f; | ||
| 202 | start.y += dir.y * 0.5f; | ||
| 203 | start.z += dir.z * 0.5f; | ||
| 204 | logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", | ||
| 205 | tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, | ||
| 206 | (int)breakable_glass); | ||
| 207 | continue; | ||
| 208 | } | ||
| 209 | return false; | ||
| 210 | } | ||
| 211 | return false; | ||
| 149 | } | 212 | } |
| 150 | 213 | ||
| 151 | // Trace the crosshair ray and return the first entity hit (nullptr for world | 214 | // Trace the crosshair ray and return the first entity hit (nullptr for world |
