Player/PlayerInventory.Ammo.cs
public sealed partial class PlayerInventory
{
	public sealed class AmmoPickupNotice
	{
		public BaseAmmoResource Type { get; init; }
		public int Amount { get; set; }
		public float ExpiresAt { get; set; }
	}

	private readonly List<AmmoPickupNotice> _ammoPickupNotices = new();
	public IReadOnlyList<AmmoPickupNotice> AmmoPickupNotices => _ammoPickupNotices;

	/// <summary>Give reserve ammo and report the amount actually accepted to its owner.</summary>
	public new int GiveAmmo( BaseAmmoResource type, int amount )
	{
		var added = base.GiveAmmo( type, amount );
		NotifyAmmoPickup( type, added );
		return added;
	}

	private void NotifyAmmoPickup( BaseAmmoResource type, int amount )
	{
		if ( !Networking.IsHost || type is null || amount <= 0 ) return;
		ReceiveAmmoPickup( type.ResourcePath, amount );
	}

	[Rpc.Owner]
	private void ReceiveAmmoPickup( string resourcePath, int amount )
	{
		if ( !Player.IsValid() || !Player.IsLocalPlayer || amount <= 0 ) return;
		var type = ResourceLibrary.Get<BaseAmmoResource>( resourcePath );
		if ( type is null ) return;

		var now = RealTime.Now;
		_ammoPickupNotices.RemoveAll( notice => notice.ExpiresAt <= now );
		var existing = _ammoPickupNotices.FirstOrDefault( notice => notice.Type.ResourcePath == resourcePath );
		if ( existing is not null )
		{
			existing.Amount += amount;
			existing.ExpiresAt = now + 1.5f;
			return;
		}

		if ( _ammoPickupNotices.Count >= 4 ) _ammoPickupNotices.RemoveAt( 0 );
		_ammoPickupNotices.Add( new AmmoPickupNotice { Type = type, Amount = amount, ExpiresAt = now + 1.5f } );
	}
}