summaryrefslogtreecommitdiffstats
path: root/src/vischeck.cpp
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 /src/vischeck.cpp
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.
Diffstat (limited to 'src/vischeck.cpp')
-rw-r--r--src/vischeck.cpp113
1 files changed, 88 insertions, 25 deletions
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