Player/Inventory/PlayerInventory.cs
using Opium;

public partial class PlayerInventory : Component
{
	/// <summary>
	/// How many weapons can we fit in our inventory?
	/// </summary>
	[Property] public int Capacity { get; set; } = 2;

	/// <summary>
	/// A list of weapons.
	/// </summary>
	[Property] public List<BaseWeapon> Weapons { get; protected set; } = new();

	/// <summary>
	/// A list of prefabs to instantiate.
	/// </summary>
	[Property] public List<GameObject> Loadout { get; set; } = new();

	/// <summary>
	/// The current weapon we're using
	/// </summary>
	[Property, ReadOnly] public BaseWeapon Current { get; private set; }

	[Property] public BaseWeapon Fists { get; set; }

	[Property] public GameObject FistsPrefab { get; set; }

	/// <summary>
	/// Our previous weapon
	/// </summary>
	public BaseWeapon Previous { get; private set; }

	public Actor Owner => Components.Get<Actor>( FindMode.EverythingInSelfAndAncestors );

	public bool CanInsertWeapon()
	{
		return Weapons.Count < Capacity;
	}

	public BaseWeapon AddWeaponPrefab( GameObject prefab, bool setActive = false )
	{
		if ( !CanInsertWeapon() ) return null;

		var wpn = CreateWeaponFromPrefab( prefab );

		if ( wpn.IsValid() )
		{
			Weapons.Add( wpn );
		}
		else
		{
			return null;
		}

		// Hide the fists
		if ( Fists.IsValid() )
			Fists.SetAsActive( false );

		if ( ( setActive || Weapons.Count == 1 ) && Current is not SingleUseWeapon )
		{
			SetCurrent( Weapons.Count );
		}

		return wpn;
	}

	public BaseWeapon CreateWeaponFromPrefab( GameObject prefab )
	{
		var obj = prefab.Clone( new CloneConfig()
		{
			Parent = GameObject,
			StartEnabled = false
		} );

		obj.BreakFromPrefab();
		obj.Enabled = false;

		var weapon = obj.Components.Get<BaseWeapon>( true );
		if ( !weapon.IsValid() )
		{
			obj.Destroy();
		}

		return weapon;
	}

	protected override void OnStart()
	{
		// already got weapons? nope.
		if ( Weapons.Count > 0 )
		{
			return;
		}

		foreach ( var prefab in Loadout )
		{
			AddWeaponPrefab( prefab );
		}

		// Set weapon as the first one.
		var firstWeapon = Weapons.FirstOrDefault();
		if ( firstWeapon.IsValid() )
		{
			SetCurrent( 1 );
		}

		if ( Weapons.Count < 1 )
		{
			if ( !Fists.IsValid() )
			{
				TryCreateFists();
				SetCurrent( Fists, false );
			}
		}
	}

	/// <summary>
	/// Is this weapon index in bounds? Used in input.
	/// </summary>
	/// <param name="index"></param>
	/// <returns></returns>
	private bool InBounds( int index )
	{
		if ( index > Weapons.Count )
		{
			return false;
		}

		return true;
	}

	public void TryCreateFists()
	{
		if ( !Fists.IsValid() )
		{
			Fists = CreateWeaponFromPrefab( FistsPrefab );
		}
	}

	public void SetCurrent( BaseWeapon weapon, bool queue = false )
	{
		// Kinda poopy, but it works.
		if ( queue )
		{
			if ( Current.IsValid() )
			{
				QueuedWeapon = weapon;
				Holster();
			}
			else
			{
				SetCurrent( weapon );

				if ( !Current.IsValid() )
				{
					TryCreateFists();
					SetCurrent( Fists, false );
					return;
				}
			}

			return;
		}

		if ( Current.IsValid() )
		{
			Current.SetAsActive( false );
		}

		Previous = Current;

		if ( Previous is SingleUseWeapon single && single.IsValid() )
		{
            single.Discard();
		}

		Current = weapon;

		if ( !Current.IsValid() )
		{
			TryCreateFists();
			SetCurrent( Fists, false );
			return;
		}

		Current.Actor = Owner;
		Current.SetAsActive( true );
		Deploy();
	}

	public void SetCurrent( int index )
	{
		if ( !InBounds( index ) ) return;

		var weapon = Weapons[index - 1];

		// can't change to same wpn
		if ( Current == weapon ) return;

		SetCurrent( weapon, true );
	}

	private bool CanSwitchWeapon()
	{
		if ( Current.IsValid() && Current is SingleUseWeapon )
		{
			return false;
		}
		return true;
	}

	void CheckSlotForInput( int slot )
	{
		if ( Input.Pressed( $"Slot{slot}" ) )
		{
			if ( CanSwitchWeapon() )
			{
				SetCurrent( slot );
			}
		}
	}

	/// <summary>
	/// Tries to drop a weapon on the floor.
	/// </summary>
	/// <param name="weapon"></param>
	public WeaponPickup Drop( BaseWeapon weapon )
	{
		if ( !weapon.IsValid() ) return null;
		if ( !weapon.Droppable ) return null;

		var player = Components.Get<Opium.PlayerController>( FindMode.EverythingInSelfAndParent );

		if ( Current == weapon ) Current = null;
		
		if ( Weapons.Remove( weapon ) )
		{
			var go = weapon.Drop( player.MainCamera.Transform.Position, player.MainCamera.Transform.Position + player.MainCamera.Transform.Rotation.Forward * 50,
				player.MainCamera.Transform.Rotation.Forward );

			// Change weapon if we have one
			if ( Weapons.Count > 0 )
			{
				SetCurrent( 1 );
			}
			else
			{
				if ( player.IsAlive )
				{
					TryCreateFists();
					SetCurrent( Fists, false );
				}

			}

			return go;
		}

		return null;
	}

	void TryDropCurrentWeapon() => Drop( Current );

	protected override void OnUpdate()
	{
		for ( int i = 1; i < 5; i++ )
		{
			CheckSlotForInput( i );
		}

		if ( Input.Pressed( "Drop" ) )
		{
			TryDropCurrentWeapon();
		}

		UpdateEquipState();
	}

	/// <summary>
	/// Clears all weapons from a player's inventory.
	/// </summary>
	internal void Clear()
	{
		Fists?.GameObject?.DestroyImmediate();

		for ( int i = Weapons.Count - 1; i >= 0; i-- )
		{
			Weapons[i].Destroy();
		}

		Weapons.Clear();
		Current = null;
	}

	/// <summary>
	/// Get an inventory.
	/// </summary>
	/// <param name="type"></param>
	/// <returns></returns>
	public InventoryContainer GetInventory( ItemType type )
	{
		return Components.GetAll<InventoryContainer>().FirstOrDefault( x => x.ItemType == type );
	}

	/// <summary>
	/// does the player have an item in any inventory container?
	/// </summary>
	/// <param name="resource"></param>
	/// <returns></returns>
	public bool HasItem( InventoryItemResource resource )
	{
		foreach ( var inventory in Components.GetAll<InventoryContainer>() )
		{
			if ( inventory.HasItem( resource ) )
			{
				return true;
			}
		}

		return false;
	}

	public (InventoryContainer Container, int Index) FindItem( InventoryItemResource resource )
	{
		foreach ( var inventory in Components.GetAll<InventoryContainer>() )
		{
			if ( inventory.HasItem( resource ) )
			{
				var data = inventory.GetItem( resource );

				return (inventory, data.Index);
			}
		}

		return (null, -1);
	}
}