using System; using System.Collections; using System.Collections.Generic; using KitsuneCafe.System; using ObservableCollections; using UnityEngine; using Unit = KitsuneCafe.System.Unit; namespace KitsuneCafe.ItemSystem { [Serializable] public struct InventoryItem : IEquatable, ICountable { public Item Item; [SerializeField] private int count; public readonly int Count => count; private InventoryItem(Item item, int count) { Item = item; this.count = count; } public static int Create(Item item, int count, out InventoryItem? inventoryItem) { int quantity = Math.Clamp(count, 0, item.MaxStackCount); inventoryItem = quantity > 0 ? new InventoryItem(item, quantity) : null; return quantity; } public readonly int IncreaseCount(int count, out InventoryItem? item) { return Create(Item, Count + count, out item); } public readonly int ReduceCount(int count, out InventoryItem? item) { return Create(Item, Count - count, out item); } public readonly bool Equals(Item other) { return Item == other; } public override readonly bool Equals(object obj) { return obj is InventoryItem other && Item == other.Item && Count == other.Count; } public override int GetHashCode() { return HashCode.Combine(Item, Count); } } public enum InventoryError { None, InventoryFull, ItemNotFound, NotEnoughQuantity, ItemNotStackable, InvalidQuantity } public interface ICountable { int Count { get; } } public interface IInventory where T : IEquatable, ICountable { int Capacity { get; } int Count { get; } IResult Add(Item item, int count = 1); IResult Remove(Item item, int count = 1); IEnumerable Find(Func predicate); bool Has(Item item); } [CreateAssetMenu(menuName = KitsuneCafeMenu.ItemMenu + "Inventory")] public class Inventory : ScriptableObject, IInventory, IEnumerable, IObservableCollection, ISerializationCallbackReceiver { [SerializeField] private List serializableItems = new(); private ObservableList runtimeItems; [SerializeField] private int capacity = 8; public event NotifyCollectionChangedEventHandler CollectionChanged = default; public int Capacity => capacity; public int Count => runtimeItems?.Count ?? 0; public bool IsEmpty => Count == 0; public bool IsFull => Count == Capacity; public object SyncRoot => runtimeItems.SyncRoot; public InventoryItem this[int index] { get => runtimeItems[index]; set => runtimeItems[index] = value; } private void OnEnable() { if (runtimeItems == null) { runtimeItems = new ObservableList(); } runtimeItems.CollectionChanged -= OnCollectionChanged; runtimeItems.CollectionChanged += OnCollectionChanged; } private void OnDisable() { if (runtimeItems != null) { runtimeItems.CollectionChanged -= OnCollectionChanged; } } private void OnCollectionChanged(in NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(e); } public void OnBeforeSerialize() { if (runtimeItems != null) { serializableItems.Clear(); foreach (var item in runtimeItems) { serializableItems.Add(item); } } } public void OnAfterDeserialize() { if (runtimeItems == null) { runtimeItems = new ObservableList(); } else { runtimeItems.Clear(); } foreach (var item in serializableItems) { runtimeItems.Add(item); } } public IEnumerable Find(Func predicate) { for (int i = 0; i < Count; i++) { var item = runtimeItems[i]; if (predicate(item)) { yield return item; } } } private IResult AddToExisting(Item item, int count) { if (!item.IsStackable) { return Result.Err(InventoryError.ItemNotStackable); } if (count <= 0) { return Result.Err(InventoryError.InvalidQuantity); } for (int i = 0; i < Count; i++) { var existingItem = runtimeItems[i]; if (existingItem.Equals(item)) { var consumedAmount = existingItem.IncreaseCount(count, out var updatedItem); runtimeItems[i] = updatedItem.Value; count -= consumedAmount; if (count <= 0) { return Result.Ok(0); } } } return Result.Ok(count); } private IResult AddNew(Item item, int count) { while (count > 0) { if (IsFull) { return Result.Err(InventoryError.InventoryFull); } count -= InventoryItem.Create(item, count, out var newItem); runtimeItems.Add(newItem.Value); } return Result.Ok(default); } public IResult Add(Item item, int count = 1) { return AddToExisting(item, count) .Where(count => count > 0) .SelectMany(count => AddNew(item, count)) switch { Ok ok => ok, Err(InventoryError.None) => Result.Ok(default), var err => err }; } public IResult Remove(Item item, int count = 1) { if (count <= 0) { return Result.Err(InventoryError.InvalidQuantity); } for (int i = runtimeItems.Count - 1; i >= 0; i--) { var existingItem = runtimeItems[i]; if (existingItem.Item == item) { if (existingItem.Count > count) { var consumedAmount = existingItem.ReduceCount(count, out var updatedItem); runtimeItems[i] = updatedItem.Value; return Result.Ok(Unit.Default); } else { count -= existingItem.Count; runtimeItems.RemoveAt(i); if (count == 0) { return Result.Ok(Unit.Default); } } } } if (count > 0) { return Result.Err(InventoryError.NotEnoughQuantity); } return Result.Ok(Unit.Default); } public bool Has(Item item) { for (int i = 0; i < Count; i++) { if (runtimeItems[i].Equals(item)) { return true; } } return false; } public IEnumerator GetEnumerator() { return runtimeItems.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public ISynchronizedView CreateView(Func transform) { return runtimeItems.CreateView(transform); } } }