diff --git a/README.md b/README.md index 089182c..22640b9 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,6 @@ bool LoadRayTrace() ### C# (CounterStrikeSharp plugin) -There are **two ways** to use the managed side: - -1. `public/Example.cs` (reference implementation, for learning / custom - binding) -2. **Official release packages (recommended)** - The recommended way is to use the prebuilt release components: - Install **RayTraceImpl** as a CS# plugin: @@ -138,9 +132,6 @@ The recommended way is to use the prebuilt release components: Then reference `RayTraceApi.dll` in your own plugin project and call the API normally. -`public/Example.cs` demonstrates manual binding and struct layout, but -in production you should rely on the release bridge + shared API. - ------------------------------------------------------------------------ ## Calling methods from C++ (Metamod) @@ -242,7 +233,6 @@ Due to C++ ABI differences: | Linux (Itanium ABI) | 2 | 3 | 4 | | Windows (MSVC ABI) | 1 | 2 | 3 | -`public/Example.cs` shows how offsets are applied manually.\ Release builds handle this automatically inside RayTraceImpl. ------------------------------------------------------------------------ diff --git a/managed/RayTrace/RayTraceApi/RayTraceApi.cs b/managed/RayTrace/RayTraceApi/RayTraceApi.cs index 44ee6b5..ceb755f 100644 --- a/managed/RayTrace/RayTraceApi/RayTraceApi.cs +++ b/managed/RayTrace/RayTraceApi/RayTraceApi.cs @@ -2,6 +2,7 @@ using System.Runtime.InteropServices; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; @@ -87,24 +88,281 @@ namespace RayTraceAPI DrawBeam = drawBeam ? 1 : 0; } } + + [StructLayout(LayoutKind.Sequential, Size = 8)] + public struct CUtlString + { + public nint _ptr; // make public so Marshal sees it - [StructLayout(LayoutKind.Explicit, Size = 48)] + public string Value + { + get + { + if (_ptr == 0 || _ptr == IntPtr.MaxValue) return string.Empty; + return Marshal.PtrToStringUTF8(_ptr)!; // read UTF8 string from native pointer + } + } + + public static implicit operator string(CUtlString s) => s.Value; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CPhysSurfacePropertiesTrace + { + public CUtlString Name; + public uint NameHash; + public uint BaseNameHash; + public int ListIndex; + public int BaseListIndex; + [MarshalAs(UnmanagedType.I1)] + public bool Hidden; + public CUtlString Description; + public CPhysSurfacePropertiesPhysicsTrace Physics; + public CPhysSurfacePropertiesSoundNamesTrace AudioSounds; + public CPhysSurfacePropertiesAudioTrace AudioParams; + } + + public enum CollisionFunctionMask_t : byte + { + FCOLLISION_FUNC_ENABLE_SOLID_CONTACT = (1 << 0), + FCOLLISION_FUNC_ENABLE_TRACE_QUERY = (1 << 1), + FCOLLISION_FUNC_ENABLE_TOUCH_EVENT = (1 << 2), + FCOLLISION_FUNC_ENABLE_SELF_COLLISIONS = (1 << 3), + FCOLLISION_FUNC_IGNORE_FOR_HITBOX_TEST = (1 << 4), + FCOLLISION_FUNC_ENABLE_TOUCH_PERSISTS = (1 << 5), + } + + [StructLayout(LayoutKind.Sequential)] + public struct RnCollisionAttr_t + { + public ulong InteractsAs; + public ulong InteractsWith; + public ulong InteractsExclude; + public uint EntityId; + public uint OwnerId; + public ushort HierarchyId; + public CollisionGroup CollisionGroup; + public CollisionFunctionMask_t CollisionFunctionMask; + } + + public enum RayType_t : byte + { + RAY_TYPE_LINE = 0, + RAY_TYPE_SPHERE, + RAY_TYPE_HULL, + RAY_TYPE_CAPSULE, + RAY_TYPE_MESH, + } + + [StructLayout(LayoutKind.Sequential)] + public struct CPhysSurfacePropertiesPhysicsTrace + { + public float Friction; + public float Elasticity; + public float Density; + public float Thickness; + public float SoftContactFrequency; + public float SoftContactDampingRatio; + public float WheelDrag; + public float HeatConductivity; + public float Flashpoint; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CPhysSurfacePropertiesSoundNamesTrace + { + public CUtlString ImpactSoft; + public CUtlString ImpactHard; + public CUtlString ScrapeSmooth; + public CUtlString ScrapeRough; + public CUtlString BulletImpact; + public CUtlString Rolling; + public CUtlString Break; + public CUtlString Strain; + public CUtlString MeleeImpact; + public CUtlString PushOff; + public CUtlString SkidStop; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CPhysSurfacePropertiesAudioTrace + { + public float Reflectivity; + public float HardnessFactor; + public float RoughnessFactor; + public float RoughThreshold; + public float HardThreshold; + public float HardVelocityThreshold; + public float StaticImpactVolume; + public float OcclusionFactor; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CUtlStringToken : IEquatable + { + private uint _hashCode; + + public CUtlStringToken(uint hashCode) + { + _hashCode = hashCode; + } + + public bool IsValid => _hashCode != 0; + + public uint GetHashCodeValue() => _hashCode; + + public void SetHashCode(uint hash) => _hashCode = hash; + + public bool Equals(CUtlStringToken other) + => _hashCode == other._hashCode; + + public override bool Equals(object? obj) + => obj is CUtlStringToken other && Equals(other); + + public override int GetHashCode() + => (int)_hashCode; + + public static bool operator ==(CUtlStringToken a, CUtlStringToken b) + => a._hashCode == b._hashCode; + + public static bool operator !=(CUtlStringToken a, CUtlStringToken b) + => a._hashCode != b._hashCode; + + public static bool operator <(CUtlStringToken a, CUtlStringToken b) + => a._hashCode < b._hashCode; + + public static bool operator >(CUtlStringToken a, CUtlStringToken b) + => a._hashCode > b._hashCode; + + public override string ToString() + => $"0x{_hashCode:X8}"; + } + + + [StructLayout(LayoutKind.Sequential)] + public struct CHitBox + { + public CUtlString m_name; // pointer to CUtlString + public CUtlString m_sSurfaceProperty; // pointer to CUtlString + public CUtlString m_sBoneName; // pointer to CUtlString + + public Vector3 m_vMinBounds; // blittable + public Vector3 m_vMaxBounds; // blittable + public float m_flShapeRadius; + + public CUtlStringToken m_nBoneNameHash; // pointer or uint + + public byte m_nShapeType; + public bool m_bTranslationOnly; + public uint m_CRC; + public uint m_cRenderColor; + public ushort m_nHitBoxIndex; + public bool m_bForcedTransform; + + public CTransform m_forcedTransform; // only if blittable (no managed types inside) + } + + [StructLayout(LayoutKind.Sequential, Pack = 16)] + public struct CTransform + { + public Vector4 Position; // VectorAligned: use Vector4 for 16-byte alignment + public System.Numerics.Quaternion Orientation; + + public CTransform(Vector3 position, System.Numerics.Quaternion orientation) + { + Position = new Vector4(position, 1.0f); // w = 1.0f like vec3_origin.w + Orientation = orientation; + } + + public bool IsValid() + { + return !Position.Equals(Vector4.Zero) && Orientation != System.Numerics.Quaternion.Identity; + } + + public void SetToIdentity() + { + Position = new Vector4(0, 0, 0, 1); // vec3_origin + w = 1 + Orientation = System.Numerics.Quaternion.Identity; // quat_identity + } + + public static bool operator ==(CTransform a, CTransform b) + { + return a.Position == b.Position && a.Orientation == b.Orientation; + } + + public static bool operator !=(CTransform a, CTransform b) + { + return !(a == b); + } + + public override bool Equals(object? obj) + { + return obj is CTransform t && this == t; + } + + public override int GetHashCode() + { + return HashCode.Combine(Position, Orientation); + } + + public override string ToString() + { + return $"CTransform(Position: {Position}, Orientation: {Orientation})"; + } + } + + [StructLayout(LayoutKind.Explicit, Size = 208)] public struct TraceResult { - [FieldOffset(0)] public float EndPosX; - [FieldOffset(4)] public float EndPosY; - [FieldOffset(8)] public float EndPosZ; - [FieldOffset(16)] public nint HitEntity; - [FieldOffset(24)] public float Fraction; - [FieldOffset(28)] public int AllSolid; - [FieldOffset(32)] public float NormalX; - [FieldOffset(36)] public float NormalY; - [FieldOffset(40)] public float NormalZ; + [FieldOffset(0)] public float StartPosX; + [FieldOffset(4)] public float StartPosY; + [FieldOffset(8)] public float StartPosZ; + [FieldOffset(12)] public float EndPosX; + [FieldOffset(16)] public float EndPosY; + [FieldOffset(20)] public float EndPosZ; + [FieldOffset(24)] public float HitPointX; + [FieldOffset(28)] public float HitPointY; + [FieldOffset(32)] public float HitPointZ; + [FieldOffset(36)] public float NormalX; + [FieldOffset(40)] public float NormalY; + [FieldOffset(44)] public float NormalZ; + [FieldOffset(48)] public float Fraction; + [FieldOffset(52)] public float HitOffset; - public Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ); - public Vector3 Normal => new(NormalX, NormalY, NormalZ); - public bool DidHit => Fraction < 1.0f; - public bool IsAllSolid => AllSolid != 0; + [FieldOffset(56)] public int TriangleIndex; + [FieldOffset(60)] public int HitboxBoneIndex; + [FieldOffset(64)] public int Contents; + [FieldOffset(68)] public int RayType; + [FieldOffset(72)] public int AllSolid; + [FieldOffset(76)] public int ExactHitPoint; + + [FieldOffset(80)] public nint HitEntityHandle; + [FieldOffset(88)] public nint HitboxHandle; + [FieldOffset(96)] public nint SurfacePropsHandle; + [FieldOffset(104)] public nint BodyHandle; + [FieldOffset(112)] public nint ShapeHandle; + + + [FieldOffset(128)] public CTransform BodyTransform; + [FieldOffset(160)] public RnCollisionAttr_t ShapeAttributes; + + public Vector3 StartPos => new(StartPosX, StartPosY, StartPosZ); + public Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ); + public Vector3 HitPoint => new(HitPointX, HitPointY, HitPointZ); + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + + public bool DidHit => Fraction < 1.0f; + public bool IsAllSolid => AllSolid != 0; + public bool HasExactHit => ExactHitPoint != 0; + + public CEntityInstance? HitEntity + => HitEntityHandle == 0 ? null : new CEntityInstance(HitEntityHandle); + + public CHitBox Hitbox + => HitboxHandle == 0 ? default : Marshal.PtrToStructure(HitboxHandle); + + public CPhysSurfacePropertiesTrace SurfaceProps + => SurfacePropsHandle == 0 ? default : Marshal.PtrToStructure(SurfacePropsHandle); } #endregion diff --git a/public/CRayTraceInterface.h b/public/CRayTraceInterface.h index 83fa242..b81c5c1 100644 --- a/public/CRayTraceInterface.h +++ b/public/CRayTraceInterface.h @@ -144,15 +144,36 @@ struct TraceOptions struct TraceResult { + float StartPosX; + float StartPosY; + float StartPosZ; float EndPosX; float EndPosY; float EndPosZ; - CEntityInstance* HitEntity; - float Fraction; - int AllSolid; + float HitPointX; + float HitPointY; + float HitPointZ; float NormalX; float NormalY; float NormalZ; + float Fraction; + float HitOffset; + + int TriangleIndex; + int HitboxBoneIndex; + int Contents; + int RayType; + int AllSolid; + int ExactHitPoint; + + CEntityInstance *HitEntity; + CHitBox *Hitbox; + CPhysSurfaceProperties *SurfaceProps; + IPhysicsBody *BodyHandle; + IPhysicsShape *ShapeHandle; + + CTransform BodyTransform; + RnCollisionAttr_t ShapeAttributes; }; class CRayTraceInterface diff --git a/public/Example.cs b/public/Example.cs deleted file mode 100644 index 43edaf0..0000000 --- a/public/Example.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Numerics; -using System.Runtime.InteropServices; -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Utils; -using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; - -namespace RayTrace -{ -#region Native Structs (matching C++ layout exactly) - [Flags] - public enum InteractionLayers: ulong - { - Solid = 0x1, - Hitboxes = 0x2, - Trigger = 0x4, - Sky = 0x8, - PlayerClip = 0x10, - NPCClip = 0x20, - BlockLOS = 0x40, - BlockLight = 0x80, - Ladder = 0x100, - Pickup = 0x200, - BlockSound = 0x400, - NoDraw = 0x800, - Window = 0x1000, - PassBullets = 0x2000, - WorldGeometry = 0x4000, - Water = 0x8000, - Slime = 0x10000, - TouchAll = 0x20000, - Player = 0x40000, - NPC = 0x80000, - Debris = 0x100000, - Physics_Prop = 0x200000, - NavIgnore = 0x400000, - NavLocalIgnore = 0x800000, - PostProcessingVolume = 0x1000000, - UnusedLayer3 = 0x2000000, - CarriedObject = 0x4000000, - PushAway = 0x8000000, - ServerEntityOnClient = 0x10000000, - CarriedWeapon = 0x20000000, - StaticLevel = 0x40000000, - csgo_team1 = 0x80000000, - csgo_team2 = 0x100000000, - csgo_grenadeclip = 0x200000000, - csgo_droneclip = 0x400000000, - csgo_moveable = 0x800000000, - csgo_opaque = 0x1000000000, - csgo_monster = 0x2000000000, - csgo_thrown_grenade = 0x8000000000, - - MASK_SHOT_PHYSICS = Solid | PlayerClip | Window | PassBullets | Player | NPC | Physics_Prop, - MASK_SHOT_HITBOX = Hitboxes | Player | NPC, - MASK_SHOT_FULL = MASK_SHOT_PHYSICS | Hitboxes, - MASK_WORLD_ONLY = Solid | Window | PassBullets, - MASK_GRENADE = Solid | Window | Physics_Prop | PassBullets, - MASK_BRUSH_ONLY = Solid | Window, - MASK_PLAYER_MOVE = Solid | Window | PlayerClip | PassBullets, - MASK_NPC_MOVE = Solid | Window | NPCClip | PassBullets - } - - [StructLayout(LayoutKind.Explicit, Size = 32)] - public struct TraceOptions - { - [FieldOffset(0)] public ulong InteractsAs; - [FieldOffset(8)] public ulong InteractsWith; - [FieldOffset(16)] public ulong InteractsExclude; - [FieldOffset(24)] public int DrawBeam; - - public TraceOptions() - { - InteractsAs = 0; - InteractsWith = (ulong)InteractionLayers.MASK_SHOT_PHYSICS; - InteractsExclude = 0; - DrawBeam = 0; - } - - public TraceOptions(InteractionLayers interactsAs, InteractionLayers interactsWith, InteractionLayers interactsExclude = 0, bool drawBeam = false) - { - InteractsAs = (ulong)interactsAs; - InteractsWith = (ulong)interactsWith; - InteractsExclude = (ulong)interactsExclude; - DrawBeam = drawBeam ? 1 : 0; - } - } - - [StructLayout(LayoutKind.Explicit, Size = 48)] - public struct TraceResult - { - [FieldOffset(0)] public float EndPosX; - [FieldOffset(4)] public float EndPosY; - [FieldOffset(8)] public float EndPosZ; - [FieldOffset(16)] public nint HitEntity; - [FieldOffset(24)] public float Fraction; - [FieldOffset(28)] public int AllSolid; - [FieldOffset(32)] public float NormalX; - [FieldOffset(36)] public float NormalY; - [FieldOffset(40)] public float NormalZ; - - public Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ); - public Vector3 Normal => new(NormalX, NormalY, NormalZ); - public bool DidHit => Fraction < 1.0f; - public bool IsAllSolid => AllSolid != 0; - } -#endregion - - public static class CRayTrace - { - private static nint g_pRayTraceHandle = nint.Zero; - private static bool g_bRayTraceLoaded = false; - - private static Func? _traceShape; - private static Func? _traceEndShape; - private static Func? _traceHullShape; - - public static void Init() - { - g_pRayTraceHandle = (nint)Utilities.MetaFactory("CRayTraceInterface001")!; - - if (g_pRayTraceHandle == nint.Zero) - throw new Exception("Failed to get Ray-Trace interface handle. Is Ray-Trace MetaMod module loaded?"); - - Bind(); - g_bRayTraceLoaded = true; - } - - private static void Bind() - { - int traceShapeIndex = 2; - int traceEndShapeIndex = 3; - int traceHullShapeIndex = 4; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - traceShapeIndex = 1; - traceEndShapeIndex = 2; - traceHullShapeIndex = 3; - } - - _traceShape = VirtualFunction.Create(g_pRayTraceHandle, traceShapeIndex); - _traceEndShape = VirtualFunction.Create(g_pRayTraceHandle, traceEndShapeIndex); - _traceHullShape = VirtualFunction.Create(g_pRayTraceHandle, traceHullShapeIndex); - } - - public static unsafe bool TraceShape(Vector origin, QAngle angles, CBaseEntity? ignoreEntity, TraceOptions options, out TraceResult result) - { - result = default; - - if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) - return false; - - TraceResult resultBuffer = default; - TraceOptions optionsBuffer = options; - - bool success = _traceShape!(g_pRayTraceHandle, - origin.Handle, - angles.Handle, - ignoreEntity?.Handle ?? nint.Zero, - (nint)(&optionsBuffer), - (nint)(&resultBuffer)); - - result = resultBuffer; - return success; - } - - public static unsafe bool TraceEndShape(Vector origin, Vector endOrigin, CBaseEntity? ignoreEntity, TraceOptions options, out TraceResult result) - { - result = default; - - if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) - return false; - - TraceResult resultBuffer = default; - TraceOptions optionsBuffer = options; - - bool success = _traceEndShape!(g_pRayTraceHandle, - origin.Handle, - endOrigin.Handle, - ignoreEntity?.Handle ?? nint.Zero, - (nint)(&optionsBuffer), - (nint)(&resultBuffer)); - - result = resultBuffer; - return success; - } - - public static unsafe bool TraceHullShape(Vector vecStart, Vector vecEnd, Vector hullMins, Vector hullMaxs, CBaseEntity? ignoreEntity, TraceOptions options, out TraceResult result) - { - result = default; - - if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) - return false; - - TraceResult resultBuffer = default; - TraceOptions optionsBuffer = options; - - bool success = _traceHullShape!(g_pRayTraceHandle, - vecStart.Handle, - vecEnd.Handle, - hullMins.Handle, - hullMaxs.Handle, - ignoreEntity?.Handle ?? nint.Zero, - (nint)(&optionsBuffer), - (nint)(&resultBuffer)); - - result = resultBuffer; - return success; - } - } -} diff --git a/src/Plugin.cpp b/src/Plugin.cpp index 56e86d7..af3ecfb 100644 --- a/src/Plugin.cpp +++ b/src/Plugin.cpp @@ -119,7 +119,7 @@ namespace RayTracePlugin return nullptr; } - const char* IPlugin::GetAuthor() { return "Slynx"; } + const char* IPlugin::GetAuthor() { return "Slynx, contributors"; } const char* IPlugin::GetName() { return "RayTrace"; } const char* IPlugin::GetDescription() { return "RayTrace Metamod plugin for CS2 servers."; } const char* IPlugin::GetURL() { return "https://slynxdev.cz"; } diff --git a/src/RayTrace.cpp b/src/RayTrace.cpp index 1ff2944..6fba6bd 100644 --- a/src/RayTrace.cpp +++ b/src/RayTrace.cpp @@ -73,15 +73,35 @@ namespace RayTracePlugin::RayTrace &filterInc, &tr); TraceResult r{}; + r.StartPosX = tr.m_vStartPos.x; + r.StartPosY = tr.m_vStartPos.y; + r.StartPosZ = tr.m_vStartPos.z; r.EndPosX = tr.m_vEndPos.x; r.EndPosY = tr.m_vEndPos.y; r.EndPosZ = tr.m_vEndPos.z; - r.HitEntity = tr.m_pEnt; - r.Fraction = tr.m_flFraction; - r.AllSolid = tr.m_bStartInSolid; + r.HitPointX = tr.m_vHitPoint.x; + r.HitPointY = tr.m_vHitPoint.y; + r.HitPointZ = tr.m_vHitPoint.z; r.NormalX = tr.m_vHitNormal.x; r.NormalY = tr.m_vHitNormal.y; r.NormalZ = tr.m_vHitNormal.z; + r.Fraction = tr.m_flFraction; + r.HitOffset = tr.m_flHitOffset; + + r.TriangleIndex = tr.m_nTriangle; + r.HitboxBoneIndex = tr.m_nHitboxBoneIndex; + r.Contents = tr.m_nContents; + r.RayType = static_cast(tr.m_eRayType); + r.AllSolid = tr.m_bStartInSolid; + r.ExactHitPoint = tr.m_bExactHitPoint; + + r.HitEntity = tr.m_pEnt; + r.Hitbox = const_cast(tr.m_pHitbox); + r.SurfaceProps = const_cast(tr.m_pSurfaceProperties); + r.BodyHandle = tr.m_hBody; + r.ShapeHandle = tr.m_hShape; + r.BodyTransform = tr.m_BodyTransform; + r.ShapeAttributes = tr.m_ShapeAttributes; return r; }