No description
Find a file
2026-01-30 10:31:43 +04:00
.github/workflows Fix: fix crash on plugin start with no spdlog loaded 2026-01-14 17:36:03 +04:00
configs/addons Update collision rules and teleport offsets in gamedata.json 2026-01-26 20:50:01 +04:00
docker Fix: fix crash on plugin start with no spdlog loaded 2026-01-14 17:36:03 +04:00
makefiles Update protobuf.cmake 2026-01-26 21:34:34 +04:00
public Cleanup 2026-01-28 21:37:13 +04:00
src Update readme 2026-01-26 21:29:09 +04:00
vendor Cleanup 2026-01-28 21:37:13 +04:00
.clang-tidy Initial commit 2026-01-11 18:57:42 +04:00
.gitignore Initial commit 2026-01-11 18:57:42 +04:00
.gitmodules prepare KHook 2026-01-12 21:03:08 +04:00
CMakeLists.txt prepare KHook 2026-01-12 21:03:08 +04:00
LICENSE Initial commit 2026-01-11 18:57:42 +04:00
README.md update readme 2026-01-30 10:31:43 +04:00

Ray-Trace

Shared ray tracing interface for Metamod:Source & CounterStrikeSharp plugins


Overview

Ray-Trace is a lightweight Metamod interface module for
Counter-Strike 2 servers.

It exposes a shared interface: CRayTraceInterface001 which can be consumed from:

  • Native Metamod C++ plugins
  • Managed CounterStrikeSharp C# plugins

The interface is implemented as a C++ virtual class and is accessed from C# by calling its vtable functions directly using a native handle.

The goal is to provide a single tracing backend usable from both worlds
without duplicating engine detours.


Features

  • Metamod meta interface (CRayTraceInterface001)
  • Works in C++ and C#
  • Physics / hitbox / world trace presets
  • Custom collision masks
  • Optional debug beam rendering
  • Zero configuration
  • Ultra low overhead
  • ABI-safe (no STL types in interface)
  • Virtual destructor for safe lifetime management

Exposed Interface (C++)

class CRayTraceInterface
{
public:
    virtual ~CRayTraceInterface() = default;

    virtual bool TraceShape(
        const Vector* pOrigin,
        const QAngle* pViewAngles,
        CBaseEntity* pIgnorePlayer,
        const TraceOptions* pOpts,
        TraceResult* pOutResult
    ) = 0;

    virtual bool TraceEndShape(
        const Vector* pOrigin,
        const Vector* pEndOrigin,
        CBaseEntity* pIgnorePlayer,
        const TraceOptions* pOpts,
        TraceResult* pOutResult
    ) = 0;

    virtual bool TraceShapeEx(
        const Vector* pVecStart,
        const Vector* pVecEnd,
        CTraceFilter* pFilterInc,
        Ray_t rayInc,
        TraceResult* pOutResult
    ) = 0;
};

Return value: - true → trace hit something, TraceResult is valid\

  • false → no hit

Getting the interface

C++ (Metamod plugin)

CRayTraceInterface* g_pRayTrace = nullptr;
bool g_bRayTraceLoaded = false;

bool LoadRayTrace()
{
    int iRet = 0;

    g_pRayTrace = static_cast<CRayTraceInterface*>(
        g_SMAPI->MetaFactory("CRayTraceInterface001", &iRet, nullptr)
    );

    if (iRet == META_IFACE_FAILED || !g_pRayTrace)
    {
        FP_ERROR("Failed to lookup Ray-Trace interface!");
        return false;
    }

    g_bRayTraceLoaded = true;
    return true;
}

C# (CounterStrikeSharp plugin)

private nint g_pRayTraceHandle = nint.Zero;
private bool g_bRayTraceLoaded = false;

public override void Load(bool hotReload)
{
    g_pRayTraceHandle = Utilities.MetaFactory("CRayTraceInterface001");

    if (g_pRayTraceHandle == nint.Zero)
    {
        throw new Exception("Failed to get Ray-Trace interface handle");
    }

    Bind();
    g_bRayTraceLoaded = true;
}

The returned handle is a pointer to the native CRayTraceInterface object.


Calling methods from C++ (Metamod)

TraceShape example

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

TraceResult traceResult{};

bool bHit = g_pRayTrace->TraceEndShape(
    &vecStartPos,
    &vecEndPos,
    nullptr,
    &traceOpts,
    &traceResult
);

TraceShapeEx (low-level)

Ray_t ray{};
CTraceFilter filter(
    static_cast<uint64_t>(MASK_SHOT_FULL),
    COLLISION_GROUP_DEFAULT,
    true
);

TraceResult traceResult{};

bool bHit = g_pRayTrace->TraceShapeEx(
    &vecStartPos,
    &vecEndPos,
    &filter,
    ray,
    &traceResult
);

Calling methods from C# (CounterStrikeSharp plugin)

private delegate bool TraceShapeFn(
    nint pThis,
    nint pOrigin,
    nint pAngles,
    nint pIgnoreEntity,
    nint pOptions,
    nint pOutResult
);

private TraceShapeFn? _traceShape;
private TraceShapeFn? _traceEndShape;
private TraceShapeFn? _traceShapeEx;

private void Bind()
{
    _traceShape = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 1);
    _traceEndShape = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 2);
    _traceShapeEx = VirtualFunction.Create<TraceShapeFn>(g_pRayTraceHandle, 3);
}

public bool TraceShape(
    nint pOrigin,
    nint pAngles,
    nint pIgnoreEntity,
    nint pOptions,
    nint pOutResult)
{
    if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero)
        return false;

    return _traceShape!(
        g_pRayTraceHandle,
        pOrigin,
        pAngles,
        pIgnoreEntity,
        pOptions,
        pOutResult
    );
}

Memory allocation from C# (Important)

When calling TraceShape or TraceEndShape from C#, the plugin must allocate native memory for the following structures:

  • TraceOptions
  • TraceResult

These parameters are native pointers in C++ (TraceOptions* and TraceResult*) and must remain valid for the duration of the call.

The recommended and safest approach is using stackalloc (or unsafe stack variables) to provide native memory on the stack.

Failing to allocate valid memory for these parameters will result in crashes or undefined behavior.

Example (C# stackalloc)

[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct TraceOptions
{
    public ulong InteractsWith;
    public ulong InteractsExclude;
    public int DrawBeam;
}

[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct TraceResult
{
    public Vector EndPos;
    public nint HitEntity;
    public float Fraction;
    public int AllSolid;
    public Vector Normal;
}

unsafe
{
    Vector origin = player.Position;
    QAngle angles = player.ViewAngles;

    TraceOptions* opts = stackalloc TraceOptions[1];
    opts->InteractsWith = (ulong)MASK_SHOT_FULL;
    opts->InteractsExclude = 0;
    opts->DrawBeam = 0;

    TraceResult* result = stackalloc TraceResult[1];

    bool hit = _traceShape!(
        g_pRayTraceHandle,
        (nint)&origin,
        (nint)&angles,
        nint.Zero,
        (nint)opts,
        (nint)result
    );

    if (hit)
    {
        Console.WriteLine($"Hit at: {result->EndPos}");
    }
}

Low-level usage from C# (Ray_t & CTraceFilter)

When using the low-level method TraceShapeEx from a C# plugin, the plugin must provide its own native-compatible implementations of the following engine structures:

  • Ray_t
  • 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):

[StructLayout(LayoutKind.Sequential)]
public struct Ray_t
{
    public Vector3 m_vecStart;
    public Vector3 m_vecDelta;
    public byte m_IsRay;
    public byte m_IsSwept;
}

(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.

Example concept:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct CTraceFilter
{
    public nint __vtable;
    public ulong m_nInteractsWith;
    public T ...;
}

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.


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).

Build

Requirements

  • HL2SDK-CS2
  • Metamod:Source
  • CMake
git clone https://github.com/FUNPLAY-pro-CS2/Ray-Trace.git
cd Ray-Trace
git submodule update --init --recursive
docker compose -f docker/docker-compose.yml up

License

GPLv3
https://www.gnu.org/licenses/gpl-3.0.en.html

Author

Michal "Slynx" Přikryl
https://slynxdev.cz