diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-03 22:03:01 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-03 22:03:01 +0100 |
| commit | 2e9373d530bca779b4070a79972f246ef41e4038 (patch) | |
| tree | cc263d92cdcf7c9befd29f36da3cf04ed0b3a7f4 /src/vischeck.cpp | |
| parent | Handles: use the 11-bit networked EHANDLE entity index (diff) | |
| download | hl2h-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 '')
| -rw-r--r-- | src/vischeck.cpp | 113 |
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 | ||
| 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 |
