No description
Find a file
2026-01-26 21:29:09 +04:00
.github/workflows Fix: fix crash on plugin start with no spdlog loaded 2026-01-14 17:36:03 +04:00
configs/addons Finish RayTrace plugin 2026-01-14 11:41:53 +04:00
docker Fix: fix crash on plugin start with no spdlog loaded 2026-01-14 17:36:03 +04:00
makefiles prepare KHook 2026-01-12 21:03:08 +04:00
public Update readme 2026-01-26 21:29:09 +04:00
src Update readme 2026-01-26 21:29:09 +04:00
vendor Fix: fix crash on plugin start with no spdlog loaded 2026-01-14 17:36:03 +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-26 21:29:09 +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
    );
}

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.

This is required because CTraceFilter is a polymorphic C++ class and its virtual table is not directly exposed to managed code.

Example concept:

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

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