You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
7.9 KiB
268 lines
7.9 KiB
#nullable enable
|
|
using System;
|
|
using Godot;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Babushka.scripts.CSharp.Common.Savegame;
|
|
|
|
namespace Babushka.scripts.CSharp.Common.Inventory;
|
|
|
|
public partial class InventoryInstance : Node, ISaveable
|
|
{
|
|
private List<InventorySlot> _slots = new();
|
|
public IReadOnlyList<InventorySlot> Slots => _slots;
|
|
|
|
[Signal]
|
|
public delegate void SlotAmountChangedEventHandler();
|
|
|
|
[Signal]
|
|
public delegate void InventoryContentsChangedEventHandler();
|
|
|
|
public static string ID = "inventoryInstance";
|
|
|
|
/// <summary>
|
|
/// The total amount of Inventoryslots in the inventory (empty and occupied).
|
|
/// </summary>
|
|
[Export]
|
|
public int SlotAmount
|
|
{
|
|
get => _slots.Count;
|
|
set
|
|
{
|
|
if (value < _slots.Count)
|
|
{
|
|
_slots.RemoveRange(value, _slots.Count - value);
|
|
}
|
|
else if (value > _slots.Count)
|
|
{
|
|
for (var i = _slots.Count; i < value; i++)
|
|
{
|
|
_slots.Add(new InventorySlot());
|
|
}
|
|
}
|
|
|
|
EmitSignal(SignalName.SlotAmountChanged);
|
|
}
|
|
}
|
|
|
|
public override void _EnterTree()
|
|
{
|
|
LoadFromSaveData();
|
|
InventoryContentsChanged += UpdateSaveData;
|
|
SlotAmountChanged += UpdateSaveData;
|
|
SavegameService.OnSaveGameReset += SaveGameReset;
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
InventoryContentsChanged -= UpdateSaveData;
|
|
SlotAmountChanged -= UpdateSaveData;
|
|
SavegameService.OnSaveGameReset -= SaveGameReset;
|
|
}
|
|
|
|
public InventoryActionResult AddItem(ItemInstance newItem)
|
|
{
|
|
var result = AddItemAndStackRecursive(newItem, 0);
|
|
EmitSignal(SignalName.InventoryContentsChanged);
|
|
return result;
|
|
}
|
|
|
|
private InventoryActionResult AddItemAndStackRecursive(ItemInstance newItem, int slotSearch)
|
|
{
|
|
if (newItem.blueprint == null || newItem.amount == 0)
|
|
return InventoryActionResult.SourceDoesNotExist;
|
|
|
|
var slotIndex = -1;
|
|
// find stackable slot
|
|
for (var i = slotSearch; i < _slots.Count; i++)
|
|
{
|
|
if (_slots[i].itemInstance?.blueprint == newItem.blueprint)
|
|
{
|
|
slotIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slotIndex < 0)
|
|
{
|
|
// find empty slot
|
|
for (var i = slotSearch; i < _slots.Count; i++)
|
|
{
|
|
if (_slots[i].IsEmpty())
|
|
{
|
|
slotIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slotIndex < 0)
|
|
{
|
|
return InventoryActionResult.DestinationFull;
|
|
}
|
|
|
|
var itemInstance = _slots[slotIndex].itemInstance ??
|
|
new ItemInstance { blueprint = newItem.blueprint, amount = 0 };
|
|
var maxStack = itemInstance!.blueprint.maxStack;
|
|
var freeOnStack = maxStack - itemInstance.amount;
|
|
var moveAmount = Math.Min(freeOnStack, newItem.amount);
|
|
itemInstance.amount += moveAmount;
|
|
newItem.amount -= moveAmount;
|
|
_slots[slotIndex].itemInstance = itemInstance;
|
|
return newItem.amount <= 0
|
|
? InventoryActionResult.Success
|
|
: AddItemAndStackRecursive(newItem, slotIndex + 1);
|
|
}
|
|
|
|
public InventoryActionResult RemoveItem(int inventorySlot, out ItemInstance? itemInstance)
|
|
{
|
|
if (inventorySlot < 0 || inventorySlot >= _slots.Count)
|
|
{
|
|
itemInstance = null;
|
|
return InventoryActionResult.SourceDoesNotExist;
|
|
}
|
|
|
|
if (_slots[inventorySlot].IsEmpty())
|
|
{
|
|
itemInstance = null;
|
|
return InventoryActionResult.SourceIsEmpty;
|
|
}
|
|
|
|
itemInstance = _slots[inventorySlot].itemInstance;
|
|
if (itemInstance == null)
|
|
return InventoryActionResult.SourceDoesNotExist;
|
|
|
|
itemInstance.amount -= 1;
|
|
|
|
if (itemInstance.amount == 0)
|
|
_slots[inventorySlot].itemInstance = null;
|
|
|
|
EmitSignal(SignalName.InventoryContentsChanged);
|
|
return InventoryActionResult.Success;
|
|
}
|
|
|
|
public InventoryActionResult RemoveItem(int inventorySlot)
|
|
{
|
|
return RemoveItem(inventorySlot, out _);
|
|
}
|
|
|
|
public InventoryActionResult TryRemoveAllItems(ItemInstance items)
|
|
{
|
|
var hasItemsCount = TotalItemsOfBlueprint(items.blueprint);
|
|
if (hasItemsCount < items.amount)
|
|
return InventoryActionResult.SourceDoesNotExist;
|
|
|
|
var amountToRemove = items.amount;
|
|
foreach (var s in _slots)
|
|
{
|
|
if (s.IsEmpty() || s.itemInstance!.blueprint != items.blueprint)
|
|
continue;
|
|
|
|
var slotItem = s.itemInstance!;
|
|
if (slotItem.amount <= amountToRemove)
|
|
{
|
|
amountToRemove -= slotItem.amount;
|
|
s.itemInstance = null;
|
|
}
|
|
else
|
|
{
|
|
slotItem.amount -= amountToRemove;
|
|
amountToRemove = 0;
|
|
}
|
|
|
|
if (amountToRemove == 0)
|
|
break;
|
|
}
|
|
|
|
EmitSignal(SignalName.InventoryContentsChanged);
|
|
return InventoryActionResult.Success;
|
|
}
|
|
|
|
public InventoryActionResult AddItemToSlot(ItemInstance itemInstance, int destinationSlot)
|
|
{
|
|
if (destinationSlot < 0 || destinationSlot >= _slots.Count)
|
|
return InventoryActionResult.DestinationDoesNotExist;
|
|
|
|
if (!_slots[destinationSlot].IsEmpty())
|
|
return InventoryActionResult.DestinationFull;
|
|
|
|
_slots[destinationSlot].itemInstance = itemInstance;
|
|
EmitSignal(SignalName.InventoryContentsChanged);
|
|
return InventoryActionResult.Success;
|
|
}
|
|
|
|
public int TotalItemsOfBlueprint(ItemResource blueprint)
|
|
{
|
|
return _slots
|
|
.Where(i => !i.IsEmpty() && i.itemInstance!.blueprint == blueprint)
|
|
.Sum(i => i.itemInstance!.amount);
|
|
}
|
|
|
|
public bool HasItems(ItemInstance item)
|
|
{
|
|
return TotalItemsOfBlueprint(item.blueprint) >= item.amount;
|
|
}
|
|
|
|
public bool HasItems(IEnumerable<ItemInstance> items)
|
|
{
|
|
return items.All(HasItems);
|
|
}
|
|
|
|
#region SAVE AND LOAD
|
|
|
|
public void UpdateSaveData()
|
|
{
|
|
var payloadData = new Godot.Collections.Dictionary<string, Variant>();
|
|
|
|
for (int i = 0; i < _slots.Count; i++)
|
|
{
|
|
if (!_slots[i].IsEmpty())
|
|
{
|
|
string key = i.ToString();
|
|
string[] value = new string[2];
|
|
value[0] = _slots[i].itemInstance.blueprint.ResourcePath;
|
|
value[1] = _slots[i].itemInstance.amount.ToString();
|
|
payloadData.Add(key, value);
|
|
}
|
|
}
|
|
|
|
SavegameService.AppendDataToSave(ID, payloadData);
|
|
}
|
|
|
|
public void LoadFromSaveData()
|
|
{
|
|
var id = ID;
|
|
|
|
Godot.Collections.Dictionary<string, Variant> save = SavegameService.GetSaveData(id);
|
|
|
|
if (save.Count > 0)
|
|
{
|
|
for (int i = 0; i < _slots.Count; i++)
|
|
{
|
|
if (save.TryGetValue(i.ToString(), out Variant inventoryItemData))
|
|
{
|
|
string[] savePayload = inventoryItemData.AsStringArray();
|
|
ItemResource resource = ResourceLoader.Load<ItemResource>(savePayload[0]);
|
|
int _amount = int.Parse(savePayload[1]);
|
|
|
|
ItemInstance instance = new ItemInstance { blueprint = resource, amount = _amount };
|
|
AddItem(instance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a new save is created.
|
|
/// Needs to do a runtime check because the InventoryInstance is already in existence at the beginning of the first scene.
|
|
/// </summary>
|
|
private void SaveGameReset()
|
|
{
|
|
foreach (var slot in _slots)
|
|
{
|
|
slot.itemInstance = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} |