// vischeck.cpp — world-only line-of-sight test via EngineTraceClient003. // // IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the // aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows // (you can see through them) while still hitting opaque world geometry. // Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the // deliberate trade-off for this build. #include "hl2h.h" #include #include namespace { struct alignas(16) VectorAlignedCompat { float x, y, z, w; }; // Ray_t: four VectorAligned (16 bytes each) followed by two bools. struct alignas(16) RayCompat { VectorAlignedCompat start; VectorAlignedCompat delta; VectorAlignedCompat startOffset; VectorAlignedCompat extents; bool isRay; bool isSwept; }; static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout"); static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); // trace_t / CGameTrace layout (Source 2013, 32-bit): // CBaseTrace: startpos 0, endpos 12, cplane_t plane 24 (20 bytes), // fraction 44, contents 48, dispFlags 52, allsolid 54, // startsolid 55 // CGameTrace: fractionleftsolid 56, csurface_t surface 60 (8 bytes), // hitgroup 68, physicsbone 72, m_pEnt 76, hitbox 80 struct SurfaceCompat { const char *name; short surfaceProps; unsigned short flags; }; struct GameTraceCompat { Vector startpos; Vector endpos; Vector normal; float dist; uint8_t type; uint8_t signbits; uint8_t pad[2]; float fraction; int contents; uint16_t dispFlags; bool allsolid; bool startsolid; float fractionleftsolid; SurfaceCompat surface; int hitgroup; short physicsbone; void *ent; int hitbox; uint8_t rest[64]; // future-proof / avoid short writes }; static_assert(offsetof(GameTraceCompat, fraction) == 44, "trace_t fraction"); static_assert(offsetof(GameTraceCompat, startsolid) == 55, "trace_t startsolid"); static_assert(offsetof(GameTraceCompat, ent) == 76, "trace_t m_pEnt"); // ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, // vtable[1] = GetTraceType. struct ITraceFilterCompat { virtual bool ShouldHitEntity(void *pEntity, int contentsMask) = 0; virtual int GetTraceType() const = 0; }; struct WorldOnlyFilter : ITraceFilterCompat { bool ShouldHitEntity(void *, int) override { return false; } int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY }; struct EntityFilter : ITraceFilterCompat { void *ignore; explicit EntityFilter(void *ig) : ignore(ig) {} bool ShouldHitEntity(void *pEntity, int) override { return pEntity != ignore; } int GetTraceType() const override { return 0; } // TRACE_EVERYTHING }; // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | // CONTENTS_IGNORE_NODRAW_OPAQUE constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; // MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u | 0x4000000u | 0x40000000u; // MASK_PLAYERSOLID = SOLID|MOVEABLE|PLAYERCLIP|WINDOW|MONSTER|GRATE constexpr unsigned MASK_PLAYERSOLID = 0x1u | 0x4000u | 0x10000u | 0x4u | 0x2000000u | 0x8u; static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { float dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z; ray = RayCompat{}; ray.start = {start.x, start.y, start.z, 0.0f}; ray.delta = {dx, dy, dz, 0.0f}; ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; ray.isRay = true; ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; } // Swept AABB matching the standing player hull used by CGameMovement. // Ray_t::Init shifts m_Start to the box center and stores the negated center // offset in m_StartOffset. With mins=(-16,-16,0), maxs=(16,16,72), the // extents are (16,16,36) and m_StartOffset is (0,0,-36). static void make_standing_hull_ray(RayCompat &ray, const Vector &origin, float distance) { ray = RayCompat{}; ray.start = {origin.x, origin.y, origin.z + 36.0f, 0.0f}; ray.delta = {0.0f, 0.0f, -distance, 0.0f}; ray.startOffset = {0.0f, 0.0f, -36.0f, 0.0f}; ray.extents = {16.0f, 16.0f, 36.0f, 0.0f}; ray.isRay = false; ray.isSwept = true; } } // namespace bool is_visible(const Vector &eye, const Vector &point) { static bool warned = false; if (!g_iface.enginetrace) { if (!warned) { warned = true; logf("vischeck: EngineTraceClient003 unavailable; failing open"); } return true; } RayCompat ray; make_ray(ray, eye, point); GameTraceCompat tr{}; WorldOnlyFilter filter; using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, GameTraceCompat *); vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_VISIBLE, &filter, &tr); return tr.fraction > 0.97f && !tr.startsolid; } // Trace the crosshair ray and return the first entity hit (nullptr for world // or no hit). `ignore` is the local player entity pointer. void *trace_entity(const Vector &start, const Vector &end, void *ignore) { if (!g_iface.enginetrace) return nullptr; RayCompat ray; make_ray(ray, start, end); GameTraceCompat tr{}; EntityFilter filter(ignore); using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, GameTraceCompat *); vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_SHOT, &filter, &tr); if (tr.fraction >= 1.0f || tr.startsolid) return nullptr; return tr.ent; } // Vertical distance from the standing player hull's feet to the first // standable surface below. Matches CategorizePosition's 2-unit probe and its // plane.normal.z >= 0.7 rule, but sweeps much farther so the caller can // predict the landing. Returns -1 when there is no usable ground. float ground_distance(const Vector &origin, void *ignore, float max_dist) { if (!g_iface.enginetrace || max_dist <= 0.0f) return -1.0f; RayCompat ray; make_standing_hull_ray(ray, origin, max_dist); GameTraceCompat tr{}; EntityFilter filter(ignore); using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, GameTraceCompat *); vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_PLAYERSOLID, &filter, &tr); if (tr.startsolid || tr.fraction >= 1.0f || tr.normal.z < 0.7f) { static int solid_logs = 0; if (tr.startsolid && solid_logs < 4) { solid_logs++; logd("ground trace: startsolid at (%.1f %.1f %.1f)", origin.x, origin.y, origin.z); } return -1.0f; } return tr.fraction * max_dist; }