#nullable enable using System; using Godot; using System.Collections.Generic; using System.Linq; using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.GameEntity.LoadSave; using Newtonsoft.Json.Linq; namespace Babushka.scripts.CSharp.Common.Inventory; public partial class InventoryInstance : IJsonSerializable { private readonly List _slots; public IReadOnlyList Slots => _slots; public event Action? SlotAmountChanged; public event Action? InventoryContentsChanged; /// /// The total amount of InventorySlots in the inventory (empty and occupied). /// [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()); } } SlotAmountChanged?.Invoke(); } } public InventoryInstance(int slotCount) { _slots = new(); SlotAmount = slotCount; } public InventoryActionResult AddItem(ItemInstance newItem) { var result = AddItemAndStackRecursive(newItem, 0); InventoryContentsChanged?.Invoke(); 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; InventoryContentsChanged?.Invoke(); 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; } InventoryContentsChanged?.Invoke(); 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; InventoryContentsChanged?.Invoke(); 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 items) { return items.All(HasItems); } public void LoadFromJson(JObject json) { var itemsArray = (JArray?)json["items"]; if (itemsArray == null) return; foreach (var (itemToken, slot) in itemsArray.Zip(_slots)) { var itemObj = (JObject?)itemToken; if (itemObj == null) continue; slot.LoadFromJson(itemObj); } InventoryContentsChanged?.Invoke(); } public JObject SaveToJson() { return new JObject { ["items"] = new JArray(_slots.Select(s => s.SaveToJson())) }; } }