Update readme

This commit is contained in:
SlynxCZ 2026-01-26 21:29:09 +04:00
parent 98f00073ab
commit 65a480f505
4 changed files with 299 additions and 352 deletions

526
README.md
View file

@ -1,400 +1,315 @@
# Ray-Trace # Ray-Trace
**Shared ray tracing interface for Metamod:source & CounterStrikeSharp **Shared ray tracing interface for Metamod:Source & CounterStrikeSharp plugins**
plugins**
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Overview ## Overview
`Ray-Trace` is a lightweight **Metamod interface module** for\ `Ray-Trace` is a lightweight **Metamod interface module** for
**Counter-Strike 2** servers. **Counter-Strike 2** servers.
It exposes a shared interface: It exposes a shared interface: `CRayTraceInterface001` which can be consumed from:
CRayTraceInterface001 - Native **Metamod C++ plugins**
- Managed **CounterStrikeSharp C# plugins**
which can be consumed from: The interface is implemented as a **C++ virtual class** and is accessed
from C# by calling its **vtable functions directly** using a native handle.
- Native **Metamod C++ plugins** The goal is to provide a **single tracing backend** usable from both worlds
- Managed **CounterStrikeSharp C# plugins**
The goal is to provide a **single tracing backend** usable from both
worlds\
without duplicating engine detours. without duplicating engine detours.
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Features ## Features
- Metamod meta interface (`CRayTraceInterface001`) - Metamod meta interface (`CRayTraceInterface001`)
- Works in **C++ and C#** - Works in **C++ and C#**
- Physics / hitbox / world trace presets - Physics / hitbox / world trace presets
- Custom collision masks - Custom collision masks
- Optional debug beam rendering - Optional debug beam rendering
- Zero configuration - Zero configuration
- Ultra low overhead - Ultra low overhead
- ABI-safe (no STL types in interface)
- Virtual destructor for safe lifetime management
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Exposed Interface ## Exposed Interface (C++)
``` cpp ```cpp
class CRayTraceInterface class CRayTraceInterface
{ {
public: public:
virtual ~CRayTraceInterface() = default; virtual ~CRayTraceInterface() = default;
virtual std::optional<TraceResult> TraceShape( virtual bool TraceShape(
const Vector& origin, const Vector* pOrigin,
const QAngle& viewangles, const QAngle* pViewAngles,
CBaseEntity* ignorePlayer = nullptr, CBaseEntity* pIgnorePlayer,
const TraceOptions* opts = nullptr) = 0; const TraceOptions* pOpts,
TraceResult* pOutResult
) = 0;
virtual std::optional<TraceResult> TraceEndShape( virtual bool TraceEndShape(
const Vector& origin, const Vector* pOrigin,
const Vector& endOrigin, const Vector* pEndOrigin,
CBaseEntity* ignorePlayer = nullptr, CBaseEntity* pIgnorePlayer,
const TraceOptions* opts = nullptr) = 0; const TraceOptions* pOpts,
TraceResult* pOutResult
) = 0;
virtual std::optional<TraceResult> TraceShapeEx( virtual bool TraceShapeEx(
const Vector& vecStart, const Vector* pVecStart,
const Vector& vecEnd, const Vector* pVecEnd,
CTraceFilter& filterInc, CTraceFilter* pFilterInc,
Ray_t rayInc) = 0; Ray_t rayInc,
TraceResult* pOutResult
) = 0;
}; };
``` ```
**Return value:**
- true → trace hit something, TraceResult is valid
- false → no hit
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Getting the interface ## Getting the interface
**C++ (Metamod plugin)**
### C++ (Metamod plugin)
``` cpp
int ret = 0;
auto* rayTraceIface = static_cast<CRayTraceInterface*>(
g_SMAPI->MetaFactory("CRayTraceInterface001", &ret, nullptr)
);
if (ret == META_IFACE_FAILED || !rayTraceIface)
{
FP_ERROR("Failed to lookup Ray-Trace interface!");
}
```
### C# (CounterStrikeSharp plugin)
``` csharp
private nint _handle;
public override void Load(bool hotReload)
{
_handle = Utilities.MetaFactory("CRayTraceInterface001");
if (_handle == nint.Zero)
{
throw new Exception("Failed to get Ray-Trace interface handle");
}
}
```
------------------------------------------------------------------------
## Calling methods from C++ (Metamod)
On the native side you simply call the interface methods directly
(no vtable binding required).
### Basic usage
```cpp ```cpp
CRayTraceInterface* g_RayTrace = nullptr; CRayTraceInterface* g_pRayTrace = nullptr;
bool g_bRayTraceLoaded = false;
bool InitRayTrace() bool LoadRayTrace()
{ {
int ret = 0; int iRet = 0;
g_RayTrace = static_cast<CRayTraceInterface*>( g_pRayTrace = static_cast<CRayTraceInterface*>(
g_SMAPI->MetaFactory("CRayTraceInterface001", &ret, nullptr) g_SMAPI->MetaFactory("CRayTraceInterface001", &iRet, nullptr)
); );
if (ret == META_IFACE_FAILED || !g_RayTrace) if (iRet == META_IFACE_FAILED || !g_pRayTrace)
{ {
FP_ERROR("Failed to lookup Ray-Trace interface!"); FP_ERROR("Failed to lookup Ray-Trace interface!");
return false; return false;
} }
g_bRayTraceLoaded = true;
return true; return true;
} }
``` ```
--- **C# (CounterStrikeSharp plugin)**
```csharp
private nint g_pRayTraceHandle = nint.Zero;
private bool g_bRayTraceLoaded = false;
### Calling TraceShape public override void Load(bool hotReload)
```cpp
TraceOptions opts;
opts.InteractsWith = MASK_SHOT_FULL;
opts.DrawBeam = true;
auto result = g_RayTrace->TraceShape(
origin,
viewAngles,
ignorePlayer,
&opts
);
if (result.has_value())
{ {
auto& tr = result.value(); g_pRayTraceHandle = Utilities.MetaFactory("CRayTraceInterface001");
Msg("Hit fraction: %f\n", tr.Fraction); if (g_pRayTraceHandle == nint.Zero)
Msg("End pos: %f %f %f\n", {
tr.EndPos.x, throw new Exception("Failed to get Ray-Trace interface handle");
tr.EndPos.y, }
tr.EndPos.z);
Bind();
g_bRayTraceLoaded = true;
}
```
The returned handle is a pointer to the native CRayTraceInterface object.
------------------------------------------------------------------------
## Calling methods from C++ (Metamod)
**TraceShape example**
```cpp
Vector vecOrigin{};
QAngle angView{};
TraceOptions traceOpts{};
traceOpts.InteractsWith = static_cast<uint64_t>(MASK_SHOT_FULL);
traceOpts.DrawBeam = 1;
TraceResult traceResult{};
if (g_pRayTrace && g_bRayTraceLoaded)
{
bool bHit = g_pRayTrace->TraceShape(
&vecOrigin,
&angView,
nullptr,
&traceOpts,
&traceResult
);
if (bHit)
{
Msg("Hit fraction: %f\n", traceResult.Fraction);
Msg("End pos: %f %f %f\n",
traceResult.EndPos.x,
traceResult.EndPos.y,
traceResult.EndPos.z);
}
} }
``` ```
--- **TraceEndShape example**
### Calling TraceEndShape
```cpp ```cpp
auto result = g_RayTrace->TraceEndShape( TraceResult traceResult{};
startPos,
endPos, bool bHit = g_pRayTrace->TraceEndShape(
ignorePlayer, &vecStartPos,
&opts &vecEndPos,
nullptr,
&traceOpts,
&traceResult
); );
``` ```
--- **TraceShapeEx (low-level)**
### Calling TraceShapeEx (low-level)
```cpp ```cpp
Ray_t ray; Ray_t ray{};
CTraceFilter filter( CTraceFilter filter(
static_cast<uint64_t>(MASK_SHOT_FULL), static_cast<uint64_t>(MASK_SHOT_FULL),
COLLISION_GROUP_DEFAULT, COLLISION_GROUP_DEFAULT,
true true
); );
auto result = g_RayTrace->TraceShapeEx( TraceResult traceResult{};
startPos,
endPos, bool bHit = g_pRayTrace->TraceShapeEx(
filter, &vecStartPos,
ray &vecEndPos,
&filter,
ray,
&traceResult
); );
``` ```
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Calling methods from C# (CounterStrikeSharp plugin) ## Calling methods from C# (CounterStrikeSharp plugin)
```csharp
private delegate bool TraceShapeFn(
nint pThis,
nint pOrigin,
nint pAngles,
nint pIgnoreEntity,
nint pOptions,
nint pOutResult
);
``` csharp private TraceShapeFn? _traceShape;
private Func<nint, nint, nint, nint, nint, nint>? _traceShape; private TraceShapeFn? _traceEndShape;
private Func<nint, nint, nint, nint, nint, nint>? _traceEndShape; private TraceShapeFn? _traceShapeEx;
private Func<nint, nint, nint, nint, nint, nint>? _traceShapeEx;
private void Bind() private void Bind()
{ {
// index 1 - TraceShape _traceShape = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 1);
_traceShape = VirtualFunction.Create< _traceEndShape = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 2);
nint, // this _traceShapeEx = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 3);
nint, // Vector*
nint, // QAngle*
nint, // ignore entity
nint, // TraceOptions*
nint
>(_handle, 1);
// index 2 - TraceEndShape
_traceEndShape = VirtualFunction.Create<
nint, // this
nint, // start Vector*
nint, // end Vector*
nint, // ignore entity
nint, // TraceOptions*
nint
>(_handle, 2);
// index 3 - TraceShapeEx
_traceShapeEx = VirtualFunction.Create<
nint, // this
nint, // start Vector*
nint, // end Vector*
nint, // CTraceFilter*
nint, // Ray_t*
nint
>(_handle, 3);
} }
public nint TraceShape( public bool TraceShape(
nint origin, nint pOrigin,
nint angles, nint pAngles,
nint ignoreEntity, nint pIgnoreEntity,
nint options) nint pOptions,
nint pOutResult)
{ {
if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero)
return false;
return _traceShape!( return _traceShape!(
_handle, g_pRayTraceHandle,
origin, pOrigin,
angles, pAngles,
ignoreEntity, pIgnoreEntity,
options pOptions,
); pOutResult
}
public nint TraceEndShape(
nint start,
nint end,
nint ignoreEntity,
nint options)
{
return _traceEndShape!(
_handle,
start,
end,
ignoreEntity,
options
);
}
public nint TraceShapeEx(
nint start,
nint end,
nint filter,
nint ray)
{
return _traceShapeEx!(
_handle,
start,
end,
filter,
ray
); );
} }
``` ```
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Collision masks (C#) ## Low-level usage from C# (Ray_t & CTraceFilter)
``` csharp When using the low-level method `TraceShapeEx` from a C# plugin, the plugin
using System; must provide its own native-compatible implementations of the following
engine structures:
[Flags] - `Ray_t`
public enum InteractionLayers : ulong - `CTraceFilter`
These structures are not exposed directly by the Ray-Trace interface and
must be recreated in managed code with correct memory layout.
### Ray_t (C#)
The C# plugin must define a struct that matches the native `Ray_t` layout
used by the engine.
Example (simplified):
```csharp
[StructLayout(LayoutKind.Sequential)]
public struct Ray_t
{ {
Solid = 0x1, public Vector3 m_vecStart;
Hitboxes = 0x2, public Vector3 m_vecDelta;
Trigger = 0x4, public byte m_IsRay;
Sky = 0x8, public byte m_IsSwept;
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,
FUNPLAY_IGNORE_PLAYER = (0x8000000000ul << 1)
}
public static class TraceMasks
{
public const InteractionLayers MASK_SHOT_PHYSICS =
InteractionLayers.Solid |
InteractionLayers.PlayerClip |
InteractionLayers.Window |
InteractionLayers.PassBullets |
InteractionLayers.Player |
InteractionLayers.NPC |
InteractionLayers.Physics_Prop;
public const InteractionLayers MASK_SHOT_HITBOX =
InteractionLayers.Hitboxes |
InteractionLayers.Player |
InteractionLayers.NPC;
public const InteractionLayers MASK_SHOT_FULL =
MASK_SHOT_PHYSICS |
InteractionLayers.Hitboxes;
public const InteractionLayers MASK_WORLD_ONLY =
InteractionLayers.Solid |
InteractionLayers.Window |
InteractionLayers.PassBullets;
public const InteractionLayers MASK_GRENADE =
InteractionLayers.Solid |
InteractionLayers.Window |
InteractionLayers.Physics_Prop |
InteractionLayers.PassBullets;
public const InteractionLayers MASK_PLAYER_MOVE =
InteractionLayers.Solid |
InteractionLayers.Window |
InteractionLayers.PlayerClip |
InteractionLayers.PassBullets;
public const InteractionLayers MASK_NPC_MOVE =
InteractionLayers.Solid |
InteractionLayers.Window |
InteractionLayers.NPCClip |
InteractionLayers.PassBullets;
} }
``` ```
(Exact layout depends on the engine version and must match native memory.)
**CTraceFilter (C#)**
For `CTraceFilter`, the plugin must:
- Define a managed struct matching the native layout.
- Resolve the CTraceFilter vtable pointer using a signature scan.
- Assign the resolved vtable to the struct before calling TraceShapeEx.
This is required because CTraceFilter is a polymorphic C++ class and its
virtual table is not directly exposed to managed code.
Example concept:
```csharp
[StructLayout(LayoutKind.Sequential)]
public unsafe struct CTraceFilter
{
public nint __vtable;
public ulong m_nInteractsWith;
public T ...;
}
```
The vtable pointer must be resolved at runtime using a signature scan
against the game binary.
**Important notes**
- This setup is only required when using the low-level API:
- TraceShapeEx(...)
- High-level functions (TraceShape, TraceEndShape) do not require custom Ray_t or CTraceFilter handling from C#.
- Incorrect structure layout or invalid vtable resolution will result in crashes or undefined behavior.
- This is considered an advanced use case intended for engine-level plugins.
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Directory layout ## Notes about ABI & Destructor
- CRayTraceInterface has a virtual destructor.
- The object is owned by the Ray-Trace Metamod module.
- Plugins must never call delete on the interface pointer.
- C# must only clear its handle on unload.
- All parameters are passed as native pointers (nint).
addons/ # Build
└── Ray-Trace/ ## Requirements
└── bin/linuxsteamrt64/Ray-Trace.so - HL2SDK-CS2
- Metamod:Source
------------------------------------------------------------------------ - CMake
```bash
## Build
### Requirements
- HL2SDK-CS2
- Metamod:Source
- CMake
``` bash
git clone https://github.com/FUNPLAY-pro-CS2/Ray-Trace.git git clone https://github.com/FUNPLAY-pro-CS2/Ray-Trace.git
cd Ray-Trace cd Ray-Trace
git submodule update --init --recursive git submodule update --init --recursive
@ -404,12 +319,9 @@ docker compose -f docker/docker-compose.yml up
------------------------------------------------------------------------ ------------------------------------------------------------------------
## License ## License
GPLv3
GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
------------------------------------------------------------------------
## Author ## Author
**Michal "Slynx" Přikryl**
**Michal "Slynx" Přikryl**\ https://slynxdev.cz
https://slynxdev.cz

View file

@ -1,5 +1,4 @@
#pragma once #pragma once
#include <optional>
#include "vector.h" #include "vector.h"
#include "gametrace.h" #include "gametrace.h"
@ -158,18 +157,18 @@ constexpr InteractionLayers BuildShotMask(bool includePlayers, bool includeHitbo
struct TraceOptions struct TraceOptions
{ {
std::optional<InteractionLayers> InteractsWith{ MASK_SHOT_PHYSICS }; uint64_t InteractsWith = static_cast<uint64_t>(MASK_SHOT_PHYSICS);
std::optional<InteractionLayers> InteractsExclude{}; uint64_t InteractsExclude = 0;
bool DrawBeam{ false }; int DrawBeam = 0;
}; };
struct TraceResult struct TraceResult
{ {
Vector EndPos{}; Vector EndPos;
CEntityInstance* HitEntity{}; CEntityInstance* HitEntity;
float Fraction{}; float Fraction;
bool AllSolid{}; int AllSolid;
Vector Normal{}; Vector Normal;
}; };
class CRayTraceInterface class CRayTraceInterface
@ -177,21 +176,27 @@ class CRayTraceInterface
public: public:
virtual ~CRayTraceInterface() = default; virtual ~CRayTraceInterface() = default;
virtual std::optional<TraceResult> TraceShape( virtual bool TraceShape(
const Vector& origin, const Vector* origin,
const QAngle& viewangles, const QAngle* viewangles,
CBaseEntity* ignorePlayer = nullptr, CBaseEntity* ignorePlayer,
const TraceOptions* opts = nullptr) = 0; const TraceOptions* opts,
TraceResult* outResult
) = 0;
virtual std::optional<TraceResult> TraceEndShape( virtual bool TraceEndShape(
const Vector& origin, const Vector* origin,
const Vector& endOrigin, const Vector* endOrigin,
CBaseEntity* ignorePlayer = nullptr, CBaseEntity* ignorePlayer,
const TraceOptions* opts = nullptr) = 0; const TraceOptions* opts,
TraceResult* outResult
) = 0;
virtual std::optional<TraceResult> TraceShapeEx( virtual bool TraceShapeEx(
const Vector& vecStart, const Vector* vecStart,
const Vector& vecEnd, const Vector* vecEnd,
CTraceFilter& filterInc, CTraceFilter* filterInc,
Ray_t rayInc) = 0; Ray_t rayInc,
TraceResult* outResult
) = 0;
}; };

View file

@ -47,7 +47,7 @@ namespace RayTracePlugin::RayTrace
beam->m_clrRender().SetColor(color.r(), color.g(), color.b(), color.a()); beam->m_clrRender().SetColor(color.r(), color.g(), color.b(), color.a());
beam->m_fWidth() = 1.5f; beam->m_fWidth() = 1.5f;
beam->m_nRenderMode() = kRenderGlow; beam->m_nRenderMode() = kRenderNormal;
beam->m_nRenderFX() = kRenderFxNone; beam->m_nRenderFX() = kRenderFxNone;
beam->Teleport(&start, &VectorExtends::RotationZero, &VectorExtends::VectorZero); beam->Teleport(&start, &VectorExtends::RotationZero, &VectorExtends::VectorZero);
@ -95,10 +95,16 @@ namespace RayTracePlugin::RayTrace
CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx(); CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx();
filter.m_nInteractsWith = static_cast<uint64_t>(MASK_SHOT_PHYSICS);
filter.m_nInteractsExclude = 0;
if (opts) if (opts)
{ {
if (opts->InteractsWith) filter.m_nInteractsWith = static_cast<uint64_t>(*opts->InteractsWith); if (opts->InteractsWith != 0)
if (opts->InteractsExclude) filter.m_nInteractsExclude = static_cast<uint64_t>(*opts->InteractsExclude); filter.m_nInteractsWith = opts->InteractsWith;
if (opts->InteractsExclude != 0)
filter.m_nInteractsExclude = opts->InteractsExclude;
} }
Ray_t ray; Ray_t ray;
@ -121,10 +127,16 @@ namespace RayTracePlugin::RayTrace
{ {
CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx(); CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx();
filter.m_nInteractsWith = static_cast<uint64_t>(MASK_SHOT_PHYSICS);
filter.m_nInteractsExclude = 0;
if (opts) if (opts)
{ {
if (opts->InteractsWith) filter.m_nInteractsWith = static_cast<uint64_t>(*opts->InteractsWith); if (opts->InteractsWith != 0)
if (opts->InteractsExclude) filter.m_nInteractsExclude = static_cast<uint64_t>(*opts->InteractsExclude); filter.m_nInteractsWith = opts->InteractsWith;
if (opts->InteractsExclude != 0)
filter.m_nInteractsExclude = opts->InteractsExclude;
} }
Ray_t ray; Ray_t ray;

View file

@ -50,31 +50,49 @@ namespace RayTracePlugin::RayTrace
class CRayTrace : public CRayTraceInterface class CRayTrace : public CRayTraceInterface
{ {
public: public:
std::optional<TraceResult> TraceShape( bool TraceShape(
const Vector& origin, const Vector* origin,
const QAngle& viewangles, const QAngle* viewangles,
CBaseEntity* ignorePlayer, CBaseEntity* ignorePlayer,
const TraceOptions* opts) override const TraceOptions* opts,
TraceResult* outResult) override
{ {
return RayTrace::TraceShape(origin, viewangles, ignorePlayer, opts); auto result = RayTrace::TraceShape(*origin, *viewangles, ignorePlayer, opts);
if (!result.has_value())
return false;
*outResult = result.value();
return true;
} }
std::optional<TraceResult> TraceEndShape( bool TraceEndShape(
const Vector& origin, const Vector* origin,
const Vector& endOrigin, const Vector* endOrigin,
CBaseEntity* ignorePlayer, CBaseEntity* ignorePlayer,
const TraceOptions* opts) override const TraceOptions* opts,
TraceResult* outResult) override
{ {
return RayTrace::TraceEndShape(origin, endOrigin, ignorePlayer, opts); auto result = RayTrace::TraceEndShape(*origin, *endOrigin, ignorePlayer, opts);
if (!result.has_value())
return false;
*outResult = result.value();
return true;
} }
std::optional<TraceResult> TraceShapeEx( bool TraceShapeEx(
const Vector& vecStart, const Vector* vecStart,
const Vector& vecEnd, const Vector* vecEnd,
CTraceFilter& filterInc, CTraceFilter* filterInc,
Ray_t rayInc) override Ray_t rayInc,
TraceResult* outResult) override
{ {
return RayTrace::TraceShapeEx(vecStart, vecEnd, filterInc, rayInc); auto result = RayTrace::TraceShapeEx(*vecStart, *vecEnd, *filterInc, rayInc);
if (!result.has_value())
return false;
*outResult = result.value();
return true;
} }
}; };