393 lines
9.2 KiB
C#
393 lines
9.2 KiB
C#
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<Item>, 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<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);
|
|
}
|
|
|
|
[MessagePackObject(AllowPrivate = true)]
|
|
[CreateAssetMenu(menuName = KitsuneCafeMenu.Item + "Inventory")]
|
|
public partial class Inventory : ScriptableObject, IList, IInventory<InventoryItem>, IEnumerable<InventoryItem>
|
|
{
|
|
[Key(0)]
|
|
[SerializeField]
|
|
private List<InventoryItem> items = new();
|
|
[IgnoreMember]
|
|
public IReadOnlyList<InventoryItem> Items => items;
|
|
|
|
[IgnoreMember]
|
|
[SerializeField]
|
|
private int capacity = 8;
|
|
|
|
public event NotifyCollectionChangedEventHandler<InventoryItem> 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<InventoryItem> 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<InventoryItem> Find(Func<InventoryItem, bool> predicate)
|
|
{
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
var item = items[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);
|
|
}
|
|
|
|
List<InventoryItem> existing = new();
|
|
List<InventoryItem> 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<int, InventoryError>(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<int, InventoryError>(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
Notify(NotifyCollectionChangedAction.Replace, updated, existing);
|
|
return Result.Ok<int, InventoryError>(count);
|
|
}
|
|
|
|
private IResult<Unit, InventoryError> AddNew(Item item, int count)
|
|
{
|
|
List<InventoryItem> added = new();
|
|
while (count > 0)
|
|
{
|
|
if (IsFull)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(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<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
|
|
};
|
|
}
|
|
|
|
private void Notify(NotifyCollectionChangedAction action, IList<InventoryItem> newItems = default, IList<InventoryItem> oldItems = default)
|
|
{
|
|
NotifyCollectionChangedEventArgs<InventoryItem> 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<Unit, InventoryError> Remove(Item item, int count = 1)
|
|
{
|
|
if (count <= 0)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(InventoryError.InvalidQuantity);
|
|
}
|
|
|
|
List<InventoryItem> removed = new();
|
|
List<InventoryItem> 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, InventoryError>(Unit.Default);
|
|
}
|
|
else
|
|
{
|
|
count -= existingItem.Count;
|
|
items.RemoveAt(i);
|
|
removed.Add(existingItem);
|
|
|
|
if (count == 0)
|
|
{
|
|
Notify(NotifyCollectionChangedAction.Remove, updated, removed);
|
|
return Result.Ok<Unit, InventoryError>(Unit.Default);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
return Result.Err<Unit, InventoryError>(InventoryError.NotEnoughQuantity);
|
|
}
|
|
|
|
Notify(NotifyCollectionChangedAction.Remove, updated, removed);
|
|
return Result.Ok<Unit, InventoryError>(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<InventoryItem> 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);
|
|
}
|
|
}
|
|
}
|