terminalCode Example

Sandbox: BaseWeapon — ammo system, reload, shoot delay, and IPlayerControllable

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

BaseWeapon — Ammo, Reloading, and Firing Pattern

BaseWeapon extends BaseCarryable and adds ammo management, reloading, and a shoot delay system. It implements IPlayerControllable so it can also be used from vehicle seats.

Ammo system

CSHARP
public partial class BaseWeapon : BaseCarryable, IPlayerControllable
{
    // Two ammo modes: clip-based or reserve-only
    [Property, Feature( "Ammo" )] public bool UsesAmmo { get; set; } = true;
    [Property, Feature( "Ammo" )] public bool UsesClips { get; set; } = true;
    [Property, Feature( "Ammo" )] public int ClipSize { get; set; } = 30;
    [Property, Feature( "Ammo" )] public int StartingAmmo { get; set; } = 90;

    // Shared ammo pool (e.g. all 9mm weapons share one pool)
    [Property, Feature( "Ammo" )] public AmmoResource AmmoType { get; set; }

    public override void OnAdded( Player player )
    {
        base.OnAdded( player );

        if ( !UsesAmmo ) return;

        if ( AmmoType is not null )
        {
            // Seed the shared pool with the resource's default if the player has none yet
            var inv = GetAmmoInventory();
            if ( inv is not null && !inv.HasAmmo( AmmoType ) && AmmoType.DefaultStartingAmmo > 0 )
                inv.AddAmmo( AmmoType, AmmoType.DefaultStartingAmmo );
        }
        else if ( StartingAmmo > 0 )
        {
            _reserveAmmo = Math.Min( StartingAmmo, _maxReserveAmmo );
        }
    }
}

Shoot delay and dry fire

CSHARP
// Prevents shooting for a duration (deploy time, fire rate, etc.)
public void AddShootDelay( float seconds )
{
    TimeUntilNextShotAllowed = seconds;
}

// Play dry fire sound — use on weapons that can't reload
public void DryFire()
{
    if ( HasAmmo() || IsReloading() || TimeUntilNextShotAllowed > 0 ) return;
    GameObject.PlaySound( DryFireSound );
}

// Play dry fire AND start reloading — use on weapons that can reload
public virtual void TryAutoReload()
{
    if ( HasAmmo() || IsReloading() || TimeUntilNextShotAllowed > 0 ) return;
    DryFire();
    AddShootDelay( 0.1f );
    if ( CanReload() ) OnReloadStart();
}

Control loop

CSHARP
public override void OnControl( Player player )
{
    // Cancel reload on attack input if weapon has ammo
    bool wantsToCancelReload = Input.Pressed( "Attack1" ) || Input.Pressed( "Attack2" );
    if ( CanCancelReload && IsReloading() && wantsToCancelReload && HasAmmo() )
        CancelReload();

    if ( CanReload() && Input.Pressed( "reload" ) )
        OnReloadStart();

    if ( CanPrimaryAttack() && WantsPrimaryAttack() )
        PrimaryAttack();

    if ( CanSecondaryAttack() && WantsSecondaryAttack() )
        SecondaryAttack();
}

public virtual bool CanPrimaryAttack()
{
    if ( IsReloading() ) return false;
    if ( TimeUntilNextShotAllowed > 0 ) return false;
    return true;
}

IPlayerControllable — use from vehicle seats

CSHARP
// Weapons can be controlled from seats via ClientInput properties
[Property, Sync, ClientEditable, Group( "Inputs" )]
public ClientInput ShootInput { get; set; }

[Property, Sync, ClientEditable, Group( "Inputs" )]
public ClientInput SecondaryInput { get; set; }

public void OnControl() // Called when controlled via seat (no Player arg)
{
    if ( HasOwner ) return;
    if ( IsProxy ) return;

    if ( ShootInput.Down() && CanPrimaryAttack() )
        PrimaryAttack();

    if ( SecondaryInput.Down() && CanSecondaryAttack() )
        SecondaryAttack();
}

Key points

Was this helpful?