Game/Weapon/BaseWeapon/BaseWeapon.Ammo.cs
public partial class BaseWeapon
{
	/// <summary>
	/// Does this weapon consume ammo at all?
	/// </summary>
	[Property, FeatureEnabled( "Ammo" )] public bool UsesAmmo { get; set; } = true;

	/// <summary>
	/// Does this weapon use clips?
	/// </summary>
	[Property, Feature( "Ammo" )] public bool UsesClips { get; set; } = true;

	/// <summary>
	/// When reloading, we'll take ammo from the reserve as much as we can to fill to this amount.
	/// </summary>
	[Property, Feature( "Ammo" ), ShowIf( nameof( UsesClips ), true )] public int ClipMaxSize { get; set; } = 30;

	/// <summary>
	/// The default amount of bullets in a weapon's magazine on pickup.
	/// </summary>
	[Property, Feature( "Ammo" ), ShowIf( nameof( UsesClips ), true )] public int ClipContents { get; set; } = 20;

	/// <summary>
	/// The ammo resource this weapon uses for its reserve pool.
	/// When set, reserve ammo is shared with all weapons using the same resource.
	/// When null, ammo is tracked per-weapon (tied to this weapon instance).
	/// </summary>
	[Property, Feature( "Ammo" )] public AmmoResource AmmoType { get; set; }

	/// <summary>
	/// The maximum reserve ammo this weapon can hold (used only when <see cref="AmmoType"/> is null).
	/// When <see cref="AmmoType"/> is set, the resource's <see cref="AmmoResource.MaxReserve"/> is used instead.
	/// </summary>
	[Property, Feature( "Ammo" )] public int MaxReserveAmmo
	{
		get => AmmoType?.MaxReserve ?? _maxReserveAmmo;
		set => _maxReserveAmmo = value;
	}
	private int _maxReserveAmmo = 120;

	/// <summary>
	/// The current reserve ammo. When <see cref="AmmoType"/> is set this proxies to the player's
	/// <see cref="AmmoInventory"/>; otherwise it is stored directly on the weapon.
	/// </summary>
	[Property, Feature( "Ammo" )] public int ReserveAmmo
	{
		get
		{
			if ( AmmoType is not null )
				return GetAmmoInventory()?.GetAmmo( AmmoType ) ?? 0;

			return _reserveAmmo;
		}
		set
		{
			if ( AmmoType is not null )
			{
				GetAmmoInventory()?.SetAmmo( AmmoType, value );
				return;
			}

			_reserveAmmo = value;
		}
	}

	/// <summary>
	/// Backing field for per-weapon reserve ammo (used when <see cref="AmmoType"/> is null).
	/// </summary>
	[Sync] private int _reserveAmmo { get; set; } = 0;

	/// <summary>
	/// How much reserve ammo this weapon starts with on pickup.
	/// When <see cref="AmmoType"/> is set, this seeds the shared pool only if the pool is empty.
	/// </summary>
	[Property, Feature( "Ammo" )] public int StartingAmmo { get; set; } = 0;

	/// <summary>
	/// How long does it take to reload?
	/// </summary>
	[Property, Feature( "Ammo" )] public float ReloadTime { get; set; } = 2.5f;

	/// <summary>
	/// Returns the player's <see cref="AmmoInventory"/>, or null if unavailable.
	/// </summary>
	private AmmoInventory GetAmmoInventory() => Owner?.GetComponent<AmmoInventory>();

	/// <summary>
	/// Can we switch to this gun?
	/// </summary>
	public override bool CanSwitch()
	{
		return true;
	}

	/// <summary>
	/// Takes ammo from the clip, or from reserve if not using clips.
	/// </summary>
	public bool TakeAmmo( int count )
	{
		if ( !UsesAmmo ) return true;
		if ( WeaponConVars.UnlimitedAmmo ) return true;

		if ( UsesClips )
		{
			if ( ClipContents < count )
				return false;

			ClipContents -= count;
			return true;
		}

		// No clips — take directly from reserve
		if ( WeaponConVars.InfiniteReserves ) return true;

		if ( AmmoType is not null )
		{
			var inv = GetAmmoInventory();
			if ( inv is null ) return false;
			return inv.TakeAmmo( AmmoType, count );
		}

		if ( _reserveAmmo < count )
			return false;

		_reserveAmmo -= count;
		return true;
	}

	/// <summary>
	/// Do we have ammo?
	/// </summary>
	public bool HasAmmo()
	{
		if ( !UsesAmmo ) return true;
		if ( WeaponConVars.UnlimitedAmmo ) return true;

		if ( UsesClips )
			return ClipContents > 0;

		if ( WeaponConVars.InfiniteReserves ) return true;

		return ReserveAmmo > 0;
	}

	/// <summary>
	/// Adds reserve ammo to this weapon (or the shared pool), clamped to max.
	/// Returns the actual amount added.
	/// </summary>
	public int AddReserveAmmo( int count )
	{
		if ( AmmoType is not null )
		{
			var inv = GetAmmoInventory();
			return inv?.AddAmmo( AmmoType, count ) ?? 0;
		}

		var space = _maxReserveAmmo - _reserveAmmo;
		var toAdd = Math.Min( count, space );
		_reserveAmmo += toAdd;
		return toAdd;
	}
}