Files
GameJamDungeon/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs
2025-11-03 02:48:05 -08:00

428 lines
13 KiB
C#

using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using System.Linq;
using System.Threading.Tasks;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
public interface IInventoryMenu : IControl
{
public Task RefreshInventoryScreen();
public Task DisplayMessage(string message);
public void RemoveItem(InventoryItem item);
}
[Meta(typeof(IAutoNode))]
public partial class InventoryMenu : Control, IInventoryMenu
{
public override void _Notification(int what) => this.Notify(what);
[Dependency] private IGameRepo _gameRepo => this.DependOn<IGameRepo>();
[Dependency] public IGame Game => this.DependOn<IGame>();
[Dependency] public IPlayer Player => this.DependOn<IPlayer>();
private InventoryPageNumber _currentPageNumber = InventoryPageNumber.FirstPage;
private string ITEM_SLOT_SCENE = "res://src/ui/inventory_menu/ItemSlot.tscn";
private const int _itemsPerPage = 10;
private int _currentIndex = 0;
private IItemSlot[] ItemSlots => [.. ItemsPage.GetChildren().OfType<IItemSlot>()];
#region Control Nodes
[Node] public Label ATKValue { get; set; } = default!;
[Node] public Label ATKBonusLabel { get; set; } = default!;
[Node] public Label DEFValue { get; set; } = default!;
[Node] public Label DEFBonusLabel { get; set; } = default!;
[Node] public Label ItemDescriptionTitle { get; set; } = default!;
[Node] public Label ItemEffectLabel { get; set; } = default!;
// Item Menu
[Node] public Label BackArrow { get; set; } = default!;
[Node] public Label ForwardArrow { get; set; } = default!;
[Node] public Control ItemsPage { get; set; } = default!;
// User Prompt Menu
[Node] public Label UseItemPrompt { get; set; } = default!;
[Node] public Button UseButton { get; set; } = default!;
[Node] public Button ThrowButton { get; set; } = default!;
[Node] public Button DropButton { get; set; } = default!;
[Node] public AnimationPlayer AnimationPlayer { get; set; } = default!;
#endregion
public InventoryMenu()
{
SetProcessInput(false);
SetProcess(false);
}
public void OnResolved()
{
UseButton.Pressed += UseButtonPressed;
ThrowButton.Pressed += ThrowButtonPressed;
DropButton.Pressed += DropButtonPressed;
Player.AttackComponent.CurrentAttack.Sync += AttackSync;
Player.AttackComponent.MaximumAttack.Sync += AttackSync;
Player.EquipmentComponent.EquippedWeapon.Sync += BonusSync;
Player.EquipmentComponent.EquippedArmor.Sync += BonusSync;
Player.EquipmentComponent.EquippedAccessory.Sync += BonusSync;
Player.DefenseComponent.CurrentDefense.Sync += DefenseSync;
Player.DefenseComponent.MaximumDefense.Sync += DefenseSync;
}
public void OnExitTree()
{
Player.AttackComponent.CurrentAttack.Sync -= AttackSync;
Player.AttackComponent.MaximumAttack.Sync -= AttackSync;
Player.EquipmentComponent.EquippedWeapon.Sync -= BonusSync;
Player.EquipmentComponent.EquippedArmor.Sync -= BonusSync;
Player.EquipmentComponent.EquippedAccessory.Sync -= BonusSync;
Player.DefenseComponent.CurrentDefense.Sync -= DefenseSync;
Player.DefenseComponent.MaximumDefense.Sync -= DefenseSync;
}
public async Task DisplayMessage(string message)
{
SetProcessInput(false);
await HideUserActionPrompt();
await ShowInventoryInfo();
ItemEffectLabel.Text = message;
await ToSignal(GetTree().CreateTimer(1f), "timeout");
await RefreshInventoryScreen();
SetProcessInput(true);
}
private void AttackSync(int obj) => ATKValue.Text = $"{Player.AttackComponent.CurrentAttack.Value}/{Player.AttackComponent.MaximumAttack.Value}";
private void BonusSync(EquipableItem equip)
{
ATKBonusLabel.Text = $"{Player.EquipmentComponent.BonusAttack:+0;-#;\\.\\.\\.}";
DEFBonusLabel.Text = $"{Player.EquipmentComponent.BonusDefense:+0;-#;\\.\\.\\.}";
}
private void DefenseSync(int obj) => DEFValue.Text = $"{Player.DefenseComponent.CurrentDefense.Value}/{Player.DefenseComponent.MaximumDefense.Value}";
public async Task RefreshInventoryScreen()
{
await ClearItems();
PopulateInventory();
PopulatePlayerInfo();
await HideUserActionPrompt();
await ShowInventoryInfo();
}
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
public override void _Input(InputEvent @event)
{
if (Visible && @event.IsActionPressed(GameInputs.Inventory))
{
if (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
{
HideUserActionPrompt();
ShowInventoryInfo();
Autoload.AudioManager.Play(SoundEffect.Cancel);
}
else
{
AcceptEvent();
Autoload.AudioManager.Play(SoundEffect.Cancel);
_gameRepo.CloseInventory();
}
}
if (ItemSlots.Length == 0 || UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
return;
if (@event.IsActionPressed(GameInputs.MoveRight) && _currentPageNumber == InventoryPageNumber.FirstPage)
{
var inventory = Player.Inventory;
if (inventory.Items.Count > _itemsPerPage)
ChangeInventoryPage(InventoryPageNumber.SecondPage);
}
if (@event.IsActionPressed(GameInputs.MoveLeft) && _currentPageNumber == InventoryPageNumber.SecondPage)
ChangeInventoryPage(InventoryPageNumber.FirstPage);
if (@event.IsActionPressed(GameInputs.MoveDown))
{
var oldIndex = _currentIndex;
var newIndex = new[] { _currentIndex + 1, _itemsPerPage - 1, ItemSlots.Length - 1 }.Min();
if (oldIndex == newIndex)
return;
SetToUnselectedStyle(ItemSlots.ElementAt(oldIndex));
SetToSelectedStyle(ItemSlots.ElementAt(newIndex));
Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions);
_currentIndex = newIndex;
}
if (@event.IsActionPressed(GameInputs.MoveUp))
{
var oldIndex = _currentIndex;
var newIndex = new[] { _currentIndex - 1, 0 }.Max();
if (oldIndex == newIndex)
return;
SetToUnselectedStyle(ItemSlots.ElementAt(oldIndex));
SetToSelectedStyle(ItemSlots.ElementAt(newIndex));
Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions);
_currentIndex = newIndex;
}
if (@event.IsActionPressed(GameInputs.Attack))
{
DisplayUserActionPrompt();
Autoload.AudioManager.Play(SoundEffect.Select);
}
if (@event.IsActionPressed(GameInputs.InventorySort))
{
var inventory = Player.Inventory;
inventory.Sort(Player.EquipmentComponent.EquippedWeapon.Value, Player.EquipmentComponent.EquippedArmor.Value, Player.EquipmentComponent.EquippedAccessory.Value);
if (_currentIndex > inventory.Items.Count - 1)
_currentIndex = inventory.Items.Count - 1;
RefreshInventoryScreen();
}
}
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
public async void RemoveItem(InventoryItem item)
{
Player.Inventory.Remove(item);
if (_currentIndex >= ItemSlots.Length - 1)
_currentIndex--;
if (_currentIndex <= 0)
_currentIndex = 0;
}
private void PopulateItems()
{
PopulateInventory();
PopulatePlayerInfo();
}
private async Task ClearItems()
{
foreach (var item in ItemSlots)
ItemsPage.RemoveChildEx(item);
ItemDescriptionTitle.Text = string.Empty;
ItemEffectLabel.Text = string.Empty;
}
private void PopulatePlayerInfo()
{
if (ItemSlots.Length != 0)
{
var item = ItemSlots.ElementAt(_currentIndex).Item;
ItemDescriptionTitle.Text = $"{item.ItemName}";
ItemEffectLabel.Text = $"{item.Description}";
}
}
private void DisplayUserActionPrompt()
{
ItemDescriptionTitle.Hide();
ItemEffectLabel.Hide();
UseItemPrompt.Show();
UseButton.Show();
ThrowButton.Show();
DropButton.Show();
var currentItem = ItemSlots.ElementAt(_currentIndex).Item;
if (currentItem is EquipableItem equipable)
{
var isItemEquipped = Player.EquipmentComponent.IsItemEquipped(equipable);
UseButton.Text = isItemEquipped ? "Unequip" : "Equip";
ThrowButton.Disabled = isItemEquipped;
ThrowButton.FocusMode = isItemEquipped ? FocusModeEnum.None : FocusModeEnum.All;
DropButton.Disabled = isItemEquipped;
DropButton.FocusMode = isItemEquipped ? FocusModeEnum.None : FocusModeEnum.All;
}
else
{
UseButton.Text = "Use";
}
UseButton.CallDeferred(MethodName.GrabFocus);
}
private async Task HideUserActionPrompt()
{
UseItemPrompt.Hide();
UseButton.Hide();
ThrowButton.Hide();
DropButton.Hide();
UseButton.ReleaseFocus();
ThrowButton.ReleaseFocus();
DropButton.ReleaseFocus();
}
private async Task ShowInventoryInfo()
{
ItemDescriptionTitle.Show();
ItemEffectLabel.Show();
}
private async Task ChangeInventoryPage(InventoryPageNumber pageToChangeTo)
{
await ClearItems();
await ToSignal(GetTree().CreateTimer(0.1f), "timeout");
_currentIndex = 0;
_currentPageNumber = pageToChangeTo;
await RefreshInventoryScreen();
Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions);
}
private async void PopulateInventory()
{
var inventory = Player.Inventory;
var numberOfItemsToDisplay = _currentPageNumber == InventoryPageNumber.FirstPage ? Mathf.Min(inventory.Items.Count, _itemsPerPage) : Mathf.Min(inventory.Items.Count - _itemsPerPage, _itemsPerPage);
var indexToStart = _currentPageNumber == InventoryPageNumber.FirstPage ? 0 : _itemsPerPage;
ForwardArrow.Text = "";
BackArrow.Text = "";
if (_currentPageNumber == InventoryPageNumber.FirstPage && inventory.Items.Count > _itemsPerPage)
{
ForwardArrow.Text = "►";
BackArrow.Text = "";
}
if (_currentPageNumber == InventoryPageNumber.SecondPage)
{
ForwardArrow.Text = "";
BackArrow.Text = "◄";
}
for (var i = 0; i < numberOfItemsToDisplay; i++)
{
var item = inventory.Items.ElementAt(i + indexToStart);
var itemScene = GD.Load<PackedScene>(ITEM_SLOT_SCENE);
var itemSlot = itemScene.Instantiate<IItemSlot>();
itemSlot.Item = item;
ItemsPage.AddChildEx(itemSlot);
if (Player.EquipmentComponent.IsItemEquipped(itemSlot.Item))
itemSlot.SetEquippedItemStyle();
}
if (ItemSlots.Length != 0)
{
ItemSlots.ElementAt(_currentIndex).SetSelectedItemStyle();
if (Player.EquipmentComponent.IsItemEquipped(ItemSlots.ElementAt(_currentIndex).Item))
ItemSlots.ElementAt(_currentIndex).SetEquippedSelectedItemStyle();
}
}
private async Task SetToUnselectedStyle(IItemSlot itemSlot)
{
await ToSignal(GetTree().CreateTimer(0.1f), "timeout");
itemSlot.SetItemStyle();
if (Player.EquipmentComponent.IsItemEquipped(itemSlot.Item))
itemSlot.SetEquippedItemStyle();
}
private async Task SetToSelectedStyle(IItemSlot itemSlot)
{
await ToSignal(GetTree().CreateTimer(0.1f), "timeout");
itemSlot.SetSelectedItemStyle();
ItemDescriptionTitle.Text = $"{itemSlot.Item.ItemName}";
ItemEffectLabel.Text = $"{itemSlot.Item.Description}";
}
private async Task EquipOrUnequipItem()
{
var itemSlot = ItemSlots[_currentIndex];
if (itemSlot.Item is not EquipableItem)
return;
var equippableItem = (EquipableItem)itemSlot.Item;
if (Player.EquipmentComponent.IsItemEquipped(equippableItem))
{
ItemEffectLabel.Text = $"{equippableItem.GetType().Name} unequipped.";
Player.EquipmentComponent.Unequip(equippableItem);
itemSlot.SetSelectedItemStyle();
if (itemSlot.Item.ItemTag == ItemTag.BreaksOnChange)
Player.Inventory.Remove(equippableItem);
}
else
{
ItemEffectLabel.Text = $"{equippableItem.GetType().Name} equipped.";
Player.Equip(equippableItem);
itemSlot.SetEquippedSelectedItemStyle();
}
RefreshUIAfterUserSelection();
}
private async void UseButtonPressed()
{
UseButton.Disabled = true;
var currentItem = ItemSlots[_currentIndex].Item;
if (currentItem is EquipableItem)
await EquipOrUnequipItem();
else
await Game.UseItem(currentItem);
RefreshUIAfterUserSelection();
UseButton.Disabled = false;
}
private async void ThrowButtonPressed()
{
var currentItem = ItemSlots[_currentIndex].Item;
Game.ThrowItem(currentItem);
Player.Inventory.Remove(currentItem);
if (_currentIndex >= ItemSlots.Length - 1)
_currentIndex--;
if (_currentIndex <= 0)
_currentIndex = 0;
_gameRepo.CloseInventory();
}
private async void DropButtonPressed()
{
var currentItem = ItemSlots[_currentIndex].Item;
Game.DropItem(currentItem);
Player.Inventory.Remove(currentItem);
if (_currentIndex >= ItemSlots.Length - 1)
_currentIndex--;
if (_currentIndex <= 0)
_currentIndex = 0;
_gameRepo.CloseInventory();
}
private async void RefreshUIAfterUserSelection()
{
SetProcessInput(false);
await HideUserActionPrompt();
await ShowInventoryInfo();
await RefreshInventoryScreen();
await ToSignal(GetTree().CreateTimer(1f), "timeout");
SetProcessInput(true);
}
private enum InventoryPageNumber
{
FirstPage,
SecondPage
}
}