summaryrefslogtreecommitdiffstats
path: root/src/vischeck.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-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