Added all basic item stacks;

Move item stacks to new namespace
This commit is contained in:
霧雨烨 2024-06-13 13:53:10 +08:00
parent dde7bb16ca
commit 229098b261
11 changed files with 317 additions and 32 deletions

View File

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using ColdMint.scripts.item;
using ColdMint.scripts.item.itemStacks;
using Godot;

View File

@ -1,4 +1,5 @@
using ColdMint.scripts.item;
using ColdMint.scripts.item.itemStacks;
using ColdMint.scripts.utils;
using Godot;
@ -256,7 +257,7 @@ public partial class ItemSlotNode : MarginContainer
var debugText = TranslationServerUtils.Translate("item_prompt_debug");
if (debugText != null)
{
_control.TooltipText = string.Format(debugText, _itemStack.Id,
_control.TooltipText = string.Format(debugText, _itemStack.GetItem()?.Id,
TranslationServerUtils.Translate(_itemStack.Name),
_itemStack.Quantity, _itemStack.MaxQuantity, _itemStack.GetType().Name,
TranslationServerUtils.Translate(_itemStack.Description));

View File

@ -37,6 +37,8 @@ public partial class Packsack : RigidBody2D, IItem
QueueFree();
}
public bool CanStackWith(IItem item) => false;
private IItemContainer? _itemContainer;
public override void _Ready()

View File

@ -6,6 +6,7 @@ using System.Linq;
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.item;
using ColdMint.scripts.item.itemStacks;
using ColdMint.scripts.utils;
using Godot;

View File

@ -0,0 +1,28 @@
using ColdMint.scripts.item.itemStacks;
namespace ColdMint.scripts.item;
/// <summary>
/// <para>Special item interface that make item common, which means will stack in a <see cref="CommonItemStack"/></para>
/// <para>该特殊的物品接口使得物品成为平凡物品,换言之,将会堆叠在<see cref="CommonItemStack"/></para>中。
/// </summary>
/// <typeparam name="TSelf">
/// <para>Make this the class itself</para>
/// <para>应当为当前类自身</para>
/// </typeparam>
/// <remarks>
/// <para>
/// Notice when you implement: To avoid unexpected behavior, unless you understand what you're doing, the <see cref="IItem.CanStackWith"/> method
/// of an item that implements the interface must only match its own exact same instance.
/// </para>
/// <para>实现时注意:为避免意外行为,除非你明白自己在做什么,否则实现接口的物品的<see cref="IItem.CanStackWith"/>方法必须只和自己完全相同的实例匹配。</para>
/// </remarks>
public interface ICommonItem : IItem
{
/// <summary>
/// <para>Method to copy an instance same with self. Will be used to pick out item instance from a <see cref="CommonItemStack"/></para>
/// <para>复制与自身相同的实例的方法。将用于从 <see cref="CommonItemStack"/> 中拿取新的物品实例。</para>
/// </summary>
/// <returns></returns>
ICommonItem CopyInstance();
}

View File

@ -1,4 +1,6 @@
using Godot;
using ColdMint.scripts.item.itemStacks;
using Godot;
namespace ColdMint.scripts.item;
@ -32,4 +34,41 @@ public interface IItem
/// <para>Execute when current item be removed from game.</para>
/// </summary>
void Destroy();
/// <summary>
/// <para>Return true if this item can be stacked with the given item in one stack</para>
/// <para>若该物品是否能与给定物品堆叠在同一个物品堆上返回true</para>
/// </summary>
/// <remarks>
/// <para>
/// ! Note in the implementation: the correspondence of this predicate must be an equivalence relation over the full set of stackable items
/// (i.e., be able to derive a division into the full set of stackable items).<br/>
/// Or if the item is not stackable, make it always return false.
/// </para>
/// <para>
/// !实现时注意:该谓词的对应关系必须是在全部可堆叠的物品集合上的等价关系(即能导出一个对可堆叠物品全集的划分)。<br/>
/// 或如果该物品不可堆叠令其永远返回false。<br/>
/// </para>
/// <para>
/// If it is necessary to implement special stacking relationships (e.g. containers that can be stacked to hold items),
/// customize the special <see cref="IItemStack"/> type, implement the special stacking determination in it by overriding <see cref="IItemStack.CanAddItem"/>,
/// and override the <see cref="SpecialStack"/> method so that it returns an instance of that custom ItemStack.
/// </para>
/// <para>
/// 如果有必要实现特殊的堆叠关系(如可以用堆叠来收纳物品的容器),请自定义特殊的<see cref="IItemStack"/>类型,在其中重写<see cref="IItemStack.CanAddItem"/>以实现特殊的堆叠判定,
/// 并重写<see cref="SpecialStack"/>方法使其返回该自定义ItemStack实例。
/// </para>
/// </remarks>
bool CanStackWith(IItem item);
/// <summary>
/// <para>If this item need a special stack type, return the special item stack instance that contains the item. If else, just leave this null.</para>
/// <para>如果该项目需要特殊的物品堆类型重写此方法来返回包含该物品的特殊物品堆实例。否则保留原本的null返回值。</para>
/// </summary>
/// <remarks>
/// <para>DO NOT use this method to create stack from item, use <see cref="IItemStack.FromItem"/> instead</para>
/// <para>**不要**使用此方法从一个物品创建堆,请使用 <see cref="IItemStack.FromItem"/></para>。
/// </remarks>
/// <seealso cref="CanStackWith"/>
IItemStack? SpecialStack() => null;
}

View File

@ -0,0 +1,89 @@
using System;
using Godot;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>
/// one of the basic item stacks, where there is only one instance of an item actually held in the stack,
/// meaning that all items are identical (or completely random in some ways)
/// </para>
/// <para>平凡物品堆,基础物品堆之一,堆中实际保存的物品实例仅有一个,意味着所有物品都完全一致(或某些方面完全随机)</para>
/// </summary>
/// <param name="innerItem"></param>
/// <seealso cref="UniqueItemStack"/><seealso cref="SingleItemStack"/>
public class CommonItemStack(ICommonItem innerItem) : IItemStack
{
public int MaxQuantity { get; } = ItemTypeManager.MaxStackQuantityOf(innerItem.Id);
public int Quantity { get; private set; } = 1;
public bool Empty => Quantity == 0;
public Texture2D Icon => innerItem.Icon;
public string Name => $"{innerItem.Name}({Quantity})";
public string? Description => innerItem.Description;
public bool CanAddItem(IItem item1)
{
return innerItem.CanStackWith(item1) && (Quantity < MaxQuantity);
}
public bool AddItem(IItem item)
{
if (!CanAddItem(item)) return false;
Quantity++;
return true;
}
public int CanTakeFrom(IItemStack itemStack)
{
if (itemStack.Empty || !innerItem.CanStackWith(itemStack.GetItem()!)) return 0;
return Math.Min(itemStack.Quantity, MaxQuantity - Quantity);
}
public bool TakeFrom(IItemStack itemStack)
{
var number = CanTakeFrom(itemStack);
itemStack.RemoveItem(number);
Quantity += number;
return itemStack.Empty;
}
public IItem? GetItem()
{
return Empty ? null : innerItem;
}
public IItem? PickItem()
{
if (Empty) return null;
Quantity--;
if (Empty) innerItem.Destroy();
return innerItem.CopyInstance();
}
public IItemStack? PickItems(int value)
{
if (Empty) return null;
var result = new CommonItemStack(innerItem.CopyInstance());
var n = Math.Min(Quantity, value);
result.Quantity = n;
Quantity -= n;
if (Empty) innerItem.Destroy();
return result;
}
public int RemoveItem(int number)
{
var n = Math.Min(number, Quantity);
Quantity -= n;
if (Empty) innerItem.Destroy();
return number - n;
}
public void ClearStack()
{
if (Empty) return;
Quantity = 0;
innerItem.Destroy();
}
}

View File

@ -1,20 +1,14 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Godot;
namespace ColdMint.scripts.item;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>Item stack in an inventory slot</para>
/// </summary>
public interface IItemStack
{
/// <summary>
/// <para>ID of items inside current stack</para>
/// </summary>
string Id { get; }
/// <summary>
/// <para>Max number of current stack</para>
/// </summary>
@ -25,6 +19,11 @@ public interface IItemStack
/// </summary>
int Quantity { get; }
/// <summary>
/// <para>True if this stack is empty</para>
/// </summary>
bool Empty { get; }
/// <summary>
/// <para>Icon of current item</para>
/// </summary>
@ -134,10 +133,12 @@ public interface IItemStack
/// <summary>
/// Create a new ItemStack with the given item as the first item
/// </summary>
public static IItemStack FromItem(IItem item) => ItemTypeManager.MaxStackQuantityOf(item.Id) switch
{
1 => new SingleItemStack(item),
> 1 => throw new NotImplementedException(),
var other => throw new ArgumentException($"item {item} of type '{item.Id}' has unexpected max stack quantity {other}")
};
public static IItemStack FromItem(IItem item) =>
item.SpecialStack() ??
ItemTypeManager.MaxStackQuantityOf(item.Id) switch
{
1 => new SingleItemStack(item),
> 1 => item is ICommonItem commonItem ? new CommonItemStack(commonItem) : new UniqueItemStack(item),
var other => throw new ArgumentException($"item {item} of type '{item.Id}' has unexpected max stack quantity {other}")
};
}

View File

@ -1,22 +1,21 @@
using System;
using ColdMint.scripts.inventory;
using Godot;
namespace ColdMint.scripts.item;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>Item stack of single item</para>
/// <para>One of the basic item stacks, there are always one item in stack</para>
/// <para>单身狗物品堆,基础物品堆之一,堆中永远只会有一个物品</para>
/// </summary>
//maybe we'd move this into inventory namespace
/// <seealso cref="UniqueItemStack"/><seealso cref="CommonItemStack"/>
public class SingleItemStack(IItem item) : IItemStack
{
public IItem Item { get; init; } = item;
public string Id => Item.Id;
public int MaxQuantity => 1;
public int Quantity { get; set; } = 1;
public int Quantity => 1;
public bool Empty { get; private set; } = false;
public Texture2D Icon => Item.Icon;
public string Name => Item.Name;
public string? Description => Item.Description;
@ -31,29 +30,28 @@ public class SingleItemStack(IItem item) : IItemStack
public IItem? GetItem()
{
return Quantity == 1 ? Item : null;
return Empty ? null : Item;
}
public IItem? PickItem()
{
Quantity = 0;
if (Empty) return null;
Empty = true;
return Item;
}
public IItemStack? PickItems(int value)
{
if (value == 0) return null;
else
{
Quantity = 0;
return new SingleItemStack(Item);
}
if (value == 0 || Empty) return null;
Empty = true;
return new SingleItemStack(Item);
}
public int RemoveItem(int number)
{
if (number == 0) return 0;
Quantity = 0;
if (number == 0 || Empty) return 0;
Empty = true;
Item.Destroy();
return Math.Max(number - 1, 0);
}

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Godot;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>One of the basic item stacks, where each item in the stack maintains its original state (allowing for items that are not identical to each other)</para>
/// <para>独特物品堆,基础物品堆之一,堆中的每一个物品会保持各自原本的状态(允许互不相同的物品存在)</para>
/// </summary>
/// <seealso cref="CommonItemStack"/><seealso cref="SingleItemStack"/>
public class UniqueItemStack : IItemStack
{
private readonly Stack<IItem> _items;
public UniqueItemStack(IItem item)
{
_items = [];
_items.Push(item);
MaxQuantity = ItemTypeManager.MaxStackQuantityOf(item.Id);
}
private UniqueItemStack(UniqueItemStack from)
{
_items = from._items;
MaxQuantity = from.MaxQuantity;
Quantity = from.Quantity;
from.Quantity = 0;
}
public int MaxQuantity { get; }
public int Quantity { get; private set; } = 1;
public bool Empty => Quantity == 0;
public Texture2D Icon => GetItem()?.Icon ?? new PlaceholderTexture2D();
public string Name => $"{GetItem()?.Name}({Quantity})";
public string? Description => GetItem()?.Description;
public bool CanAddItem(IItem item)
{
//当两个物品相容且当前堆未满时我们返回true
return (GetItem()?.CanStackWith(item) ?? false) && (Quantity < MaxQuantity);
}
public bool AddItem(IItem item)
{
if (!CanAddItem(item)) return false;
_items.Push(item);
Quantity++;
return true;
}
public int CanTakeFrom(IItemStack itemStack)
{
if (!(itemStack.GetItem() is { } item)) return 0;
//如果两个物品相容,那么可以获取的数量取决于当前物品堆空位的大小和对方物品数量中较小的一方
if (CanAddItem(item))
return Mathf.Min(itemStack.Quantity, MaxQuantity - Quantity);
else return 0;
}
public bool TakeFrom(IItemStack itemStack)
{
var value = CanTakeFrom(itemStack);
for (int i = 0; i < value; i++)
{
//一个如果代码没有出错就用不到的安全检查
if (!AddItem(itemStack.PickItem()!)) break;
}
return itemStack.Empty;
}
public IItem? GetItem()
{
return Empty ? null : _items.Peek();
}
public IItem? PickItem()
{
if (Empty) return null;
Quantity--;
return _items.Pop();
}
public IItemStack? PickItems(int value)
{
if (Empty) return null;
if (value < 0) value = Quantity;
var result = new UniqueItemStack(PickItem()!);
//计算剩余的要取出的数量
var restToMove = Math.Min(value - 1, Quantity);
for (int i = 0; i < restToMove; i++)
{
result.AddItem(PickItem()!);
}
return result;
}
public int RemoveItem(int number)
{
if (number < 0) number = Quantity;
while (number > 0 && Quantity > 0)
{
PickItem()!.Destroy();
number--;
}
return number;
}
public void ClearStack()
{
while (Quantity > 0)
{
PickItem()!.Destroy();
}
}
}

View File

@ -40,6 +40,8 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem
QueueFree();
}
public bool CanStackWith(IItem item) => false;
/// <summary>
/// <para>Whether the weapon is currently picked up</para>