357 lines
8.4 KiB
C#
357 lines
8.4 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using KitsuneCafe.Sys;
|
|
using ObservableCollections;
|
|
using UnityEngine;
|
|
using Unit = KitsuneCafe.Sys.Unit;
|
|
|
|
namespace KitsuneCafe.ItemSystem
|
|
{
|
|
[Serializable]
|
|
public struct InventoryItem : IEquatable<Item>, 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<T> where T : IEquatable<Item>, ICountable
|
|
{
|
|
int Capacity { get; }
|
|
int Count { get; }
|
|
|
|
IResult<Unit, InventoryError> Add(Item item, int count = 1);
|
|
IResult<Unit, InventoryError> Remove(Item item, int count = 1);
|
|
IEnumerable<T> Find(Func<T, bool> predicate);
|
|
bool Has(Item item);
|
|
}
|
|
|
|
[CreateAssetMenu(menuName = KitsuneCafeMenu.Item + "Inventory")]
|
|
public class Inventory : ScriptableObject, IList, IInventory<InventoryItem>, IEnumerable<InventoryItem>, IObservableCollection<InventoryItem>, ISerializationCallbackReceiver
|
|
{
|
|
[SerializeField]
|
|
private List<InventoryItem> serializableItems = new();
|
|
|
|
private ObservableList<InventoryItem> runtimeItems;
|
|
|
|
[SerializeField]
|
|
private int capacity = 8;
|
|
|
|
public event NotifyCollectionChangedEventHandler<InventoryItem> 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 bool IsFixedSize => ((IList)runtimeItems).IsFixedSize;
|
|
|
|
public bool IsReadOnly => ((IList)runtimeItems).IsReadOnly;
|
|
|
|
public bool IsSynchronized => ((ICollection)runtimeItems).IsSynchronized;
|
|
|
|
object IList.this[int index] { get => runtimeItems[index]; set => runtimeItems[index] = (InventoryItem)value; }
|
|
|
|
public InventoryItem this[int index]
|
|
{
|
|
get => runtimeItems[index];
|
|
set => runtimeItems[index] = value;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (runtimeItems == null)
|
|
{
|
|
runtimeItems = new ObservableList<InventoryItem>();
|
|
}
|
|
|
|
runtimeItems.CollectionChanged -= OnCollectionChanged;
|
|
runtimeItems.CollectionChanged += OnCollectionChanged;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (runtimeItems != null)
|
|
{
|
|
runtimeItems.CollectionChanged -= OnCollectionChanged;
|
|
}
|
|
}
|
|
|
|
private void OnCollectionChanged(in NotifyCollectionChangedEventArgs<InventoryItem> 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<InventoryItem>();
|
|
}
|
|
else
|
|
{
|
|
runtimeItems.Clear();
|
|
}
|
|
|
|
foreach (var item in serializableItems)
|
|
{
|
|
runtimeItems.Add(item);
|
|
}
|
|
}
|
|
|
|
public IEnumerable<InventoryItem> Find(Func<InventoryItem, bool> predicate)
|
|
{
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
var item = runtimeItems[i];
|
|
|
|
if (predicate(item))
|
|
{
|
|
yield return item;
|
|
}
|
|
}
|
|
}
|
|
|
|
private IResult<int, InventoryError> AddToExisting(Item item, int count)
|
|
{
|
|
if (count <= 0)
|
|
{
|
|
return Result.Err<int, InventoryError>(InventoryError.InvalidQuantity);
|
|
}
|
|
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
var existingItem = runtimeItems[i];
|
|
|
|
if (existingItem.Equals(item))
|
|
{
|
|
if (!item.IsStackable)
|
|
{
|
|
return Result.Err<int, InventoryError>(InventoryError.ItemNotStackable);
|
|
}
|
|
|
|
var consumedAmount = existingItem.IncreaseCount(count, out var updatedItem);
|
|
runtimeItems[i] = updatedItem.Value;
|
|
|
|
count -= consumedAmount;
|
|
if (count <= 0)
|
|
{
|
|
return Result.Ok<int, InventoryError>(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result.Ok<int, InventoryError>(count);
|
|
}
|
|
|
|
private IResult<Unit, InventoryError> AddNew(Item item, int count)
|
|
{
|
|
while (count > 0)
|
|
{
|
|
if (IsFull)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(InventoryError.InventoryFull);
|
|
}
|
|
|
|
count -= InventoryItem.Create(item, count, out var newItem);
|
|
runtimeItems.Add(newItem.Value);
|
|
|
|
}
|
|
|
|
return Result.Ok<Unit, InventoryError>(default);
|
|
}
|
|
|
|
public IResult<Unit, InventoryError> Add(Item item, int count = 1)
|
|
{
|
|
return AddToExisting(item, count)
|
|
.Where(count => count > 0)
|
|
.SelectMany(count => AddNew(item, count)) switch
|
|
{
|
|
Ok<Unit, InventoryError> ok => ok,
|
|
Err<Unit, InventoryError>(InventoryError.None) => Result.Ok<Unit, InventoryError>(default),
|
|
var err => err
|
|
};
|
|
}
|
|
|
|
public IResult<Unit, InventoryError> Remove(Item item, int count = 1)
|
|
{
|
|
if (count <= 0)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(InventoryError.InvalidQuantity);
|
|
}
|
|
|
|
for (int i = runtimeItems.Count - 1; i >= 0; i--)
|
|
{
|
|
var existingItem = runtimeItems[i];
|
|
|
|
if (existingItem.Item == item)
|
|
{
|
|
if (existingItem.Count > count)
|
|
{
|
|
existingItem.ReduceCount(count, out var updatedItem);
|
|
runtimeItems[i] = updatedItem.Value;
|
|
return Result.Ok<Unit, InventoryError>(Unit.Default);
|
|
}
|
|
else
|
|
{
|
|
count -= existingItem.Count;
|
|
runtimeItems.RemoveAt(i);
|
|
|
|
if (count == 0)
|
|
{
|
|
return Result.Ok<Unit, InventoryError>(Unit.Default);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(InventoryError.NotEnoughQuantity);
|
|
}
|
|
|
|
return Result.Ok<Unit, InventoryError>(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<InventoryItem> GetEnumerator()
|
|
{
|
|
return runtimeItems.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public ISynchronizedView<InventoryItem, TView> CreateView<TView>(Func<InventoryItem, TView> transform)
|
|
{
|
|
return runtimeItems.CreateView(transform);
|
|
}
|
|
|
|
public int Add(object value)
|
|
{
|
|
return ((IList)runtimeItems).Add(value);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
((IList)runtimeItems).Clear();
|
|
}
|
|
|
|
public bool Contains(object value)
|
|
{
|
|
return ((IList)runtimeItems).Contains(value);
|
|
}
|
|
|
|
public int IndexOf(object value)
|
|
{
|
|
return ((IList)runtimeItems).IndexOf(value);
|
|
}
|
|
|
|
public void Insert(int index, object value)
|
|
{
|
|
((IList)runtimeItems).Insert(index, value);
|
|
}
|
|
|
|
public void Remove(object value)
|
|
{
|
|
((IList)runtimeItems).Remove(value);
|
|
}
|
|
|
|
public void RemoveAt(int index)
|
|
{
|
|
((IList)runtimeItems).RemoveAt(index);
|
|
}
|
|
|
|
public void CopyTo(Array array, int index)
|
|
{
|
|
((ICollection)runtimeItems).CopyTo(array, index);
|
|
}
|
|
}
|
|
}
|