using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using KitsuneCafe.Sys; using ObservableCollections; using UnityEngine; using Unit = KitsuneCafe.Sys.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.Item + "Inventory")] public class Inventory : ScriptableObject, IList, IInventory, IEnumerable { [SerializeField] private List items = new(); [SerializeField] private int capacity = 8; public event NotifyCollectionChangedEventHandler CollectionChanged; public int Capacity => capacity; public int Count => items.Count; public bool IsEmpty => Count == 0; public bool IsFull => Count == Capacity; public object SyncRoot => ((IList)items).SyncRoot; public bool IsFixedSize => ((IList)items).IsFixedSize; public bool IsReadOnly => ((IList)items).IsReadOnly; public bool IsSynchronized => ((ICollection)items).IsSynchronized; object IList.this[int index] { get => items[index]; set => items[index] = (InventoryItem)value; } public InventoryItem this[int index] { get => items[index]; set => items[index] = value; } public IEnumerable Find(Func predicate) { for (int i = 0; i < Count; i++) { var item = items[i]; if (predicate(item)) { yield return item; } } } private IResult AddToExisting(Item item, int count) { if (count <= 0) { return Result.Err(InventoryError.InvalidQuantity); } List existing = new(); List updated = new(); for (int i = 0; i < Count; i++) { var existingItem = items[i]; existing.Add(existingItem); if (existingItem.Equals(item)) { if (!item.IsStackable) { return Result.Err(InventoryError.ItemNotStackable); } var consumedAmount = existingItem.IncreaseCount(count, out var updatedItem); updated.Add(updatedItem.Value); items[i] = updatedItem.Value; count -= consumedAmount; if (count <= 0) { return Result.Ok(0); } } } Notify(NotifyCollectionChangedAction.Replace, updated, existing); return Result.Ok(count); } private IResult AddNew(Item item, int count) { List added = new(); while (count > 0) { if (IsFull) { return Result.Err(InventoryError.InventoryFull); } count -= InventoryItem.Create(item, count, out var newItem); items.Add(newItem.Value); added.Add(newItem.Value); } Notify(NotifyCollectionChangedAction.Add, added); 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 }; } private void Notify(NotifyCollectionChangedAction action, IList newItems = default, IList oldItems = default) { NotifyCollectionChangedEventArgs args; if (newItems.Count == 1) { args = new( action, true, newItems[0], oldItems[0] ); } else { args = new( action, false, newItems: newItems.ToArray(), oldItems: oldItems.ToArray() ); } CollectionChanged?.Invoke(args); } public IResult Remove(Item item, int count = 1) { if (count <= 0) { return Result.Err(InventoryError.InvalidQuantity); } List removed = new(); List updated = new(); for (int i = items.Count - 1; i >= 0; i--) { var existingItem = items[i]; if (existingItem.Item == item) { if (existingItem.Count > count) { existingItem.ReduceCount(count, out var updatedItem); items[i] = updatedItem.Value; removed.Add(existingItem); updated.Add(updatedItem.Value); Notify(NotifyCollectionChangedAction.Remove, updated, removed); return Result.Ok(Unit.Default); } else { count -= existingItem.Count; items.RemoveAt(i); removed.Add(existingItem); if (count == 0) { Notify(NotifyCollectionChangedAction.Remove, updated, removed); return Result.Ok(Unit.Default); } } } } if (count > 0) { return Result.Err(InventoryError.NotEnoughQuantity); } Notify(NotifyCollectionChangedAction.Remove, updated, removed); return Result.Ok(Unit.Default); } public bool Has(Item item) { for (int i = 0; i < Count; i++) { if (items[i].Equals(item)) { return true; } } return false; } public IEnumerator GetEnumerator() { return items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public int Add(object value) { return ((IList)items).Add(value); } public void Clear() { ((IList)items).Clear(); } public bool Contains(object value) { return ((IList)items).Contains(value); } public int IndexOf(object value) { return ((IList)items).IndexOf(value); } public void Insert(int index, object value) { ((IList)items).Insert(index, value); } public void Remove(object value) { ((IList)items).Remove(value); } public void RemoveAt(int index) { ((IList)items).RemoveAt(index); } public void CopyTo(Array array, int index) { ((ICollection)items).CopyTo(array, index); } // public ISynchronizedView CreateView(Func transform) // { // } } public class InventoryView : ISynchronizedView { private Inventory inventory; public object SyncRoot => inventory.SyncRoot; private ISynchronizedViewFilter filter; public ISynchronizedViewFilter Filter => filter; public IEnumerable<(InventoryItem Value, TView View)> Filtered => throw new NotImplementedException(); public IEnumerable<(InventoryItem Value, TView View)> Unfiltered => throw new NotImplementedException(); public int UnfilteredCount => throw new NotImplementedException(); public int Count => throw new NotImplementedException(); public event NotifyViewChangedEventHandler ViewChanged; public event Action RejectedViewChanged; public event Action CollectionStateChanged; public void AttachFilter(ISynchronizedViewFilter filter) { throw new NotImplementedException(); } public void Dispose() { throw new NotImplementedException(); } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } public void ResetFilter() { throw new NotImplementedException(); } public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { throw new NotImplementedException(); } public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher collectionEventDispatcher) { throw new NotImplementedException(); } public ISynchronizedViewList ToViewList() { throw new NotImplementedException(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }