454 lines
14 KiB
C#
454 lines
14 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>();
|
|
|
|
[Dependency] public IMap _map => this.DependOn<IMap>();
|
|
|
|
[Dependency] public IGameEventDepot GameEventDepot => this.DependOn<IGameEventDepot>();
|
|
|
|
private InventoryPageNumber _currentPageNumber = InventoryPageNumber.FirstPage;
|
|
|
|
private string ITEM_SLOT_SCENE = "res://src/inventory_menu/ItemSlot.tscn";
|
|
|
|
private const int _itemsPerPage = 10;
|
|
|
|
private int _currentIndex = 0;
|
|
|
|
private IItemSlot[] ItemSlots => ItemsPage.GetChildren().OfType<IItemSlot>().ToArray();
|
|
|
|
#region Control Nodes
|
|
[Node] public Label FloorLabel { get; set; } = default!;
|
|
[Node] public Label CurrentLevelLabel { get; set; } = default!;
|
|
[Node] public Label EXPValue { get; set; } = default!;
|
|
[Node] public Label HPValue { get; set; } = default!;
|
|
[Node] public Label HPBonusLabel { get; set; } = default!;
|
|
[Node] public Label VTValue { get; set; } = default!;
|
|
[Node] public Label VTBonusLabel { get; set; } = default!;
|
|
[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 void OnReady()
|
|
{
|
|
UseButton.Pressed += UseButtonPressed;
|
|
ThrowButton.Pressed += ThrowButtonPressed;
|
|
DropButton.Pressed += DropButtonPressed;
|
|
}
|
|
|
|
public void OnResolved()
|
|
{
|
|
Player.Stats.CurrentHP.Sync += CurrentHP_Sync;
|
|
Player.Stats.MaximumHP.Sync += MaximumHP_Sync;
|
|
Player.Stats.CurrentVT.Sync += CurrentVT_Sync;
|
|
Player.Stats.MaximumVT.Sync += MaximumVT_Sync;
|
|
Player.Stats.CurrentAttack.Sync += CurrentAttack_Sync;
|
|
Player.Stats.MaxAttack.Sync += MaxAttack_Sync;
|
|
Player.Stats.CurrentDefense.Sync += CurrentDefense_Sync;
|
|
Player.Stats.MaxDefense.Sync += MaxDefense_Sync;
|
|
Player.Stats.CurrentExp.Sync += CurrentExp_Sync;
|
|
Player.Stats.ExpToNextLevel.Sync += ExpToNextLevel_Sync;
|
|
Player.Stats.CurrentLevel.Sync += CurrentLevel_Sync;
|
|
Player.Stats.BonusAttack.Sync += BonusAttack_Sync;
|
|
Player.Stats.BonusDefense.Sync += BonusDefense_Sync;
|
|
|
|
SetProcessInput(false);
|
|
}
|
|
|
|
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 BonusAttack_Sync(int bonus)
|
|
{
|
|
ATKBonusLabel.Text = $"{bonus:+0;-#;\\.\\.\\.}";
|
|
DEFBonusLabel.Text = $"{Player.Stats.BonusDefense.Value:+0;-#;\\.\\.\\.}";
|
|
}
|
|
|
|
private void BonusDefense_Sync(int bonus)
|
|
{
|
|
ATKBonusLabel.Text = $"{Player.Stats.BonusAttack.Value:+0;-#;\\.\\.\\.}";
|
|
DEFBonusLabel.Text = $"{bonus:+0;-#;\\.\\.\\.}";
|
|
}
|
|
|
|
private void CurrentLevel_Sync(int obj) => CurrentLevelLabel.Text = $"Level {obj:D2}";
|
|
|
|
private void ExpToNextLevel_Sync(int obj) => EXPValue.Text = $"{Player.Stats.CurrentExp.Value}/{obj}";
|
|
|
|
private void CurrentExp_Sync(double obj) => EXPValue.Text = $"{obj}/{Player.Stats.ExpToNextLevel.Value}";
|
|
|
|
private void MaxDefense_Sync(int obj) => DEFValue.Text = $"{Player.Stats.CurrentDefense.Value}/{obj}";
|
|
|
|
private void CurrentDefense_Sync(int obj) => DEFValue.Text = $"{obj}/{Player.Stats.MaxDefense.Value}";
|
|
|
|
private void MaxAttack_Sync(int obj) => ATKValue.Text = $"{Player.Stats.CurrentAttack.Value}/{obj}";
|
|
|
|
private void CurrentAttack_Sync(int obj) => ATKValue.Text = $"{obj}/{Player.Stats.MaxAttack.Value}";
|
|
|
|
private void MaximumVT_Sync(int obj) => VTValue.Text = $"{Player.Stats.CurrentVT.Value}/{obj}";
|
|
|
|
private void CurrentVT_Sync(int obj) => VTValue.Text = $"{obj}/{Player.Stats.MaximumVT.Value}";
|
|
|
|
private void MaximumHP_Sync(int obj) => HPValue.Text = $"{Player.Stats.CurrentHP.Value}/{obj}";
|
|
|
|
private void CurrentHP_Sync(int obj) => HPValue.Text = $"{obj}/{Player.Stats.MaximumHP.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)
|
|
{
|
|
var inventory = Player.Inventory;
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiCancel))
|
|
{
|
|
if (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
|
|
{
|
|
HideUserActionPrompt();
|
|
ShowInventoryInfo();
|
|
GameEventDepot.OnMenuBackedOut();
|
|
}
|
|
else
|
|
{
|
|
_gameRepo.CloseInventory();
|
|
}
|
|
}
|
|
|
|
if (ItemSlots.Length == 0 || UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())
|
|
return;
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiRight) && _currentPageNumber == InventoryPageNumber.FirstPage && inventory.Items.Count > _itemsPerPage)
|
|
ChangeInventoryPage(InventoryPageNumber.SecondPage);
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiLeft) && _currentPageNumber == InventoryPageNumber.SecondPage)
|
|
ChangeInventoryPage(InventoryPageNumber.FirstPage);
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiDown))
|
|
{
|
|
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));
|
|
GameEventDepot.OnMenuScrolled();
|
|
_currentIndex = newIndex;
|
|
}
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiUp))
|
|
{
|
|
var oldIndex = _currentIndex;
|
|
var newIndex = new[] { _currentIndex - 1, 0 }.Max();
|
|
|
|
if (oldIndex == newIndex)
|
|
return;
|
|
|
|
SetToUnselectedStyle(ItemSlots.ElementAt(oldIndex));
|
|
SetToSelectedStyle(ItemSlots.ElementAt(newIndex));
|
|
GameEventDepot.OnMenuScrolled();
|
|
_currentIndex = newIndex;
|
|
}
|
|
|
|
if (@event.IsActionPressed(GameInputs.UiAccept))
|
|
{
|
|
DisplayUserActionPrompt();
|
|
}
|
|
|
|
if (@event.IsActionPressed(GameInputs.InventorySort))
|
|
{
|
|
inventory.Sort();
|
|
if (_currentIndex > inventory.Items.Count - 1)
|
|
_currentIndex = inventory.Items.Count - 1;
|
|
GameEventDepot.OnInventorySorted();
|
|
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()
|
|
{
|
|
FloorLabel.Text = $"Floor {_map.CurrentFloorNumber:D2}";
|
|
|
|
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)
|
|
{
|
|
UseButton.Text = equipable.IsEquipped ? "Unequip" : "Equip";
|
|
ThrowButton.Disabled = equipable.IsEquipped;
|
|
ThrowButton.FocusMode = equipable.IsEquipped ? FocusModeEnum.None : FocusModeEnum.All;
|
|
DropButton.Disabled = equipable.IsEquipped;
|
|
DropButton.FocusMode = equipable.IsEquipped ? 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();
|
|
GameEventDepot.OnMenuScrolled();
|
|
}
|
|
|
|
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 (itemSlot.Item is EquipableItem equipable && equipable.IsEquipped)
|
|
itemSlot.SetEquippedItemStyle();
|
|
}
|
|
|
|
if (ItemSlots.Length != 0)
|
|
{
|
|
ItemSlots.ElementAt(_currentIndex).SetSelectedItemStyle();
|
|
if (ItemSlots.ElementAt(_currentIndex).Item is EquipableItem equipable && equipable.IsEquipped)
|
|
ItemSlots.ElementAt(_currentIndex).SetEquippedSelectedItemStyle();
|
|
}
|
|
}
|
|
|
|
private async Task SetToUnselectedStyle(IItemSlot itemSlot)
|
|
{
|
|
await ToSignal(GetTree().CreateTimer(0.1f), "timeout");
|
|
itemSlot.SetItemStyle();
|
|
if (itemSlot.Item is EquipableItem equipable && equipable.IsEquipped)
|
|
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 EquipableItem equipableItem)
|
|
{
|
|
if (equipableItem.IsEquipped)
|
|
{
|
|
ItemEffectLabel.Text = $"{itemSlot.Item.GetType()} unequipped.";
|
|
Player.Unequip(equipableItem);
|
|
itemSlot.SetSelectedItemStyle();
|
|
if (equipableItem.ItemTag == ItemTag.BreaksOnChange)
|
|
Player.Inventory.Remove(equipableItem);
|
|
}
|
|
else
|
|
{
|
|
ItemEffectLabel.Text = $"{itemSlot.Item.GetType()} equipped.";
|
|
Player.Equip(equipableItem);
|
|
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
|
|
}
|
|
}
|