From 85f92397514fdd89efe623d3fd77f46902253155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20P=C5=99ikryl?= Date: Tue, 2 Jun 2026 15:26:35 +0200 Subject: [PATCH 1/3] Update: add INavPhysicsInterface implementation for raytrace usage --- CMakeLists.txt | 3 + .../GameEvents/EventPlayerDisconnect.g.cs | 8 +- .../Generated/Natives/API.cs | 127 ++++++++-- .../Modules/Utils/ContentsMask.cs | 122 ++++++++++ .../Modules/Utils/Trace.cs | 150 ++++++++++++ .../interfaces/navphysicsinterface.cpp | 142 ++++++++++++ .../cs2_sdk/interfaces/navphysicsinterface.h | 66 ++++++ src/scripting/natives/natives_raytrace.cpp | 218 ++++++++++++++++++ src/scripting/natives/natives_raytrace.yaml | 6 + 9 files changed, 821 insertions(+), 21 deletions(-) create mode 100644 managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs create mode 100644 managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs create mode 100644 src/core/cs2_sdk/interfaces/navphysicsinterface.cpp create mode 100644 src/core/cs2_sdk/interfaces/navphysicsinterface.h create mode 100644 src/scripting/natives/natives_raytrace.cpp create mode 100644 src/scripting/natives/natives_raytrace.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index d60e14321..3f40d679d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,8 @@ set(SOURCE_FILES src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h src/core/cs2_sdk/interfaces/cs2_interfaces.h src/core/cs2_sdk/interfaces/cs2_interfaces.cpp + src/core/cs2_sdk/interfaces/navphysicsinterface.h + src/core/cs2_sdk/interfaces/navphysicsinterface.cpp src/core/cs2_sdk/schema.h src/core/cs2_sdk/schema.cpp src/core/function.cpp @@ -107,6 +109,7 @@ set(SOURCE_FILES src/core/managers/voice_manager.h src/core/managers/usermessage_manager.cpp src/core/managers/usermessage_manager.h + src/scripting/natives/natives_raytrace.cpp src/scripting/natives/natives_dynamichooks.cpp src/core/game_system.h src/core/game_system.cpp diff --git a/managed/CounterStrikeSharp.API/Generated/GameEvents/EventPlayerDisconnect.g.cs b/managed/CounterStrikeSharp.API/Generated/GameEvents/EventPlayerDisconnect.g.cs index 0eaa251a0..6d17a5f3f 100644 --- a/managed/CounterStrikeSharp.API/Generated/GameEvents/EventPlayerDisconnect.g.cs +++ b/managed/CounterStrikeSharp.API/Generated/GameEvents/EventPlayerDisconnect.g.cs @@ -11,7 +11,13 @@ public class EventPlayerDisconnect : GameEvent { public EventPlayerDisconnect(IntPtr pointer) : base(pointer){} public EventPlayerDisconnect(bool force) : base("player_disconnect", force){} - // player name + // Was the player ever fully connected? + public bool EverFullyConnected + { + get => Get("ever_fully_connected"); + set => Set("ever_fully_connected", value); + } +// player name public string Name { get => Get("name"); diff --git a/managed/CounterStrikeSharp.API/Generated/Natives/API.cs b/managed/CounterStrikeSharp.API/Generated/Natives/API.cs index a9794be81..790943a5e 100644 --- a/managed/CounterStrikeSharp.API/Generated/Natives/API.cs +++ b/managed/CounterStrikeSharp.API/Generated/Natives/API.cs @@ -1603,6 +1603,113 @@ public static IntPtr MetaFactory(string interfacename){ } } + public static void TraceShape(IntPtr startpos, IntPtr angles, IntPtr ignoreentity, ulong interactsas, ulong interactswith, ulong interactsexclude, IntPtr outresult){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(startpos); + ScriptContext.GlobalScriptContext.PushPrimitive(angles); + ScriptContext.GlobalScriptContext.PushPrimitive(ignoreentity); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsas); + ScriptContext.GlobalScriptContext.PushPrimitive(interactswith); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsexclude); + ScriptContext.GlobalScriptContext.PushPrimitive(outresult); + ScriptContext.GlobalScriptContext.SetIdentifier(0xDBED3874); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static void TraceEndShape(IntPtr startpos, IntPtr endpos, IntPtr ignoreentity, ulong interactsas, ulong interactswith, ulong interactsexclude, IntPtr outresult){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(startpos); + ScriptContext.GlobalScriptContext.PushPrimitive(endpos); + ScriptContext.GlobalScriptContext.PushPrimitive(ignoreentity); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsas); + ScriptContext.GlobalScriptContext.PushPrimitive(interactswith); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsexclude); + ScriptContext.GlobalScriptContext.PushPrimitive(outresult); + ScriptContext.GlobalScriptContext.SetIdentifier(0x8A833D84); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static void TraceHullShape(IntPtr startpos, IntPtr endpos, IntPtr mins, IntPtr maxs, IntPtr ignoreentity, ulong interactsas, ulong interactswith, ulong interactsexclude, IntPtr outresult){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(startpos); + ScriptContext.GlobalScriptContext.PushPrimitive(endpos); + ScriptContext.GlobalScriptContext.PushPrimitive(mins); + ScriptContext.GlobalScriptContext.PushPrimitive(maxs); + ScriptContext.GlobalScriptContext.PushPrimitive(ignoreentity); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsas); + ScriptContext.GlobalScriptContext.PushPrimitive(interactswith); + ScriptContext.GlobalScriptContext.PushPrimitive(interactsexclude); + ScriptContext.GlobalScriptContext.PushPrimitive(outresult); + ScriptContext.GlobalScriptContext.SetIdentifier(0x6C62B676); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static ulong PointContents(IntPtr pos, ulong contentsmask){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(pos); + ScriptContext.GlobalScriptContext.PushPrimitive(contentsmask); + ScriptContext.GlobalScriptContext.SetIdentifier(0x8A68FFAC); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return ScriptContext.GlobalScriptContext.GetResultPrimitive(); + } + } + + public static bool CheckAreaOverlappingEntity(IntPtr area, IntPtr entity, bool extrudehullheight){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(area); + ScriptContext.GlobalScriptContext.PushPrimitive(entity); + ScriptContext.GlobalScriptContext.PushPrimitive(extrudehullheight); + ScriptContext.GlobalScriptContext.SetIdentifier(0x2ACFC3F3); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return ScriptContext.GlobalScriptContext.GetResultPrimitive(); + } + } + + public static void GetEntityWorldSpaceAabb(IntPtr entity, IntPtr minsout, IntPtr maxsout){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.PushPrimitive(entity); + ScriptContext.GlobalScriptContext.PushPrimitive(minsout); + ScriptContext.GlobalScriptContext.PushPrimitive(maxsout); + ScriptContext.GlobalScriptContext.SetIdentifier(0x6C485DCE); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static IntPtr GetEconItemSystem(){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.SetIdentifier(0x981E9B5B); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return ScriptContext.GlobalScriptContext.GetResultPrimitive(); + } + } + + public static bool IsServerPaused(){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return ScriptContext.GlobalScriptContext.GetResultPrimitive(); + } + } + public static short GetSchemaOffset(string classname, string propname){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); @@ -1692,26 +1799,6 @@ public static void SchemaNetworkStateChanged(IntPtr instance, uint offset, uint } } - public static IntPtr GetEconItemSystem(){ - lock (ScriptContext.GlobalScriptContext.Lock) { - ScriptContext.GlobalScriptContext.Reset(); - ScriptContext.GlobalScriptContext.SetIdentifier(0x981E9B5B); - ScriptContext.GlobalScriptContext.Invoke(); - ScriptContext.GlobalScriptContext.CheckErrors(); - return ScriptContext.GlobalScriptContext.GetResultPrimitive(); - } - } - - public static bool IsServerPaused(){ - lock (ScriptContext.GlobalScriptContext.Lock) { - ScriptContext.GlobalScriptContext.Reset(); - ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC); - ScriptContext.GlobalScriptContext.Invoke(); - ScriptContext.GlobalScriptContext.CheckErrors(); - return ScriptContext.GlobalScriptContext.GetResultPrimitive(); - } - } - public static IntPtr CreateTimer(float interval, InputArgument callback, int flags){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs b/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs new file mode 100644 index 000000000..d3aaae2f7 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs @@ -0,0 +1,122 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . + */ + +namespace CounterStrikeSharp.API.Modules.Utils; + +/// +/// Contents flags for physics interaction layers (bspflags.h / const.h). +/// Used with and . +/// +public static class Contents +{ + public const ulong Empty = 0ul; + + public const ulong Solid = 1ul << 0; + public const ulong Hitbox = 1ul << 1; + public const ulong Trigger = 1ul << 2; + public const ulong Sky = 1ul << 3; + + public const ulong PlayerClip = 1ul << 4; + public const ulong NpcClip = 1ul << 5; + public const ulong BlockLos = 1ul << 6; + public const ulong BlockLight = 1ul << 7; + public const ulong Ladder = 1ul << 8; + public const ulong Pickup = 1ul << 9; + public const ulong BlockSound = 1ul << 10; + public const ulong NoDraw = 1ul << 11; + public const ulong Window = 1ul << 12; + public const ulong PassBullets = 1ul << 13; + public const ulong WorldGeometry = 1ul << 14; + public const ulong Water = 1ul << 15; + public const ulong Slime = 1ul << 16; + public const ulong TouchAll = 1ul << 17; + public const ulong Player = 1ul << 18; + public const ulong Npc = 1ul << 19; + public const ulong Debris = 1ul << 20; + public const ulong PhysicsProp = 1ul << 21; + public const ulong NavIgnore = 1ul << 22; + public const ulong NavLocalIgnore = 1ul << 23; + public const ulong PostProcessingVolume = 1ul << 24; + public const ulong UnusedLayer3 = 1ul << 25; + public const ulong CarriedObject = 1ul << 26; + public const ulong Pushaway = 1ul << 27; + public const ulong ServerEntityOnClient = 1ul << 28; + public const ulong CarriedWeapon = 1ul << 29; + public const ulong StaticLevel = 1ul << 30; + + public const ulong CsgoTeam1 = 1ul << 31; + public const ulong CsgoTeam2 = 1ul << 32; + public const ulong CsgoGrenadeClip = 1ul << 33; + public const ulong CsgoDroneClip = 1ul << 34; + public const ulong CsgoMoveable = 1ul << 35; + public const ulong CsgoOpaque = 1ul << 36; + public const ulong CsgoMonster = 1ul << 37; + public const ulong CsgoUnusedLayer = 1ul << 38; + public const ulong CsgoThrownGrenade = 1ul << 39; +} + +/// +/// Composite trace masks (bspflags.h MASK_* defines). +/// +public static class Mask +{ + public const ulong All = ~0ul; + + /// Everything that is normally solid. + public const ulong Solid = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; + + /// Everything that blocks player movement. + public const ulong PlayerSolid = Contents.Solid | Contents.PlayerClip | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; + + /// Blocks NPC movement. + public const ulong NpcSolid = Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; + + /// Blocks fluid movement. + public const ulong NpcFluid = Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc; + + /// Water physics. + public const ulong Water = Contents.Water | Contents.Slime; + + /// Bullets see these as solid. + public const ulong Shot = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | Contents.Debris | Contents.Hitbox; + + /// Bullets see these as solid, except monsters (world + brush only). + public const ulong ShotBrushOnly = Contents.Solid | Contents.Window | Contents.Debris; + + /// Non-raycasted weapons see this as solid (includes grates). + public const ulong ShotHull = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | Contents.Debris | Contents.PassBullets; + + /// Hits solids (not grates) and passes through everything else. + public const ulong ShotPortal = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc; + + /// Everything normally solid, except monsters (world + brush only). + public const ulong SolidBrushOnly = Contents.Solid | Contents.Window | Contents.PassBullets; + + /// Everything normally solid for player movement, except monsters (world + brush only). + public const ulong PlayerSolidBrushOnly = Contents.Solid | Contents.Window | Contents.PlayerClip | Contents.PassBullets; + + /// Everything normally solid for NPC movement, except monsters (world + brush only). + public const ulong NpcSolidBrushOnly = Contents.Solid | Contents.Window | Contents.NpcClip | Contents.PassBullets; +} + +public enum RayType_t : uint +{ + RAY_TYPE_LINE = 0x0, + RAY_TYPE_SPHERE = 0x1, + RAY_TYPE_HULL = 0x2, + RAY_TYPE_CAPSULE = 0x3, + RAY_TYPE_MESH = 0x4 +}; diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs new file mode 100644 index 000000000..193bb4671 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs @@ -0,0 +1,150 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . + */ + +using System.Runtime.InteropServices; +using CounterStrikeSharp.API.Core; + +namespace CounterStrikeSharp.API.Modules.Utils; + +/// Controls which physics layers a trace interacts with. +public sealed class TraceOptions +{ + /// Which interaction layers the trace represents (analogous to Source 1 contents). + public ulong InteractsAs { get; set; } = 0UL; // CONTENTS_EMPTY + + /// Which interaction layers the trace collides with (analogous to Source 1 trace mask). + public ulong InteractsWith { get; set; } = ~0UL; // MASK_ALL + + /// Which interaction layers are explicitly excluded. + public ulong InteractsExclude { get; set; } = 0UL; // CONTENTS_EMPTY +} + +/// +/// Result of a ray or hull trace. +/// Plain unmanaged value type — stack-allocated by the methods and returned +/// by value. No heap allocation, no disposal. Layout matches CSSTraceResult in +/// natives_raytrace.cpp. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public unsafe struct TraceResult +{ + private nint m_pSurfaceProperties; // 0 CPhysSurfaceProperties* + private nint m_pEnt; // 8 CEntityInstance* + private nint m_pHitbox; // 16 CHitBox* + private nint m_hBody; // 24 HPhysicsBody + private nint m_hShape; // 32 HPhysicsShape + private ulong m_nContents; // 40 + private float m_vStartX, m_vStartY, m_vStartZ; // 48 + private float m_vEndX, m_vEndY, m_vEndZ; // 60 + private float m_vHitNormalX, m_vHitNormalY, m_vHitNormalZ; // 72 + private float m_vHitPointX, m_vHitPointY, m_vHitPointZ; // 84 + private float m_flHitOffset; // 96 + private float m_flFraction; // 100 + private int m_nTriangle; // 104 + private short m_nHitboxBoneIndex; // 108 + private byte m_eRayType; // 110 (0=Line 1=Sphere 2=Hull 3=Capsule) + private byte m_bStartInSolid; // 111 + private byte m_bExactHitPoint; // 112 + + public bool DidHit() => m_flFraction < 1.0f || m_bStartInSolid != 0; + public bool IsAllSolid => m_bStartInSolid != 0; + public bool HasExactHitPoint => m_bExactHitPoint != 0; + + public float Fraction => m_flFraction; + public float HitOffset => m_flHitOffset; + public ulong Contents => m_nContents; + public int TriangleIndex => m_nTriangle; + public short HitboxBoneIndex => m_nHitboxBoneIndex; + public RayType_t RayType => (RayType_t)m_eRayType; + + public Vector StartPos => new(m_vStartX, m_vStartY, m_vStartZ); + public Vector EndPos => new(m_vEndX, m_vEndY, m_vEndZ); + public Vector Normal => new(m_vHitNormalX, m_vHitNormalY, m_vHitNormalZ); + public Vector HitPoint => new(m_vHitPointX, m_vHitPointY, m_vHitPointZ); + + public CEntityInstance HitEntity() => new(m_pEnt); + + /// Native CHitBox* — opaque pointer. + public nint Hitbox() => m_pHitbox; + + /// Native HPhysicsBody — opaque pointer. + public nint Body() => m_hBody; + + /// Native HPhysicsShape — opaque pointer. + public nint Shape() => m_hShape; + + /// Native CPhysSurfaceProperties* — opaque pointer. + public nint Surface() => m_pSurfaceProperties; +} + +/// +/// Static class for performing ray and hull traces against the game world. +/// +public static unsafe class Trace +{ + private static (ulong ia, ulong iw, ulong ie) Unpack(TraceOptions? opts) + => opts is null ? (0UL, ~0UL, 0UL) : (opts.InteractsAs, opts.InteractsWith, opts.InteractsExclude); + + /// + /// Performs a line trace from in the direction of + /// for up to 8192 units. + /// + public static TraceResult TraceShape(Vector startPos, QAngle angles, CBaseEntity? ignoreEntity = null, TraceOptions? options = null) + { + var (ia, iw, ie) = Unpack(options); + TraceResult result = default; + NativeAPI.TraceShape(startPos.Handle, angles.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + return result; + } + + /// Performs a line trace between two world-space points. + public static TraceResult TraceEndShape(Vector startPos, Vector endPos, CBaseEntity? ignoreEntity = null, TraceOptions? options = null) + { + var (ia, iw, ie) = Unpack(options); + TraceResult result = default; + NativeAPI.TraceEndShape(startPos.Handle, endPos.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + return result; + } + + /// Performs a hull (box) trace between two world-space points. + public static TraceResult TraceHullShape(Vector startPos, Vector endPos, Vector mins, Vector maxs, CBaseEntity? ignoreEntity = null, TraceOptions? options = null) + { + var (ia, iw, ie) = Unpack(options); + TraceResult result = default; + NativeAPI.TraceHullShape(startPos.Handle, endPos.Handle, mins.Handle, maxs.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + return result; + } + + /// Returns the contents bitmask at the given world position. + public static ulong PointContents(Vector pos, ulong contentsMask = ~0UL) + => NativeAPI.PointContents(pos.Handle, contentsMask); + + /// + /// Returns whether a nav area overlaps with an entity's bounding box. + /// is an opaque pointer to a CNavArea obtained from the nav system. + /// + public static bool CheckAreaOverlappingEntity(nint area, CBaseEntity entity, bool extrudeHullHeight = false) + => NativeAPI.CheckAreaOverlappingEntity(area, entity.Handle, extrudeHullHeight); + + /// Returns the world-space axis-aligned bounding box of . + public static (Vector Mins, Vector Maxs) GetEntityWorldSpaceAABB(CBaseEntity entity) + { + var mins = new Vector(); + var maxs = new Vector(); + NativeAPI.GetEntityWorldSpaceAabb(entity.Handle, mins.Handle, maxs.Handle); + return (mins, maxs); + } +} diff --git a/src/core/cs2_sdk/interfaces/navphysicsinterface.cpp b/src/core/cs2_sdk/interfaces/navphysicsinterface.cpp new file mode 100644 index 000000000..b872542fc --- /dev/null +++ b/src/core/cs2_sdk/interfaces/navphysicsinterface.cpp @@ -0,0 +1,142 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * ============================================================================= + * Source2Toolkit + * Copyright (C) 2025-2026 Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl, + * AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl and + * AlliedModders LLC give you permission to link the code of this program + * (as well as its derivative works) to "Counter-Strike 2," "Source 2," + * "Steam," and any Game MODs or server software running on software by + * Valve Corporation. You must obey the GNU General Public License in all + * respects for all other code used. + * + * Additionally, this exception applies to all derivative works unless + * otherwise stated in LICENSE.txt. + * + * Authors: + * - Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl + * - AlliedModders LLC + * + * Project: Source2Toolkit + */ + +#include "navphysicsinterface.h" + +#include "core/globals.h" +#include "core/memory_module.h" + +#include + +class CBaseEntity; + +void INavPhysicsInterface::TraceLine(const Vector& vStart, + const Vector& vEnd, + CBaseEntity* pIgnore, + uint64 nInteractsWith, + uint8 nCollisionGroup, + uint8 nObjectSetMask, + CGameTrace* trace) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + iface->Nav_TraceLine(vStart, vEnd, pIgnore, nInteractsWith, nCollisionGroup, nObjectSetMask, trace); +} + +void INavPhysicsInterface::TraceLine(const Vector& vStart, const Vector& vEnd, CTraceFilter* pFilter, CGameTrace* trace) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + iface->Nav_TraceLine(vStart, vEnd, pFilter, trace); +} + +void INavPhysicsInterface::TraceShape(const Ray_t& ray, + const Vector& vStart, + const Vector& vEnd, + CBaseEntity* pIgnore, + uint64 nInteractsWith, + uint8 nCollisionGroup, + uint8 nObjectSetMask, + CGameTrace* trace) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + iface->Nav_TraceShape(ray, vStart, vEnd, pIgnore, nInteractsWith, nCollisionGroup, nObjectSetMask, trace); +} + +void INavPhysicsInterface::TraceShape(const Ray_t& ray, const Vector& vStart, const Vector& vEnd, CTraceFilter* pFilter, CGameTrace* trace) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + iface->Nav_TraceShape(ray, vStart, vEnd, pFilter, trace); +} + +uint64 INavPhysicsInterface::PointContents(const Vector* const vTestPos, uint64 nContentsMask) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + return iface->Nav_PointContents(vTestPos, nContentsMask); +} + +bool INavPhysicsInterface::CheckAreaOverlappingEntity(const void* const rArea, const CBaseEntity* const rEntity, bool bExtrudeHullHeight) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + return iface->Nav_CheckAreaOverlappingEntity(rArea, rEntity, bExtrudeHullHeight); +} + +void INavPhysicsInterface::GetEntityWorldSpaceAABB(const CBaseEntity* const rEntity, Vector* pMinsOut, Vector* pMaxsOut) +{ + if (!vTable) + { + vTable = reinterpret_cast(counterstrikesharp::modules::server->FindVirtualTable("CNavPhysicsInterface")); + } + + assert(vTable); + auto* iface = reinterpret_cast(&vTable); + iface->Nav_GetEntityWorldSpaceAABB(rEntity, pMinsOut, pMaxsOut); +} diff --git a/src/core/cs2_sdk/interfaces/navphysicsinterface.h b/src/core/cs2_sdk/interfaces/navphysicsinterface.h new file mode 100644 index 000000000..472429c14 --- /dev/null +++ b/src/core/cs2_sdk/interfaces/navphysicsinterface.h @@ -0,0 +1,66 @@ +/** +* vim: set ts=4 sw=4 tw=99 noet: + * ============================================================================= + * Source2Toolkit + * Copyright (C) 2025-2026 Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl, + * AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl and + * AlliedModders LLC give you permission to link the code of this program + * (as well as its derivative works) to "Counter-Strike 2," "Source 2," + * "Steam," and any Game MODs or server software running on software by + * Valve Corporation. You must obey the GNU General Public License in all + * respects for all other code used. + * + * Additionally, this exception applies to all derivative works unless + * otherwise stated in LICENSE.txt. + * + * Authors: + * - Michal "Slynx (˙·٠● S l y n x ●٠·˙)" Přikryl + * - AlliedModders LLC + * + * Project: Source2Toolkit +*/ +#pragma once + +#include "gametrace.h" + +class INavPhysicsInterface +{ +private: + virtual ~INavPhysicsInterface() = 0; + virtual void Nav_TraceLine(const Vector &vStart, const Vector &vEnd, CBaseEntity *pIgnore, uint64 nInteractsWith, uint8 nCollisionGroup, uint8 nObjectSetMask, CGameTrace *trace) = 0; + virtual void Nav_TraceLine(const Vector &vStart, const Vector &vEnd, CTraceFilter *pFilter, CGameTrace *pTraceOut) = 0; + virtual void Nav_TraceShape(const Ray_t &ray, const Vector &vStart, const Vector &vEnd, CBaseEntity *pIgnore, uint64 nInteractsWith, uint8 nCollisionGroup, uint8 nObjectSetMask, CGameTrace *trace) = 0; + virtual void Nav_TraceShape(const Ray_t &ray, const Vector &vStart, const Vector &vEnd, CTraceFilter *pFilter, CGameTrace *trace) = 0; + virtual uint64 Nav_PointContents(const Vector *const vTestPos, uint64 nContentsMask) = 0; + virtual bool Nav_CheckAreaOverlappingEntity(/*CNavArea*/ const void *const rArea, const CBaseEntity *const rEntity, bool bExtrudeHullHeight) = 0; + virtual void Nav_GetEntityWorldSpaceAABB(const CBaseEntity *const rEntity, Vector *pMinsOut, Vector *pMaxsOut) = 0; + + virtual void Unk(void *) = 0; // Calls delete on the object passed in which is located at the start of the vtable. + +public: + static inline void **vTable {}; + +public: + static void TraceLine(const Vector &vStart, const Vector &vEnd, CBaseEntity *pIgnore, uint64 nInteractsWith, uint8 nCollisionGroup, uint8 nObjectSetMask, CGameTrace *trace); + static void TraceLine(const Vector &vStart, const Vector &vEnd, CTraceFilter *pFilter, CGameTrace *pTraceOut); + static void TraceShape(const Ray_t &ray, const Vector &vStart, const Vector &vEnd, CBaseEntity *pIgnore, uint64 nInteractsWith, uint8 nCollisionGroup, uint8 nObjectSetMask, CGameTrace *trace); + static void TraceShape(const Ray_t &ray, const Vector &vStart, const Vector &vEnd, CTraceFilter *pFilter, CGameTrace *trace); + static uint64 PointContents(const Vector *const vTestPos, uint64 nContentsMask); + static bool CheckAreaOverlappingEntity(const void *const rArea, const CBaseEntity *const rEntity, bool bExtrudeHullHeight); + static void GetEntityWorldSpaceAABB(const CBaseEntity *const rEntity, Vector *pMinsOut, Vector *pMaxsOut); +}; \ No newline at end of file diff --git a/src/scripting/natives/natives_raytrace.cpp b/src/scripting/natives/natives_raytrace.cpp new file mode 100644 index 000000000..790fe45a3 --- /dev/null +++ b/src/scripting/natives/natives_raytrace.cpp @@ -0,0 +1,218 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . + */ + +#include "scripting/autonative.h" +#include "scripting/script_engine.h" +#include "core/cs2_sdk/interfaces/navphysicsinterface.h" +#include "core/cs2_sdk/schema.h" +#include "core/globals.h" + +#include "gametrace.h" +#include "cmodel.h" +#include "mathlib/mathlib.h" +#include "entity2/entityinstance.h" +#include "entity2/entitysystem.h" +#include "entityhandle.h" + +namespace counterstrikesharp { + +#pragma pack(push, 1) +struct CSSTraceResult +{ + void* m_pSurfaceProperties; + void* m_pEnt; + void* m_pHitbox; + void* m_pBody; + void* m_pShape; + + uint64_t m_nContents; + + float m_flStartX, m_flStartY, m_flStartZ; + float m_flEndX, m_flEndY, m_flEndZ; + float m_flHitNormalX, m_flHitNormalY, m_flHitNormalZ; + float m_flHitPointX, m_flHitPointY, m_flHitPointZ; + + float m_flHitOffset; + float m_flFraction; + + int32_t m_nTriangle; + int16_t m_nHitboxBoneIndex; + uint8_t m_eRayType; + uint8_t m_bStartInSolid; + uint8_t m_bExactHitPoint; +}; +#pragma pack(pop) + +static void FillResult(CSSTraceResult* out, const CGameTrace& trace) +{ + out->m_pSurfaceProperties = const_cast(trace.m_pSurfaceProperties); + out->m_pEnt = trace.m_pEnt; + out->m_pHitbox = const_cast(trace.m_pHitbox); + out->m_pBody = trace.m_hBody; + out->m_pBody = trace.m_hShape; + + out->m_nContents = trace.m_nContents; + + out->m_flStartX = trace.m_vStartPos.x; + out->m_flStartY = trace.m_vStartPos.y; + out->m_flStartZ = trace.m_vStartPos.z; + out->m_flEndX = trace.m_vEndPos.x; + out->m_flEndY = trace.m_vEndPos.y; + out->m_flEndZ = trace.m_vEndPos.z; + out->m_flHitNormalX = trace.m_vHitNormal.x; + out->m_flHitNormalY = trace.m_vHitNormal.y; + out->m_flHitNormalZ = trace.m_vHitNormal.z; + out->m_flHitPointX = trace.m_vHitPoint.x; + out->m_flHitPointY = trace.m_vHitPoint.y; + out->m_flHitPointZ = trace.m_vHitPoint.z; + + out->m_flHitOffset = trace.m_flHitOffset; + out->m_flFraction = trace.m_flFraction; + out->m_nTriangle = trace.m_nTriangle; + out->m_nHitboxBoneIndex = trace.m_nHitboxBoneIndex; + out->m_eRayType = static_cast(trace.m_eRayType); + out->m_bStartInSolid = trace.m_bStartInSolid ? 1 : 0; + out->m_bExactHitPoint = trace.m_bExactHitPoint ? 1 : 0; +} + +static CTraceFilter BuildFilter(CEntityInstance* pIgnore, uint64_t interactsAs, uint64_t interactsWith, uint64_t interactsExclude) +{ + CEntityInstance* pOwner = nullptr; + uint16 nHierarchy = static_cast(0xFFFF); + + if (pIgnore) + { + static auto classKey = hash_32_fnv1a_const("CBaseEntity"); + static auto ownerKey = hash_32_fnv1a_const("m_hOwnerEntity"); + static auto collKey = hash_32_fnv1a_const("m_pCollision"); + static const auto ownerField = schema::GetOffset("CBaseEntity", classKey, "m_hOwnerEntity", ownerKey); + static const auto collField = schema::GetOffset("CBaseEntity", classKey, "m_pCollision", collKey); + + auto ownerHandle = *reinterpret_cast((uintptr_t)pIgnore + ownerField.offset); + pOwner = globals::entitySystem->GetEntityInstance(ownerHandle); + + auto* pColl = *reinterpret_cast((uintptr_t)pIgnore + collField.offset); + if (pColl) + { + static auto collClassKey = hash_32_fnv1a_const("CCollisionComponent"); + static auto attrKey = hash_32_fnv1a_const("m_collisionAttribute"); + static const auto attrField = schema::GetOffset("CCollisionComponent", collClassKey, "m_collisionAttribute", attrKey); + + // m_nHierarchyId is at byte 32 inside RnCollisionAttr_t (after 3×uint64 + 2×uint32) + nHierarchy = *reinterpret_cast(pColl + attrField.offset + 32); + } + } + + CTraceFilter filter(pIgnore, pOwner, nHierarchy, interactsWith, COLLISION_GROUP_DEFAULT, true); + filter.m_nInteractsAs = interactsAs; + filter.m_nInteractsExclude = interactsExclude; + return filter; +} + +static void NativeTraceShape(ScriptContext& script_context) +{ + auto* vecStart = script_context.GetArgument(0); + auto* angAngles = script_context.GetArgument(1); + auto* pIgnore = script_context.GetArgument(2); + uint64_t interactsAs = script_context.GetArgument(3); + uint64_t interactsWith = script_context.GetArgument(4); + uint64_t interactsExclude = script_context.GetArgument(5); + auto* out = script_context.GetArgument(6); + + Vector forward; + AngleVectors(*angAngles, &forward); + Vector vecEnd{ vecStart->x + forward.x * 8192.f, vecStart->y + forward.y * 8192.f, vecStart->z + forward.z * 8192.f }; + + CTraceFilter filter = BuildFilter(pIgnore, interactsAs, interactsWith, interactsExclude); + Ray_t ray; + CGameTrace trace; + INavPhysicsInterface::TraceShape(ray, *vecStart, vecEnd, &filter, &trace); + FillResult(out, trace); +} + +static void NativeTraceEndShape(ScriptContext& script_context) +{ + auto* vecStart = script_context.GetArgument(0); + auto* vecEnd = script_context.GetArgument(1); + auto* pIgnore = script_context.GetArgument(2); + uint64_t interactsAs = script_context.GetArgument(3); + uint64_t interactsWith = script_context.GetArgument(4); + uint64_t interactsExclude = script_context.GetArgument(5); + auto* out = script_context.GetArgument(6); + + CTraceFilter filter = BuildFilter(pIgnore, interactsAs, interactsWith, interactsExclude); + Ray_t ray; + CGameTrace trace; + INavPhysicsInterface::TraceShape(ray, *vecStart, *vecEnd, &filter, &trace); + FillResult(out, trace); +} + +static void NativeTraceHullShape(ScriptContext& script_context) +{ + auto* vecStart = script_context.GetArgument(0); + auto* vecEnd = script_context.GetArgument(1); + auto* vecMins = script_context.GetArgument(2); + auto* vecMaxs = script_context.GetArgument(3); + auto* pIgnore = script_context.GetArgument(4); + uint64_t interactsAs = script_context.GetArgument(5); + uint64_t interactsWith = script_context.GetArgument(6); + uint64_t interactsExclude = script_context.GetArgument(7); + auto* out = script_context.GetArgument(8); + + CTraceFilter filter = BuildFilter(pIgnore, interactsAs, interactsWith, interactsExclude); + Ray_t ray(*vecMins, *vecMaxs); + CGameTrace trace; + INavPhysicsInterface::TraceShape(ray, *vecStart, *vecEnd, &filter, &trace); + FillResult(out, trace); +} + +static uint64_t NativePointContents(ScriptContext& script_context) +{ + auto* vPos = script_context.GetArgument(0); + uint64_t contentsMask = script_context.GetArgument(1); + uint64_t result = INavPhysicsInterface::PointContents(vPos, contentsMask); + script_context.SetResult(result); + return 0; +} + +static bool NativeCheckAreaOverlappingEntity(ScriptContext& script_context) +{ + auto* rArea = script_context.GetArgument(0); + auto* rEntity = script_context.GetArgument(1); + auto bExtrudeHullHeight = script_context.GetArgument(2); + bool result = INavPhysicsInterface::CheckAreaOverlappingEntity(rArea, rEntity, bExtrudeHullHeight); + script_context.SetResult(result); + return 0; +} + +static void NativeGetEntityWorldSpaceAABB(ScriptContext& script_context) +{ + auto* rEntity = script_context.GetArgument(0); + auto* pMinsOut = script_context.GetArgument(1); + auto* pMaxsOut = script_context.GetArgument(2); + INavPhysicsInterface::GetEntityWorldSpaceAABB(rEntity, pMinsOut, pMaxsOut); +} + +REGISTER_NATIVES(raytrace, { + ScriptEngine::RegisterNativeHandler("TRACE_SHAPE", NativeTraceShape); + ScriptEngine::RegisterNativeHandler("TRACE_END_SHAPE", NativeTraceEndShape); + ScriptEngine::RegisterNativeHandler("TRACE_HULL_SHAPE", NativeTraceHullShape); + ScriptEngine::RegisterNativeHandler("POINT_CONTENTS", NativePointContents); + ScriptEngine::RegisterNativeHandler("CHECK_AREA_OVERLAPPING_ENTITY", NativeCheckAreaOverlappingEntity); + ScriptEngine::RegisterNativeHandler("GET_ENTITY_WORLD_SPACE_AABB", NativeGetEntityWorldSpaceAABB); +}) + +} // namespace counterstrikesharp diff --git a/src/scripting/natives/natives_raytrace.yaml b/src/scripting/natives/natives_raytrace.yaml new file mode 100644 index 000000000..205602376 --- /dev/null +++ b/src/scripting/natives/natives_raytrace.yaml @@ -0,0 +1,6 @@ +TRACE_SHAPE: startPos:pointer, angles:pointer, ignoreEntity:pointer, interactsAs:uint64, interactsWith:uint64, interactsExclude:uint64, outResult:pointer -> void +TRACE_END_SHAPE: startPos:pointer, endPos:pointer, ignoreEntity:pointer, interactsAs:uint64, interactsWith:uint64, interactsExclude:uint64, outResult:pointer -> void +TRACE_HULL_SHAPE: startPos:pointer, endPos:pointer, mins:pointer, maxs:pointer, ignoreEntity:pointer, interactsAs:uint64, interactsWith:uint64, interactsExclude:uint64, outResult:pointer -> void +POINT_CONTENTS: pos:pointer, contentsMask:uint64 -> uint64 +CHECK_AREA_OVERLAPPING_ENTITY: area:pointer, entity:pointer, extrudeHullHeight:bool -> bool +GET_ENTITY_WORLD_SPACE_AABB: entity:pointer, minsOut:pointer, maxsOut:pointer -> void From 3fad438fadaf9279a59c979db7436abf45556bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20P=C5=99ikryl?= Date: Tue, 2 Jun 2026 15:28:06 +0200 Subject: [PATCH 2/3] Update: make GetEntityWorldSpaceAABB out var instead of return (Vector, Vector) --- managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs index 193bb4671..2b037c4c8 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs @@ -140,11 +140,10 @@ public static bool CheckAreaOverlappingEntity(nint area, CBaseEntity entity, boo => NativeAPI.CheckAreaOverlappingEntity(area, entity.Handle, extrudeHullHeight); /// Returns the world-space axis-aligned bounding box of . - public static (Vector Mins, Vector Maxs) GetEntityWorldSpaceAABB(CBaseEntity entity) + public static void GetEntityWorldSpaceAABB(CBaseEntity entity, out Vector mins, out Vector maxs) { - var mins = new Vector(); - var maxs = new Vector(); + mins = new Vector(); + maxs = new Vector(); NativeAPI.GetEntityWorldSpaceAabb(entity.Handle, mins.Handle, maxs.Handle); - return (mins, maxs); } } From 43b8dacb4e2485134f23b858c711415b293dff51 Mon Sep 17 00:00:00 2001 From: roflmuffin Date: Fri, 5 Jun 2026 10:39:25 +0000 Subject: [PATCH 3/3] feat: add tests, move contents to be an enum --- .../Modules/Utils/Contents.cs | 134 ++++++++++++++++++ .../Modules/Utils/ContentsMask.cs | 122 ---------------- .../Modules/Utils/Trace.cs | 29 ++-- .../NativeTestsPlugin.csproj | 6 + .../RayTraceTests.cs | 110 ++++++++++++++ 5 files changed, 267 insertions(+), 134 deletions(-) create mode 100644 managed/CounterStrikeSharp.API/Modules/Utils/Contents.cs delete mode 100644 managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs create mode 100644 managed/CounterStrikeSharp.Tests.Native/RayTraceTests.cs diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Contents.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Contents.cs new file mode 100644 index 000000000..17c68f2c5 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Contents.cs @@ -0,0 +1,134 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . + */ + +namespace CounterStrikeSharp.API.Modules.Utils; + +/// +/// Contents flags for physics interaction layers (bspflags.h / const.h). +/// Used with and . +/// +[Flags] +public enum Contents : ulong +{ + Empty = 0ul, + + Solid = 1ul << 0, + Hitbox = 1ul << 1, + Trigger = 1ul << 2, + Sky = 1ul << 3, + + PlayerClip = 1ul << 4, + NpcClip = 1ul << 5, + BlockLos = 1ul << 6, + BlockLight = 1ul << 7, + Ladder = 1ul << 8, + Pickup = 1ul << 9, + BlockSound = 1ul << 10, + NoDraw = 1ul << 11, + Window = 1ul << 12, + PassBullets = 1ul << 13, + WorldGeometry = 1ul << 14, + Water = 1ul << 15, + Slime = 1ul << 16, + TouchAll = 1ul << 17, + Player = 1ul << 18, + Npc = 1ul << 19, + Debris = 1ul << 20, + PhysicsProp = 1ul << 21, + NavIgnore = 1ul << 22, + NavLocalIgnore = 1ul << 23, + PostProcessingVolume = 1ul << 24, + UnusedLayer3 = 1ul << 25, + CarriedObject = 1ul << 26, + Pushaway = 1ul << 27, + ServerEntityOnClient = 1ul << 28, + CarriedWeapon = 1ul << 29, + StaticLevel = 1ul << 30, + + CsgoTeam1 = 1ul << 31, + CsgoTeam2 = 1ul << 32, + CsgoGrenadeClip = 1ul << 33, + CsgoDroneClip = 1ul << 34, + CsgoMoveable = 1ul << 35, + CsgoOpaque = 1ul << 36, + CsgoMonster = 1ul << 37, + CsgoUnusedLayer = 1ul << 38, + CsgoThrownGrenade = 1ul << 39, +} + +/// +/// Composite trace masks (bspflags.h MASK_* defines). +/// +public static class Masks +{ + public const Contents All = (Contents)~0ul; + + /// Everything that is normally solid. + public const Contents Solid = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc | + Contents.PassBullets; + + /// Everything that blocks player movement. + public const Contents PlayerSolid = + Contents.Solid | Contents.PlayerClip | Contents.Window | Contents.Player | Contents.Npc | + Contents.PassBullets; + + /// Blocks NPC movement. + public const Contents NpcSolid = + Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc | + Contents.PassBullets; + + /// Blocks fluid movement. + public const Contents NpcFluid = + Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc; + + /// Water physics. + public const Contents Water = Contents.Water | Contents.Slime; + + /// Bullets see these as solid. + public const Contents Shot = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | + Contents.Debris | Contents.Hitbox; + + /// Bullets see these as solid, except monsters (world + brush only). + public const Contents ShotBrushOnly = Contents.Solid | Contents.Window | Contents.Debris; + + /// Non-raycasted weapons see this as solid (includes grates). + public const Contents ShotHull = + Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | Contents.Debris | + Contents.PassBullets; + + /// Hits solids (not grates) and passes through everything else. + public const Contents ShotPortal = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc; + + /// Everything normally solid, except monsters (world + brush only). + public const Contents SolidBrushOnly = Contents.Solid | Contents.Window | Contents.PassBullets; + + /// Everything normally solid for player movement, except monsters (world + brush only). + public const Contents PlayerSolidBrushOnly = + Contents.Solid | Contents.Window | Contents.PlayerClip | Contents.PassBullets; + + /// Everything normally solid for NPC movement, except monsters (world + brush only). + public const Contents NpcSolidBrushOnly = + Contents.Solid | Contents.Window | Contents.NpcClip | Contents.PassBullets; +} + +public enum RayType_t : uint +{ + RAY_TYPE_LINE = 0x0, + RAY_TYPE_SPHERE = 0x1, + RAY_TYPE_HULL = 0x2, + RAY_TYPE_CAPSULE = 0x3, + RAY_TYPE_MESH = 0x4 +}; diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs b/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs deleted file mode 100644 index d3aaae2f7..000000000 --- a/managed/CounterStrikeSharp.API/Modules/Utils/ContentsMask.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of CounterStrikeSharp. - * CounterStrikeSharp is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * CounterStrikeSharp is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CounterStrikeSharp. If not, see . - */ - -namespace CounterStrikeSharp.API.Modules.Utils; - -/// -/// Contents flags for physics interaction layers (bspflags.h / const.h). -/// Used with and . -/// -public static class Contents -{ - public const ulong Empty = 0ul; - - public const ulong Solid = 1ul << 0; - public const ulong Hitbox = 1ul << 1; - public const ulong Trigger = 1ul << 2; - public const ulong Sky = 1ul << 3; - - public const ulong PlayerClip = 1ul << 4; - public const ulong NpcClip = 1ul << 5; - public const ulong BlockLos = 1ul << 6; - public const ulong BlockLight = 1ul << 7; - public const ulong Ladder = 1ul << 8; - public const ulong Pickup = 1ul << 9; - public const ulong BlockSound = 1ul << 10; - public const ulong NoDraw = 1ul << 11; - public const ulong Window = 1ul << 12; - public const ulong PassBullets = 1ul << 13; - public const ulong WorldGeometry = 1ul << 14; - public const ulong Water = 1ul << 15; - public const ulong Slime = 1ul << 16; - public const ulong TouchAll = 1ul << 17; - public const ulong Player = 1ul << 18; - public const ulong Npc = 1ul << 19; - public const ulong Debris = 1ul << 20; - public const ulong PhysicsProp = 1ul << 21; - public const ulong NavIgnore = 1ul << 22; - public const ulong NavLocalIgnore = 1ul << 23; - public const ulong PostProcessingVolume = 1ul << 24; - public const ulong UnusedLayer3 = 1ul << 25; - public const ulong CarriedObject = 1ul << 26; - public const ulong Pushaway = 1ul << 27; - public const ulong ServerEntityOnClient = 1ul << 28; - public const ulong CarriedWeapon = 1ul << 29; - public const ulong StaticLevel = 1ul << 30; - - public const ulong CsgoTeam1 = 1ul << 31; - public const ulong CsgoTeam2 = 1ul << 32; - public const ulong CsgoGrenadeClip = 1ul << 33; - public const ulong CsgoDroneClip = 1ul << 34; - public const ulong CsgoMoveable = 1ul << 35; - public const ulong CsgoOpaque = 1ul << 36; - public const ulong CsgoMonster = 1ul << 37; - public const ulong CsgoUnusedLayer = 1ul << 38; - public const ulong CsgoThrownGrenade = 1ul << 39; -} - -/// -/// Composite trace masks (bspflags.h MASK_* defines). -/// -public static class Mask -{ - public const ulong All = ~0ul; - - /// Everything that is normally solid. - public const ulong Solid = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; - - /// Everything that blocks player movement. - public const ulong PlayerSolid = Contents.Solid | Contents.PlayerClip | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; - - /// Blocks NPC movement. - public const ulong NpcSolid = Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc | Contents.PassBullets; - - /// Blocks fluid movement. - public const ulong NpcFluid = Contents.Solid | Contents.NpcClip | Contents.Window | Contents.Player | Contents.Npc; - - /// Water physics. - public const ulong Water = Contents.Water | Contents.Slime; - - /// Bullets see these as solid. - public const ulong Shot = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | Contents.Debris | Contents.Hitbox; - - /// Bullets see these as solid, except monsters (world + brush only). - public const ulong ShotBrushOnly = Contents.Solid | Contents.Window | Contents.Debris; - - /// Non-raycasted weapons see this as solid (includes grates). - public const ulong ShotHull = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window | Contents.Debris | Contents.PassBullets; - - /// Hits solids (not grates) and passes through everything else. - public const ulong ShotPortal = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc; - - /// Everything normally solid, except monsters (world + brush only). - public const ulong SolidBrushOnly = Contents.Solid | Contents.Window | Contents.PassBullets; - - /// Everything normally solid for player movement, except monsters (world + brush only). - public const ulong PlayerSolidBrushOnly = Contents.Solid | Contents.Window | Contents.PlayerClip | Contents.PassBullets; - - /// Everything normally solid for NPC movement, except monsters (world + brush only). - public const ulong NpcSolidBrushOnly = Contents.Solid | Contents.Window | Contents.NpcClip | Contents.PassBullets; -} - -public enum RayType_t : uint -{ - RAY_TYPE_LINE = 0x0, - RAY_TYPE_SPHERE = 0x1, - RAY_TYPE_HULL = 0x2, - RAY_TYPE_CAPSULE = 0x3, - RAY_TYPE_MESH = 0x4 -}; diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs index 2b037c4c8..507cd339a 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Trace.cs @@ -23,13 +23,13 @@ namespace CounterStrikeSharp.API.Modules.Utils; public sealed class TraceOptions { /// Which interaction layers the trace represents (analogous to Source 1 contents). - public ulong InteractsAs { get; set; } = 0UL; // CONTENTS_EMPTY + public Contents InteractsAs { get; set; } = Contents.Empty; // CONTENTS_EMPTY /// Which interaction layers the trace collides with (analogous to Source 1 trace mask). - public ulong InteractsWith { get; set; } = ~0UL; // MASK_ALL + public Contents InteractsWith { get; set; } = Masks.All; // MASK_ALL /// Which interaction layers are explicitly excluded. - public ulong InteractsExclude { get; set; } = 0UL; // CONTENTS_EMPTY + public Contents InteractsExclude { get; set; } = Contents.Empty; // CONTENTS_EMPTY } /// @@ -65,7 +65,7 @@ public unsafe struct TraceResult public float Fraction => m_flFraction; public float HitOffset => m_flHitOffset; - public ulong Contents => m_nContents; + public Contents Contents => (Contents)m_nContents; public int TriangleIndex => m_nTriangle; public short HitboxBoneIndex => m_nHitboxBoneIndex; public RayType_t RayType => (RayType_t)m_eRayType; @@ -95,8 +95,8 @@ public unsafe struct TraceResult /// public static unsafe class Trace { - private static (ulong ia, ulong iw, ulong ie) Unpack(TraceOptions? opts) - => opts is null ? (0UL, ~0UL, 0UL) : (opts.InteractsAs, opts.InteractsWith, opts.InteractsExclude); + private static (Contents ia, Contents iw, Contents ie) Unpack(TraceOptions? opts) + => opts is null ? (0UL, (Contents)~0UL, 0UL) : (opts.InteractsAs, opts.InteractsWith, opts.InteractsExclude); /// /// Performs a line trace from in the direction of @@ -106,7 +106,8 @@ public static TraceResult TraceShape(Vector startPos, QAngle angles, CBaseEntity { var (ia, iw, ie) = Unpack(options); TraceResult result = default; - NativeAPI.TraceShape(startPos.Handle, angles.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + NativeAPI.TraceShape(startPos.Handle, angles.Handle, ignoreEntity?.Handle ?? nint.Zero, (ulong)ia, (ulong)iw, (ulong)ie, + (nint)(&result)); return result; } @@ -115,22 +116,26 @@ public static TraceResult TraceEndShape(Vector startPos, Vector endPos, CBaseEnt { var (ia, iw, ie) = Unpack(options); TraceResult result = default; - NativeAPI.TraceEndShape(startPos.Handle, endPos.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + NativeAPI.TraceEndShape(startPos.Handle, endPos.Handle, ignoreEntity?.Handle ?? nint.Zero, (ulong)ia, (ulong)iw, (ulong)ie, + (nint)(&result)); return result; } /// Performs a hull (box) trace between two world-space points. - public static TraceResult TraceHullShape(Vector startPos, Vector endPos, Vector mins, Vector maxs, CBaseEntity? ignoreEntity = null, TraceOptions? options = null) + public static TraceResult TraceHullShape(Vector startPos, Vector endPos, Vector mins, Vector maxs, CBaseEntity? ignoreEntity = null, + TraceOptions? options = null) { var (ia, iw, ie) = Unpack(options); TraceResult result = default; - NativeAPI.TraceHullShape(startPos.Handle, endPos.Handle, mins.Handle, maxs.Handle, ignoreEntity?.Handle ?? nint.Zero, ia, iw, ie, (nint)(&result)); + NativeAPI.TraceHullShape(startPos.Handle, endPos.Handle, mins.Handle, maxs.Handle, ignoreEntity?.Handle ?? nint.Zero, (ulong)ia, ( + ulong)iw, (ulong)ie, + (nint)(&result)); return result; } /// Returns the contents bitmask at the given world position. - public static ulong PointContents(Vector pos, ulong contentsMask = ~0UL) - => NativeAPI.PointContents(pos.Handle, contentsMask); + public static Contents PointContents(Vector pos, Contents contentsMask = Contents.Empty) + => (Contents)NativeAPI.PointContents(pos.Handle, (ulong)contentsMask); /// /// Returns whether a nav area overlaps with an entity's bounding box. diff --git a/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.csproj b/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.csproj index 2c9265659..df6c94b44 100644 --- a/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.csproj +++ b/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.csproj @@ -8,6 +8,12 @@ enable + + + false + + + diff --git a/managed/CounterStrikeSharp.Tests.Native/RayTraceTests.cs b/managed/CounterStrikeSharp.Tests.Native/RayTraceTests.cs new file mode 100644 index 000000000..f3e592f5d --- /dev/null +++ b/managed/CounterStrikeSharp.Tests.Native/RayTraceTests.cs @@ -0,0 +1,110 @@ +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using Xunit; +using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; + +namespace NativeTestsPlugin; + +public class RayTraceTests +{ + private CCSPlayerController player; + private CCSPlayerPawn pawn; + + public async Task InitializeAsync() + { + Server.ExecuteCommand("bot_kick; bot_quota 5; bot_quota_mode normal"); + await WaitOneFrame(); + this.player = Utilities.GetPlayers().Last(p => p.LifeState == (byte)LifeState_t.LIFE_ALIVE); + if (player.PlayerPawn.Value == null) + { + throw new Exception("No valid player pawn found for test player."); + } + + this.pawn = player.PlayerPawn.Value!; + } + + [Fact] + public async Task Trace_AABB() + { + await InitializeAsync(); + + Trace.GetEntityWorldSpaceAABB(pawn, out var mins, out var maxs); + + Assert.NotEqual(0, mins.X); + Assert.NotEqual(0, mins.Y); + Assert.NotEqual(0, mins.Z); + Assert.NotEqual(0, maxs.X); + Assert.NotEqual(0, maxs.Y); + Assert.NotEqual(0, maxs.Z); + Assert.Equal(72, maxs.Z - mins.Z, 4); // Player hull height is 72 units + } + + [Fact(Skip = "Ignore for now")] + public async Task Trace_PointContents() + { + await InitializeAsync(); + + var pos = pawn.AbsOrigin - new Vector(0, 0, 100); + var contentsMask = Trace.PointContents(pos, Masks.All); + Assert.NotEqual(0ul, (ulong)contentsMask); + Assert.True(contentsMask.HasFlag(Contents.Solid) || contentsMask.HasFlag(Contents.PlayerClip)); + } + + [Fact] + public async Task Trace_TraceShape() + { + await InitializeAsync(); + + var start = pawn.AbsOrigin; + var angles = new QAngle(0, -90, 0); + var result = Trace.TraceShape(start, angles, ignoreEntity: pawn, + options: new TraceOptions { InteractsWith = Masks.SolidBrushOnly, InteractsExclude = Contents.Pickup }); + Assert.True(result.DidHit()); + Assert.NotEqual(0ul, (ulong)result.Contents); + Assert.True(result.Contents.HasFlag(Contents.Solid)); + Assert.True(result.Contents.HasFlag(Contents.StaticLevel)); + Assert.NotEqual(Vector.Zero, result.HitPoint); + Assert.Equal(RayType_t.RAY_TYPE_LINE, result.RayType); + Assert.Equal("worldent", result.HitEntity().DesignerName); + } + + [Fact] + public async Task Trace_HullShape() + { + await InitializeAsync(); + + var start = pawn.AbsOrigin; + var end = start + new Vector(0, 0, -100); + var result = Trace.TraceHullShape(start, end, new Vector(0, 0, 0), new Vector(32, 32, 72), ignoreEntity: pawn, + options: new TraceOptions { InteractsWith = Masks.SolidBrushOnly, InteractsExclude = Contents.Pickup }); + Assert.True(result.DidHit()); + Assert.NotEqual(0ul, (ulong)result.Contents); + Assert.True(result.Contents.HasFlag(Contents.Solid)); + Assert.True(result.Contents.HasFlag(Contents.StaticLevel)); + Assert.NotEqual(Vector.Zero, result.HitPoint); + Assert.Equal(RayType_t.RAY_TYPE_HULL, result.RayType); + Assert.Equal("worldent", result.HitEntity().DesignerName); + } + + [Fact] + public async Task Trace_EndShape() + { + await InitializeAsync(); + + var start = pawn.AbsOrigin; + var end = start + new Vector(0, 0, -100); + var result = Trace.TraceEndShape(start, end, ignoreEntity: pawn, + options: new TraceOptions { InteractsWith = Masks.SolidBrushOnly, InteractsExclude = Contents.Pickup }); + Assert.True(result.DidHit()); + Assert.NotEqual(0ul, (ulong)result.Contents); + Assert.True(result.Contents.HasFlag(Contents.Solid)); + Assert.True(result.Contents.HasFlag(Contents.StaticLevel)); + Assert.NotEqual(Vector.Zero, result.HitPoint); + Assert.Equal(RayType_t.RAY_TYPE_LINE, result.RayType); + Assert.Equal("worldent", result.HitEntity().DesignerName); + } +} \ No newline at end of file