UI/Inventory/Inventory.razor
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits PanelComponent
@implements Global.IPlayerEvents

@if ( Player.IsValid() && Player.WantsHideHud )
    return;

@if ( !inventory.IsValid() )
    return;

<root>
	@for (int i = 0; i < inventory.MaxSlots; i++)
	{
		<InventorySlot Index=@i Inventory=@inventory Active=@(activeSlot == i)></InventorySlot>
	}
	<HotbarPresetsButton Inventory=@inventory></HotbarPresetsButton>
</root>

@code
{
    [Property, Group("Sound")] public SoundEvent SwitchSound { get; set; }

    Player Player => Player.FindLocalPlayer();

    PlayerInventory inventory;
    int activeSlot = -1;
    BaseCarryable prev;

    protected override int BuildHash() => HashCode.Combine( inventory, activeSlot, Player?.WantsHideHud );

    void Global.IPlayerEvents.OnPlayerPickup(PlayerPickupEvent e)
    {
        StateHasChanged();
    }

    bool IsSpawnMenuOpen()
    {
	    var host = Game.ActiveScene.Get<SpawnMenuHost>();
	    return host?.Panel?.HasClass("open") ?? false;
    }

    protected override void OnUpdate()
    {
        inventory = Game.ActiveScene.GetAllComponents<PlayerInventory>().Where(x => x.Network.IsOwner).FirstOrDefault();
        activeSlot = inventory?.ActiveWeapon?.InventorySlot ?? -1;

        Panel.SetClass( "spawnmenu-open", IsSpawnMenuOpen() );
    }

    public void HandleInput()
    {
        if (inventory is null)
            return;

        MoveSlot(-(int)Input.MouseWheel.y);

        if ( Input.Pressed( "invprev" ) && prev.IsValid() )
        {
            var weapon = prev;
            prev = inventory.ActiveWeapon;
            inventory.SwitchWeapon( weapon );
        }

        if (Input.Pressed("SlotNext")) MoveSlot(1);
        if (Input.Pressed("SlotPrev")) MoveSlot(-1);

        if (Input.Pressed("Slot1")) SelectSlot(0);
        if (Input.Pressed("Slot2")) SelectSlot(1);
        if (Input.Pressed("Slot3")) SelectSlot(2);
        if (Input.Pressed("Slot4")) SelectSlot(3);
        if (Input.Pressed("Slot5")) SelectSlot(4);
        if (Input.Pressed("Slot6")) SelectSlot(5);
        if (Input.Pressed("Slot7")) SelectSlot(6);
        if (Input.Pressed("Slot8")) SelectSlot(7);
        if (Input.Pressed("Slot9")) SelectSlot(8);
    }

    /// <summary>
    /// Pressing a number key directly switches to that slot's weapon.
    /// If the slot is empty or already active, holsters the current weapon.
    /// </summary>
    public void SelectSlot( int slot )
    {
        var weapon = inventory.GetSlot( slot );

        if ( weapon.IsValid() && !weapon.CanSwitch() )
            return;

        prev = inventory.ActiveWeapon;
        inventory.SwitchWeapon( weapon, allowHolster: true );

        Sound.Play( SwitchSound );
    }

    public void MoveSlot( int delta )
    {
        if ( delta == 0 )
            return;

        var weapons = inventory.Weapons.Where( x => x.CanSwitch() ).ToList();

        if ( weapons.Count == 0 )
            return;

        // Find current position in the ordered weapon list
        BaseCarryable current = inventory.ActiveWeapon ?? weapons.FirstOrDefault();

        int currentIndex = weapons.IndexOf( current );
        currentIndex += delta;
        currentIndex %= weapons.Count;
        if ( currentIndex < 0 )
            currentIndex = weapons.Count + currentIndex;

        var target = weapons[currentIndex];

        prev = inventory.ActiveWeapon;
        inventory.SwitchWeapon( target );

        Sound.Play( SwitchSound );
    }
}