diff options
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 |
