terminalCode Example
Sandbox: BaseWeapon — ammo system, reload, shoot delay, and IPlayerControllable
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
- DeployTime (default 0.5s) is applied as a shoot delay in OnEnabled
- AmmoType (an AmmoResource) enables shared ammo pools across weapon types
- UsesClips = false means the weapon draws directly from reserve ammo with no clip
- [ClientEditable] on ShootInput / SecondaryInput lets players remap seat weapon inputs
- ShouldAvoid returns !HasAmmo() — the inventory uses this to skip empty weapons when auto-switching
Was this helpful?