canto/Assets/Scripts/Item/Inventory.cs
2025-08-16 16:17:16 -04:00

295 lines
6.8 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>
{
[SerializeField]
private List<InventoryItem> items = new();
[SerializeField]
private int capacity = 8;
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<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);
}
for (int i = 0; i < Count; i++)
{
var existingItem = items[i];
if (existingItem.Equals(item))
{
if (!item.IsStackable)
{
return Result.Err<int, InventoryError>(InventoryError.ItemNotStackable);
}
var consumedAmount = existingItem.IncreaseCount(count, out var updatedItem);
items[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);
items.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 = 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;
return Result.Ok<Unit, InventoryError>(Unit.Default);
}
else
{
count -= existingItem.Count;
items.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 (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);
}
}
}