#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 { private List _slots = new(); public IReadOnlyList Slots => _slots; [Signal] public delegate void SlotAmountChangedEventHandler(); [Signal] public delegate void InventoryContentsChangedEventHandler(); private const string SCENE_NAME = "inventory"; private const string ID = "instace"; /// /// 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()); } } EmitSignal(SignalName.SlotAmountChanged); } } public override void _EnterTree() { LoadFromSaveData(); InventoryContentsChanged += UpdateSaveData; SlotAmountChanged += UpdateSaveData; } public override void _ExitTree() { InventoryContentsChanged -= UpdateSaveData; SlotAmountChanged -= UpdateSaveData; } 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 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 items) { return items.All(HasItems); } #region SAVE AND LOAD public void UpdateSaveData() { var saveData = new SaveData(); saveData.SceneName = SCENE_NAME; saveData.Id = ID; var payloadData = new Godot.Collections.Dictionary(); 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); } } saveData.JsonPayload = Json.Stringify(payloadData, indent: "\t"); SavegameService.AppendSave(saveData); } public void LoadFromSaveData() { var sceneName = SCENE_NAME; var id = ID; string jsonPayload = SavegameService.GetSaveData(sceneName, id); if (string.IsNullOrEmpty(jsonPayload)) return; Godot.Collections.Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); 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(savePayload[0]); int _amount = int.Parse(savePayload[1]); ItemInstance instance = new ItemInstance { blueprint = resource, amount = _amount }; AddItem(instance); } } } } #endregion }