Player/PlayerInventory.cs
public sealed class PlayerInventory : Component, IPlayerEvent
{
	[RequireComponent]
	public Player Player { get; set; }

	[Sync]
	public Carryable Current { get; private set; }

	public List<Item> Items => GetComponentsInChildren<Item>( true ).ToList();

	public List<Carryable> Carryables => Items.OfType<Carryable>()
		.OrderBy( x => x.InventorySlot )
		.ThenBy( x => x.InventoryOrder )
		.ToList();

	public void GiveDefaultWeapons()
	{
		// Don't run any pickup notices when spawning in
		using var _ = Player.NoNoticeScope();

		Pickup( "weapons/crowbar/crowbar.prefab" );
		//	Pickup( "weapons/hands/hands.prefab" );
		//	Pickup( "weapons/camera/camera.prefab" );
		Pickup( "weapons/glock/glock.prefab" );

		Player.GiveAmmo( ResourceLibrary.Get<AmmoResource>( "weapons/ammo/9mm.ammo" ), 100, false );

		if ( GameSettings.CheatMode )
		{
			GiveAll();
		}
	}

	public void GiveAll()
	{
		Pickup( "weapons/mp5/mp5.prefab" );
		Pickup( "weapons/gaussgun/gauss.prefab" );
		Pickup( "weapons/python/python.prefab" );
		Pickup( "weapons/handgrenade/hand_grenade.prefab" );
		Pickup( "weapons/tripmine/tripmine.prefab" );
		Pickup( "weapons/satchelcharge/satchelcharge.prefab" );
		Pickup( "weapons/rpg/rpg.prefab" );
		Pickup( "weapons/crossbow/crossbow.prefab" );
		Pickup( "weapons/shotgun/shotgun.prefab" );
		Pickup( "weapons/ratgun/rat_gun.prefab" );
		Pickup( "weapons/gluongun/gluon_gun.prefab" );
		Pickup( "weapons/hornetgun/hornetgun.prefab" );

		var ammo = ResourceLibrary.GetAll<AmmoResource>();
		foreach ( var a in ammo )
		{
			Player.GiveAmmo( a, 100, false );
		}
	}

	public bool HasItem<T>() where T : Item
	{
		return Items.OfType<T>().Any();
	}

	public T GetItem<T>() where T : Item
	{
		return Items.OfType<T>().FirstOrDefault();
	}

	public bool HasWeapon<T>() where T : Carryable
	{
		return Carryables.OfType<T>().Any();
	}

	public T GetWeapon<T>() where T : Carryable
	{
		return Carryables.OfType<T>().FirstOrDefault();
	}

	public bool HasWeapon( GameObject prefab )
	{
		var carry = prefab.Components.Get<Carryable>( true );
		if ( carry is null )
			return false;

		return Carryables.Where( x => x.GetType() == carry.GetType() ).FirstOrDefault().IsValid();
	}

	public void SwitchWeapon( Carryable weapon )
	{
		if ( weapon == Current )
			return;

		if ( Current.IsValid() )
		{
			Current.OnHolstered( Player );
			Current.GameObject.Enabled = false;
		}

		Current = weapon;

		if ( Current.IsValid() )
		{
			Current.OnEquipped( Player );
			Current.GameObject.Enabled = true;
		}
	}

	public Carryable GetBestWeapon()
	{
		return Carryables.OrderByDescending( x => x.Value ).FirstOrDefault();
	}

	public Carryable GetBestWeaponHolstered()
	{
		return Carryables
			.Where( x => !x.ShouldAvoid )
			.OrderByDescending( x => x.Value )
			.Where( x => x != Current )
			.FirstOrDefault();
	}

	public bool Pickup( string prefabName, bool notice = true )
	{
		if ( !Networking.IsHost )
			return false;

		var prefab = GameObject.GetPrefab( prefabName );
		return Pickup( prefab, notice );
	}

	public bool Pickup( GameObject prefab, bool notice = true )
	{
		if ( !Networking.IsHost )
			return false;

		var item = prefab.GetComponent<Item>( true );
		if ( item is null )
			return false;

		// Check if we already have this type of item
		var existingItem = Items.FirstOrDefault( x => x.GetType() == item.GetType() );
		if ( existingItem.IsValid() )
		{
			// Special case for weapons with ammo
			if ( existingItem is BaseWeapon existingWeapon && item is BaseWeapon newWeapon && newWeapon.UsesAmmo )
			{
				return HandleAmmoPickup( existingWeapon, newWeapon, notice );
			}

			return false;
		}

		// Create and set up the new item
		var clone = prefab.Clone( new CloneConfig { Parent = GameObject, StartEnabled = false } );
		clone.NetworkSpawn( false, Network.Owner );

		var newItem = clone.GetComponent<Item>( true );
		Assert.NotNull( newItem );

		newItem.OnAdded( Player );

		HandlePickup( newItem, notice );

		return true;
	}

	private bool HandleAmmoPickup( BaseWeapon existing, BaseWeapon weapon, bool notice )
	{
		var ammo = weapon.AmmoResource;
		if ( ammo is null || Player.GetAmmoCount( ammo ) >= ammo.MaxAmount )
			return false;

		Player.GiveAmmo( ammo, weapon.UsesClips ? weapon.ClipContents : weapon.StartingAmmo, notice );
		NotifyItemPickup( existing, true );
		return true;
	}

	private void HandlePickup( Item item, bool notice = true )
	{
		IPlayerEvent.PostToGameObject( Player.GameObject, e => e.OnPickup( item ) );

		if ( notice )
		{
			NotifyItemPickup( item );
		}
	}

	public void Take( Item item, bool notices = true )
	{
		// Check for existing item of same type
		var existing = Items.FirstOrDefault( x => x.GetType() == item.GetType() );
		if ( existing.IsValid() )
		{
			if ( existing is BaseWeapon existingWeapon && item is BaseWeapon newWeapon )
			{
				HandleAmmoPickup( existingWeapon, newWeapon, notices );
			}

			item.DestroyGameObject();
			return;
		}

		// Set up the new item
		item.GameObject.Parent = GameObject;
		item.Network.Refresh();

		if ( Network.Owner is not null )
		{
			item.Network.AssignOwnership( Network.Owner );
		}
		else
		{
			item.Network.DropOwnership();
		}

		item.OnAdded( Player );

		HandlePickup( item, notices );
	}

	[Rpc.Owner]
	private void NotifyItemPickup( Item item, bool justAmmo = false )
	{
		if ( !item.IsValid() )
			return;

		if ( item is Carryable carryable && ShouldAutoswitchTo( carryable ) )
		{
			SwitchToCarryable( carryable );
		}

		if ( Player.IsLocalPlayer )
			ILocalPlayerEvent.Post( e => e.OnPickup( item ) );
	}

	private bool ShouldAutoswitchTo( Carryable item )
	{
		if ( !item.IsValid() )
			return false;

		if ( !Current.IsValid() )
			return true;

		if ( !GamePreferences.AutoSwitch || Current.IsInUse() )
			return false;

		if ( item is BaseWeapon weapon && weapon.UsesAmmo )
		{
			var ammo = weapon.AmmoResource;
			if ( ammo is not null && Player.GetAmmoCount( ammo ) < 1 )
				return false;
		}

		return item.Value > Current.Value;
	}

	public void SwitchToCarryable( Carryable weapon )
	{
		if ( weapon == Current )
			return;

		if ( Current.IsValid() )
		{
			Current.OnHolstered( Player );
			Current.GameObject.Enabled = false;
		}

		Current = weapon;

		if ( Current.IsValid() )
		{
			Current.OnEquipped( Player );
			Current.GameObject.Enabled = true;
		}
	}

	protected override void OnUpdate()
	{
		if ( Current.IsValid() )
		{
			Current.OnPlayerUpdate( Player );
		}
	}

	public void DropCoffin()
	{
		if ( !Networking.IsHost )
			return;

		var go = GameObject.Clone( "items/coffin/coffin.prefab" );
		go.Name = $"Coffin for {GameObject.Name}";
		go.WorldPosition = Player.EyeTransform.Position;
		go.WorldRotation = Rotation.LookAt( Player.EyeTransform.Forward.WithZ( 0 ), Vector3.Up );

		var coffin = go.GetComponent<Coffin>();
		Assert.True( coffin.IsValid(), "Coffin not on coffin prefab" );

		coffin.AmmoCounts = new( Player.AmmoCounts );

		if ( go.GetComponent<Rigidbody>() is { } rb )
		{
			rb.Velocity = Player.Controller.Velocity + (Player.EyeTransform.Backward * 128);
		}

		go.NetworkSpawn( true, null );

		// Transfer all items to the coffin
		foreach ( var item in Items.ToArray() )
		{
			item.OnRemoved( Player );
			item.Network.DropOwnership();
			item.GameObject.Parent = go;
			item.GameObject.Enabled = false;
			item.Network.Refresh();
		}
	}

	// IPlayerEvent implementation
	void IPlayerEvent.OnSpawned() => GiveDefaultWeapons();

	void IPlayerEvent.OnDied( IPlayerEvent.DiedParams args )
	{
		foreach ( var item in Items )
		{
			item.OnPlayerDeath( args );
		}
	}

	void IPlayerEvent.OnPickup( Item item )
	{
		if ( item is BaseWeapon weapon && weapon.IsSelfAmmo )
			Player.ShowNotice( $"{weapon.AmmoResource.AmmoType} x {weapon.StartingAmmo}" );
		else
			Player.ShowNotice( item.DisplayName );
	}

	void IPlayerEvent.OnCameraMove( ref Angles angles )
	{
		if ( Current.IsValid() )
			Current.OnCameraMove( Player, ref angles );
	}

	void IPlayerEvent.OnCameraPostSetup( Sandbox.CameraComponent camera )
	{
		if ( Current.IsValid() )
			Current.OnCameraSetup( Player, camera );
	}
}