Weapons/BaseWeapon/BaseWeapon.cs

Base weapon component for carryable weapons. Exposes clip/read-only alias, dry-fire behavior, auto-reload logic, shoot delay handling, simple HUD crosshair drawing, and input handling for reload/cancel reload.

File AccessNative Interop
using Sandbox.Rendering;

/// <summary>
/// A <see cref="Carryable"/> that shoots. Magazines, reserve ammo, reloading and fire timing all live
/// on <see cref="BaseCombatWeapon"/> now - this keeps the deathmatch-facing names and the dry-fire /
/// auto-reload behaviour our weapons are written against.
/// </summary>
public partial class BaseWeapon : Carryable
{
	/// <summary>
	/// Rounds in the magazine. Read-only alias for the engine's <see cref="BaseCombatWeapon.Clip1"/>,
	/// which is host owned - spend it through <see cref="TakeAmmo(int)"/> so the host sees the spend,
	/// rather than assigning, which a client couldn't make stick.
	/// </summary>
	public int ClipContents => Clip1;

	/// <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 )
	{
		SetNextFire( seconds );
	}

	/// <summary>
	/// The dry fire sound if we have no ammo
	/// </summary>
	private static SoundEvent DefaultDryFireSound = new SoundEvent( "audio/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 override void DryFire()
	{
		if ( HasAmmo() )
			return;

		if ( IsReloading )
			return;

		if ( NextPrimaryFire > 0 )
			return;

		GameObject.PlaySound( DryFireSound ?? DefaultDryFireSound );
	}

	/// <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 ( NextPrimaryFire > 0 )
			return;

		DryFire();

		AddShootDelay( 0.1f );

		if ( CanReload() )
			Reload();
		else
			SwitchAway();
	}

	/// <summary>
	/// Are we allowed to shoot this weapon? Can be overriden per-weapon
	/// </summary>
	/// <returns></returns>
	public virtual bool CanShoot()
	{
		if ( !HasAmmo() ) return false;
		if ( IsReloading ) return false;
		if ( NextPrimaryFire > 0 ) return false;

		return true;
	}

	public override void DrawHud( HudPainter painter, Vector2 crosshair )
	{
		DrawCrosshair( painter, crosshair );
	}

	public override void DrawCrosshair( HudPainter hud, Vector2 center )
	{
		Color 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 );
	}

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

		bool wantsToCancelReload = Input.Pressed( "Attack1" ) || Input.Pressed( "Attack2" );
		if ( CanCancelReload && IsReloading && wantsToCancelReload && HasAmmo() )
		{
			CancelReload();
		}

		if ( CanReload() && Input.Pressed( "reload" ) )
		{
			Reload();
		}
	}
}