terminalCode Example

Sandbox: BaseCarryable — unified AimRay, WeaponModel resolution, and seat-aware damage

calendar_today May 4, 2026 schedule ~1 min read person patrickjr verified 50

BaseCarryable — Unified Weapon Base with AimRay and Seat Support

BaseCarryable is the root class for all holdable items. It handles viewmodel/worldmodel management, a unified AimRay, muzzle point resolution, and damage attribution that works whether the weapon is held by a player, controlled from a vehicle seat, or placed standalone in the world.
CSHARP
public partial class BaseCarryable : Component, IKillIcon
{
    [Property, Feature( "Inventory" )] public string DisplayName { get; set; }
    [Property, Feature( "Inventory" )] public Texture DisplayIcon { get; set; }
    [Property, Feature( "Inventory" )] public GameObject ItemPrefab { get; set; } // Dropped pickup prefab
    [Property, Feature( "Inventory" )] public int Value { get; set; } = 0; // Auto-switch priority

    public GameObject ViewModel { get; protected set; }
    public GameObject WorldModel { get; protected set; }

    // Optional explicit muzzle — falls back to WeaponModel.MuzzleTransform
    [Property] public GameObject MuzzleGameObject { get; set; }

    // The owner player, or null if standalone/seated
    public Player Owner => GetComponentInParent<Player>( true );
    public bool HasOwner => Owner.IsValid();
}

Unified AimRay

CSHARP
public Ray AimRay
{
    get
    {
        if ( HasOwner )
        {
            var owner = Owner;
            // Third-person: use scene camera direction
            if ( owner.Controller.ThirdPerson && Scene.Camera.IsValid() )
                return Scene.Camera.Transform.World.ForwardRay;

            return owner.EyeTransform.ForwardRay;
        }

        // Seated: use camera direction if IsTargetedAim, else muzzle
        var seated = ClientInput.Current;
        if ( seated.IsValid() && IsTargetedAim && Scene.Camera.IsValid() )
            return Scene.Camera.Transform.World.ForwardRay;

        var muzzle = MuzzleTransform.WorldTransform;
        return new Ray( muzzle.Position, muzzle.Rotation.Forward );
    }
}

// Override in weapons that support player-directed aim from seats (e.g. RPG, Physgun)
public virtual bool IsTargetedAim => false;

WeaponModel resolution

CSHARP
public WeaponModel WeaponModel
{
    get
    {
        var go = ViewModel;

        // If first-person is excluded (e.g. third-person mode), skip viewmodel
        if ( Scene.Camera.RenderExcludeTags.Contains( "firstperson" ) ) go = default;

        if ( !go.IsValid() ) go = WorldModel;
        if ( !go.IsValid() ) go = GameObject;

        var wm = go.GetComponentInChildren<WeaponModel>();
        if ( wm.IsValid() ) return wm;

        // Standalone weapons may have a WorldModel without the stored reference
        return GameObject.GetComponentInChildren<WeaponModel>();
    }
}

Damage attribution

CSHARP
// Returns the correct attacker for damage: player > seated player > weapon itself
protected GameObject EffectiveAttacker
{
    get
    {
        if ( HasOwner ) return Owner.GameObject;
        var seatedPlayer = ClientInput.Current;
        if ( seatedPlayer.IsValid() ) return seatedPlayer.GameObject;
        var killSource = GetComponentInParent<IKillSource>( true );
        if ( killSource is Component c ) return c.GameObject;
        return GameObject;
    }
}

// TraceAttack is an RPC to the host — sends hit info for server-side damage application
[Rpc.Host]
public void TraceAttack( TraceAttackInfo attack )
{
    if ( !attack.Target.IsValid() ) return;

    var damagable = attack.Target.GetComponentInChildren<IDamageable>();
    if ( damagable is not null )
    {
        var info = new DamageInfo { Attacker = EffectiveAttacker, ... };
        damagable.OnDamage( info );
    }

    // Apply physics impulse
    if ( attack.Target.GetComponentInChildren<Rigidbody>() is var rb && rb.IsValid() )
        rb.ApplyImpulseAt( attack.Position, Vector3.Direction( attack.Origin, attack.Position ) * rb.Mass * 100 );
}

Key points

Was this helpful?