Game/Weapon/BaseWeapon/BaseWeapon.cs
using Sandbox.Rendering;
public partial class BaseWeapon : BaseCarryable, IPlayerControllable
{
/// <summary>
/// How long after deploying a weapon can you not shoot a gun?
/// </summary>
[Property] public float DeployTime { get; set; } = 0.5f;
public override bool ShouldAvoid => !HasAmmo();
/// <summary>
/// How long until we can shoot again
/// </summary>
protected TimeUntil TimeUntilNextShotAllowed;
/// <summary>
/// Adds a delay, making it so we can't shoot for the specified time
/// </summary>
/// <param name="seconds"></param>
public void AddShootDelay( float seconds )
{
TimeUntilNextShotAllowed = seconds;
}
/// <summary>
/// The dry fire sound if we have no ammo
/// </summary>
private static SoundEvent DryFireSound = new SoundEvent( "sounds/dry_fire.sound" );
/// <summary>
/// Play a dry fire sound. You should only call this on weapons that can't auto reload - if they can, use <see cref="TryAutoReload"/> instead.
/// </summary>
public void DryFire()
{
if ( HasAmmo() )
return;
if ( IsReloading() )
return;
if ( TimeUntilNextShotAllowed > 0 )
return;
GameObject.PlaySound( DryFireSound );
}
/// <summary>
/// Player has fired an empty gun - play dry fire sound and start reloading. You should only call this on weapons that can reload - if they can't, use <see cref="DryFire"/> instead.
/// </summary>
public virtual void TryAutoReload()
{
if ( HasAmmo() )
return;
if ( IsReloading() )
return;
if ( TimeUntilNextShotAllowed > 0 )
return;
DryFire();
AddShootDelay( 0.1f );
if ( CanReload() )
OnReloadStart();
}
protected override void OnEnabled()
{
base.OnEnabled();
AddShootDelay( DeployTime );
}
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 );
}
}
public override void DrawHud( HudPainter painter, Vector2 crosshair )
{
DrawCrosshair( painter, crosshair );
}
public override void OnPlayerUpdate( Player player )
{
if ( player is null ) return;
CreateViewModel();
GameObject.Network.Interpolation = false;
if ( !player.IsLocalPlayer )
return;
OnControl( player );
}
public override void OnControl( Player player )
{
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();
}
}
protected virtual bool WantsSecondaryAttack()
{
return Input.Down( "attack2" );
}
protected virtual bool WantsPrimaryAttack()
{
return Input.Down( "attack1" );
}
/// <summary>
/// Override to perform the weapon's primary attack. Default no-op.
/// </summary>
public virtual void PrimaryAttack()
{
}
/// <summary>
/// Override to perform the weapon's secondary attack. Default no-op.
/// </summary>
public virtual void SecondaryAttack()
{
}
/// <summary>
/// Determines if the primary attack should trigger
/// </summary>
public virtual bool CanPrimaryAttack()
{
if ( HasOwner && !HasAmmo() ) return false;
if ( IsReloading() ) return false;
if ( TimeUntilNextShotAllowed > 0 ) return false;
return true;
}
/// <summary>
/// Determines if the secondary attack should trigger
/// </summary>
public virtual bool CanSecondaryAttack()
{
if ( HasOwner && !HasAmmo() ) return false;
if ( IsReloading() ) return false;
if ( TimeUntilNextShotAllowed > 0 ) return false;
return true;
}
/// <summary>
/// Override the primary fire rate
/// </summary>
protected virtual float GetPrimaryFireRate() => 0.1f;
/// <summary>
/// Override the secondary fire rate
/// </summary>
protected virtual float GetSecondaryFireRate() => 0.2f;
/// <summary>
/// The input that fires the primary attack when this weapon is controlled via a seat.
/// </summary>
[Property, Sync, ClientEditable, Group( "Inputs" )] public ClientInput ShootInput { get; set; }
/// <summary>
/// The input that fires the secondary attack when this weapon is controlled via a seat.
/// </summary>
[Property, Sync, ClientEditable, Group( "Inputs" )] public ClientInput SecondaryInput { get; set; }
public bool CanControl( Player player )
{
var inventory = player.GetComponent<PlayerInventory>();
return inventory is null || !inventory.ActiveWeapon.IsValid();
}
public void OnStartControl() { }
public void OnEndControl() { }
public virtual void OnControl()
{
if ( HasOwner ) return;
if ( IsProxy ) return;
if ( ShootInput.Down() && CanPrimaryAttack() )
PrimaryAttack();
if ( SecondaryInput.Down() && CanSecondaryAttack() )
SecondaryAttack();
}
public virtual void DrawCrosshair( HudPainter hud, Vector2 center )
{
var color = Color.Red;
hud.DrawLine( center + Vector2.Left * 32, center + Vector2.Left * 15, 3, color );
hud.DrawLine( center - Vector2.Left * 32, center - Vector2.Left * 15, 3, color );
hud.DrawLine( center + Vector2.Up * 32, center + Vector2.Up * 15, 3, color );
hud.DrawLine( center - Vector2.Up * 32, center - Vector2.Up * 15, 3, color );
}
protected Color CrosshairCanShoot => Color.White;
protected Color CrosshairNoShoot => Color.Red;
}