summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-03 22:03:01 +0100
committersalmonik <salmonik@tuta.io>2026-11-03 22:03:01 +0100
commit2e9373d530bca779b4070a79972f246ef41e4038 (patch)
treecc263d92cdcf7c9befd29f36da3cf04ed0b3a7f4
parentHandles: use the 11-bit networked EHANDLE entity index (diff)
downloadhl2h-2e9373d530bca779b4070a79972f246ef41e4038.tar.gz
hl2h-2e9373d530bca779b4070a79972f246ef41e4038.zip
GUI: recover invalid fonts; vischeck: props and bulletproof glass
- Validate the GUI font handles with GetTextSize and recreate them when the local player entity or UI scale changes. Small scaled sizes fall back to the proven 16 px size, and the failure log/toast is emitted once instead of every frame. This fixes sessions where the menu rendered with no text after a level transition. - Rework visibility: trace with MASK_SHOT/TRACE_EVERYTHING while ignoring the local player and target, so static props, crates and other NPCs block. Non-solid glass and breakable-glass materials are skipped; solid non-break glass (bulletproof) blocks. Fix CONTENTS_WINDOW 0x2 (was 0x4) in the masks. - Pass self/target to is_visible from aimbot/silent/melee callers.
-rw-r--r--CONTEXT.md11
-rw-r--r--src/esp.cpp6
-rw-r--r--src/gui.cpp106
-rw-r--r--src/hl2h.h3
-rw-r--r--src/vischeck.cpp113
5 files changed, 184 insertions, 55 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 5f27572..922ea98 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -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;
39static int g_body_h = 15; 39static int g_body_h = 15;
40static int g_title_h = 20; 40static int g_title_h = 20;
41static float g_font_scale = 0.0f; 41static float g_font_scale = 0.0f;
42static void *g_font_local = (void *)-1;
43static bool g_font_fail_logged = false;
42static int g_scr_w = 0, g_scr_h = 0; 44static int g_scr_w = 0, g_scr_h = 0;
43 45
44static void s_color(int r, int g, int b, int a) { 46static 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
488static void ensure_fonts() { 490static 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
498static void gui_reset_fonts() {
499 g_font_body = 0;
500 g_font_title = 0;
501 g_font_scale = 0.0f;
502}
503
504static 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
519static 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
525static int text_w(const char *s, int tall) { 583static int text_w(const char *s, int tall) {
diff --git a/src/hl2h.h b/src/hl2h.h
index 9c0440f..45a297e 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -272,7 +272,8 @@ void esp_draw();
272void esp_draw_items(); 272void esp_draw_items();
273bool world_to_screen(const Vector &w, float &sx, float &sy); 273bool world_to_screen(const Vector &w, float &sx, float &sy);
274void set_silent_restore(const QAngle &angles); 274void set_silent_restore(const QAngle &angles);
275bool is_visible(const Vector &eye, const Vector &point); 275bool is_visible(const Vector &eye, const Vector &point,
276 void *self = nullptr, void *target = nullptr);
276void *trace_entity(const Vector &start, const Vector &end, void *ignore); 277void *trace_entity(const Vector &start, const Vector &end, void *ignore);
277float ground_distance(const Vector &origin, void *ignore, float max_dist); 278float 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
13namespace { 17namespace {
14 18
@@ -75,11 +79,6 @@ struct ITraceFilterCompat {
75 virtual int GetTraceType() const = 0; 79 virtual int GetTraceType() const = 0;
76}; 80};
77 81
78struct WorldOnlyFilter : ITraceFilterCompat {
79 bool ShouldHitEntity(void *, int) override { return false; }
80 int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY
81};
82
83struct EntityFilter : ITraceFilterCompat { 82struct 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 | 91struct VisibilityFilter : ITraceFilterCompat {
93// CONTENTS_IGNORE_NODRAW_OPAQUE 92 void *self;
94constexpr 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
101constexpr unsigned CONTENTS_SOLID = 0x1u;
102constexpr unsigned CONTENTS_WINDOW = 0x2u;
95// MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX 103// MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX
96constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u | 104constexpr 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
99constexpr unsigned MASK_PLAYERSOLID = 107constexpr unsigned MASK_PLAYERSOLID =
100 0x1u | 0x4000u | 0x10000u | 0x4u | 0x2000000u | 0x8u; 108 0x1u | 0x4000u | 0x10000u | 0x2u | 0x2000000u | 0x8u;
101 109
102static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { 110static 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
130bool is_visible(const Vector &eye, const Vector &point) { 138static 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
148static bool surface_is_breakable_glass(const SurfaceCompat &surf) {
149 return surface_contains(surf, "break");
150}
151
152static bool surface_is_glass(const SurfaceCompat &surf) {
153 return surface_contains(surf, "glass") || surface_contains(surf, "window");
154}
155
156bool 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