using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using KitsuneCafe.Sys; using MessagePack; using ObservableCollections; using UnityEngine; using Unit = KitsuneCafe.Sys.Unit; namespace KitsuneCafe.ItemSystem { [MessagePackObject(AllowPrivate = true)] [Serializable] public partial struct InventoryItem : IEquatable, ICountable { [Key(0)] public Item Item; [Key(1)] [SerializeField] private int count; [IgnoreMember] public readonly int Count => count; internal 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); } [MessagePackObject(AllowPrivate = true)] [CreateAssetMenu(menuName = KitsuneCafeMenu.Item + "Inventory")] public partial class Inventory : ScriptableObject, IList, IInventory, IEnumerable { [Key(0)] [SerializeField] private List items = new(); [IgnoreMember] public IReadOnlyList Items => items; [IgnoreMember] [SerializeField] private int capacity = 8; public event NotifyCollectionChangedEventHandler CollectionChanged; [IgnoreMember] public int Capacity => capacity; [IgnoreMember] public int Count => items.Count; [IgnoreMember] public bool IsEmpty => Count == 0; [IgnoreMember] public bool IsFull => Count == Capacity; [IgnoreMember] public object SyncRoot => ((IList)items).SyncRoot; [IgnoreMember] public bool IsFixedSize => ((IList)items).IsFixedSize; [IgnoreMember] public bool IsReadOnly => ((IList)items).IsReadOnly; [IgnoreMember] public bool IsSynchronized => ((ICollection)items).IsSynchronized; [IgnoreMember] object IList.this[int index] { get => items[index]; set => items[index] = (InventoryItem)value; } [IgnoreMember] public InventoryItem this[int index] { get => items[index]; set => items[index] = value; } public void Load(Inventory inventory) { this.items = inventory.items; } public void Load(IList items) { this.items = items.ToList(); } public bool TryGetValue(int index, out InventoryItem item) { if (0 <= index && index < Count) { item = items[index]; return true; } else { item = default; return false; } } 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 != null && oldItems.Count > 0 ? oldItems[0] : default ); } 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); } } }