diff options
Diffstat (limited to 'src/vischeck.cpp')
| -rw-r--r-- | src/vischeck.cpp | 62 |
1 files changed, 53 insertions, 9 deletions
diff --git a/src/vischeck.cpp b/src/vischeck.cpp index 6b531b9..6126c88 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp | |||
| @@ -88,12 +88,29 @@ struct EntityFilter : ITraceFilterCompat { | |||
| 88 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | 88 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING |
| 89 | }; | 89 | }; |
| 90 | 90 | ||
| 91 | // True when pEntity is a child of target: held weapons are separate entities | ||
| 92 | // and heavy NPCs use phys_bone_follower entities for their collision model, so | ||
| 93 | // a trace toward an NPC's torso otherwise stops on the weapon/follower and the | ||
| 94 | // target reads as occluded. CBaseHandle stores the entity index in the low | ||
| 95 | // bits, so the owner handle resolves through the client entity list. | ||
| 96 | static bool owned_by(void *pEntity, void *target) { | ||
| 97 | if (!pEntity || !target || g_net.owner < 0) | ||
| 98 | return false; | ||
| 99 | uint32_t h = *(uint32_t *)((uintptr_t)pEntity + g_net.owner); | ||
| 100 | int idx = (int)(h & 0x1FFFu); | ||
| 101 | if (idx <= 0) | ||
| 102 | return false; | ||
| 103 | return entitylist_get(idx) == target; | ||
| 104 | } | ||
| 105 | |||
| 91 | struct VisibilityFilter : ITraceFilterCompat { | 106 | struct VisibilityFilter : ITraceFilterCompat { |
| 92 | void *self; | 107 | void *self; |
| 93 | void *target; | 108 | void *target; |
| 94 | VisibilityFilter(void *s, void *t) : self(s), target(t) {} | 109 | VisibilityFilter(void *s, void *t) : self(s), target(t) {} |
| 95 | bool ShouldHitEntity(void *pEntity, int) override { | 110 | bool ShouldHitEntity(void *pEntity, int) override { |
| 96 | return pEntity != self && pEntity != target; | 111 | if (pEntity == self || pEntity == target) |
| 112 | return false; | ||
| 113 | return !owned_by(pEntity, target); | ||
| 97 | } | 114 | } |
| 98 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | 115 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING |
| 99 | }; | 116 | }; |
| @@ -176,14 +193,14 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, | |||
| 176 | // Trace everything that bullets care about: world, static props, crates and | 193 | // 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 | 194 | // NPCs. Non-solid glass and glass whose material name marks it breakable is |
| 178 | // skipped; solid non-break glass (bulletproof) blocks. | 195 | // skipped; solid non-break glass (bulletproof) blocks. |
| 196 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 197 | GameTraceCompat *); | ||
| 179 | Vector start = eye; | 198 | Vector start = eye; |
| 180 | for (int pass = 0; pass < 5; pass++) { | 199 | for (int pass = 0; pass < 5; pass++) { |
| 181 | RayCompat ray; | 200 | RayCompat ray; |
| 182 | make_ray(ray, start, point); | 201 | make_ray(ray, start, point); |
| 183 | GameTraceCompat tr{}; | 202 | GameTraceCompat tr{}; |
| 184 | VisibilityFilter filter(self, target); | 203 | 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, | 204 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, |
| 188 | MASK_SHOT, &filter, &tr); | 205 | MASK_SHOT, &filter, &tr); |
| 189 | if (tr.startsolid || tr.allsolid) | 206 | if (tr.startsolid || tr.allsolid) |
| @@ -196,11 +213,34 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, | |||
| 196 | bool solid = (tr.contents & CONTENTS_SOLID) != 0; | 213 | bool solid = (tr.contents & CONTENTS_SOLID) != 0; |
| 197 | bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); | 214 | bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); |
| 198 | if (glass && (!solid || breakable_glass)) { | 215 | if (glass && (!solid || breakable_glass)) { |
| 199 | // Step just past the pane and look for the next obstruction. | 216 | // Glass brushes are commonly 1-4 units thick, so the old fixed 0.5-unit |
| 200 | start = tr.endpos; | 217 | // step landed inside the brush and the next pass reported startsolid, |
| 201 | start.x += dir.x * 0.5f; | 218 | // which read as "blocked". Advance until a probe from the new start is |
| 202 | start.y += dir.y * 0.5f; | 219 | // clear, doubling the step, and stop if we pass the target. |
| 203 | start.z += dir.z * 0.5f; | 220 | Vector next = tr.endpos; |
| 221 | float step = 1.0f; | ||
| 222 | bool clear = false; | ||
| 223 | for (int guard = 0; guard < 16 && !clear; guard++) { | ||
| 224 | next.x += dir.x * step; | ||
| 225 | next.y += dir.y * step; | ||
| 226 | next.z += dir.z * step; | ||
| 227 | float ox = next.x - point.x, oy = next.y - point.y, | ||
| 228 | oz = next.z - point.z; | ||
| 229 | if (ox * ox + oy * oy + oz * oz < 0.25f) | ||
| 230 | return true; // stepped past the target; nothing left to block it | ||
| 231 | RayCompat probe; | ||
| 232 | make_ray(probe, next, point); | ||
| 233 | GameTraceCompat ptr{}; | ||
| 234 | VisibilityFilter pfilter(self, target); | ||
| 235 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)( | ||
| 236 | g_iface.enginetrace, &probe, MASK_SHOT, &pfilter, &ptr); | ||
| 237 | clear = !ptr.startsolid && !ptr.allsolid; | ||
| 238 | if (!clear) | ||
| 239 | step *= 2.0f; | ||
| 240 | } | ||
| 241 | if (!clear) | ||
| 242 | return false; // still inside geometry; treat as blocked | ||
| 243 | start = next; | ||
| 204 | logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", | 244 | logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", |
| 205 | tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, | 245 | tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, |
| 206 | (int)breakable_glass); | 246 | (int)breakable_glass); |
| @@ -270,7 +310,11 @@ bool point_ground(const Vector &point, void *ignore) { | |||
| 270 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | 310 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, |
| 271 | MASK_PLAYERSOLID, &filter, &tr); | 311 | MASK_PLAYERSOLID, &filter, &tr); |
| 272 | // A start inside geometry means a wall/step there, not a ledge. | 312 | // A start inside geometry means a wall/step there, not a ledge. |
| 273 | return tr.startsolid || tr.fraction < 1.0f; | 313 | if (tr.startsolid) |
| 314 | return true; | ||
| 315 | // Only a standable surface counts: a steep cliff face hit within 96 units is | ||
| 316 | // not a floor, and treating it as one suppressed the edge jump. | ||
| 317 | return tr.fraction < 1.0f && tr.normal.z >= 0.7f; | ||
| 274 | } | 318 | } |
| 275 | 319 | ||
| 276 | // Vertical distance from the standing player hull's feet to the first | 320 | // Vertical distance from the standing player hull's feet to the first |
