Merge pull request #2 from Web13234/item_refactory

Item System Rebuild
This commit is contained in:
Cold-Mint 2024-06-13 18:33:40 +08:00 committed by GitHub
commit 423fd322f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1261 additions and 427 deletions

View File

@ -6,5 +6,9 @@
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>ColdMint</RootNamespace>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,5 @@
id,zh,en,ja
staff_of_the_undead,死灵法杖,StaffOfTheUndead,ネクロポリスの杖です
staff_of_the_undead_desc,发射诅咒,可将敌人转化为邪恶的怪物。,Cast a curse that transforms enemies into evil monsters.,呪いを発射して、敵を邪悪な怪物に変えることができます。
packsack,背包,Packsack,背嚢
packsack_desc,可以装载更多物品的背包。,A backpack that can hold more items.,より多くのアイテムを保持できるバックパックです。
item_staff_of_the_undead,死灵法杖,StaffOfTheUndead,ネクロポリスの杖です
item_staff_of_the_undead_desc,发射诅咒,可将敌人转化为邪恶的怪物。,Cast a curse that transforms enemies into evil monsters.,呪いを発射して、敵を邪悪な怪物に変えることができます。
item_packsack,背包,Packsack,背嚢
item_packsack_desc,可以装载更多物品的背包。,A backpack that can hold more items.,より多くのアイテムを保持できるバックパックです。
1 id zh en ja
2 staff_of_the_undead item_staff_of_the_undead 死灵法杖 StaffOfTheUndead ネクロポリスの杖です
3 staff_of_the_undead_desc item_staff_of_the_undead_desc 发射诅咒,可将敌人转化为邪恶的怪物。 Cast a curse that transforms enemies into evil monsters. 呪いを発射して、敵を邪悪な怪物に変えることができます。
4 packsack item_packsack 背包 Packsack 背嚢
5 packsack_desc item_packsack_desc 可以装载更多物品的背包。 A backpack that can hold more items. より多くのアイテムを保持できるバックパックです。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://cn10fimoem04m"]
[ext_resource type="Script" path="res://scripts/inventory/Packsack.cs" id="1_8ehup"]
[ext_resource type="Script" path="res://scripts/item/Packsack.cs" id="1_slakl"]
[ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_e1ale"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_xqyue"]
@ -9,12 +9,7 @@ size = Vector2(41, 57)
[node name="RigidBody2D" type="RigidBody2D"]
collision_layer = 8
collision_mask = 38
script = ExtResource("1_8ehup")
metadata/Id = "Packsack"
metadata/MaxStackQuantity = 1
metadata/Icon = ExtResource("2_e1ale")
metadata/Name = "packsack"
metadata/Description = "packsack_desc"
script = ExtResource("1_slakl")
[node name="Player" type="Sprite2D" parent="."]
texture = ExtResource("2_e1ale")

View File

@ -1,8 +1,8 @@
[gd_scene load_steps=6 format=3 uid="uid://dnnn2xyayiehk"]
[ext_resource type="Texture2D" uid="uid://e6670ykyq145" path="res://sprites/weapon/staffOfTheUndead.png" id="1_ms3us"]
[ext_resource type="Script" path="res://scripts/weapon/ProjectileWeapon.cs" id="1_w8hhv"]
[ext_resource type="Texture2D" uid="uid://b2blj0yf4ohx3" path="res://icon.svg" id="2_l5lni"]
[ext_resource type="Script" path="res://scripts/item/weapon/ProjectileWeapon.cs" id="1_w8hhv"]
[ext_resource type="PackedScene" uid="uid://c01av43yk1q71" path="res://prefab/projectile/curseOfTheUndead.tscn" id="2_34250"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_obcq2"]
size = Vector2(49, 5)
@ -14,13 +14,9 @@ size = Vector2(49, 5.25)
collision_layer = 8
collision_mask = 34
script = ExtResource("1_w8hhv")
ProjectileScenes = [ExtResource("2_34250")]
Id = "staff_of_the_undead"
metadata/Projectiles = PackedStringArray("res://prefab/projectile/curseOfTheUndead.tscn")
metadata/Name = "staff_of_the_undead"
metadata/FiringIntervalArray = PackedInt64Array(5000, 500, 250)
metadata/Icon = ExtResource("2_l5lni")
metadata/ID = "StaffOfTheUndead"
metadata/MaxStackQuantity = 1
metadata/Description = "staff_of_the_undead_desc"
[node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8

View File

@ -1,5 +1,6 @@
using ColdMint.scripts.character;
using ColdMint.scripts.weapon;
using ColdMint.scripts.item.weapon;
namespace ColdMint.scripts.behaviorTree.ai;

View File

@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ColdMint.scripts.camp;
using ColdMint.scripts.damage;
using ColdMint.scripts.debug;
using ColdMint.scripts.health;
using ColdMint.scripts.inventory;
using ColdMint.scripts.item;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using ColdMint.scripts.item.weapon;
using Godot;
namespace ColdMint.scripts.character;
@ -38,14 +41,7 @@ public partial class CharacterTemplate : CharacterBody2D
//Item containers are used to store items.
//物品容器用于存储物品。
private IItemContainer? _itemContainer;
public IItemContainer? ItemContainer
{
get => _itemContainer;
set => _itemContainer = value;
}
public IItemContainer? ItemContainer { get; set; }
//Items currently held
//当前持有的物品
@ -69,11 +65,9 @@ public partial class CharacterTemplate : CharacterBody2D
///<para>Update finished items</para>
///<para>更新完成后的物品</para>
/// </param>
protected virtual void WhenUpdateCurrentItem(Node2D? currentItem)
{
}
protected virtual void WhenUpdateCurrentItem(Node2D? currentItem) { }
//Define a pick up range
//Define a pickup range
//定义一个拾起范围
private Area2D? _pickingArea;
@ -128,7 +122,7 @@ public partial class CharacterTemplate : CharacterBody2D
/// </summary>
protected List<Node>? PickingRangeBodiesList;
public Node[] PickingRangeBodies => PickingRangeBodiesList?.ToArray() ?? Array.Empty<Node>();
public Node[] PickingRangeBodies => PickingRangeBodiesList?.ToArray() ?? [];
/// <summary>
/// <para>Resurrected character</para>
@ -161,7 +155,7 @@ public partial class CharacterTemplate : CharacterBody2D
}
/// <summary>
/// <para>Find the nearest item within the pick up area(Does not include items currently held)</para>
/// <para>Find the nearest item within the pickup area(Does not include items currently held)</para>
/// <para>在拾捡范围内查找距离最近的物品(不包括当前持有的物品)</para>
/// </summary>
/// <returns></returns>
@ -185,7 +179,7 @@ public partial class CharacterTemplate : CharacterBody2D
/// <summary>
/// <para>Get all weapons within range of the pick up</para>
/// <para>Get all weapons within range of the pickup</para>
/// <para>获取所有在拾捡范围内的武器</para>
/// </summary>
/// <returns></returns>
@ -211,8 +205,8 @@ public partial class CharacterTemplate : CharacterBody2D
base._Ready();
PickingRangeBodiesList = new List<Node>();
CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
var lootListId = GetMeta("LootListId", string.Empty).AsString();
if (!string.IsNullOrEmpty(lootListId))
{
@ -282,14 +276,14 @@ public partial class CharacterTemplate : CharacterBody2D
return false;
}
if (_itemContainer == null)
if (ItemContainer == null)
{
return false;
}
//Get the currently selected node
//拿到当前选择的节点
var itemSlotNode = _itemContainer.GetSelectItemSlotNode();
var itemSlotNode = ItemContainer.GetSelectItemSlotNode();
if (itemSlotNode == null)
{
return false;
@ -297,7 +291,7 @@ public partial class CharacterTemplate : CharacterBody2D
//First check if we can pick up the item.
//先检查我们能否拾起此物品。
var canPick = _itemContainer.CanAddItem(item);
var canPick = ItemContainer.CanAddItem(item);
if (!canPick)
{
return false;
@ -305,7 +299,7 @@ public partial class CharacterTemplate : CharacterBody2D
//Is it successfully added to the container?
//再检查是否成功的添加到容器内了?
var addSuccess = _itemContainer.AddItem(item);
var addSuccess = ItemContainer.AddItem(item);
if (!addSuccess)
{
return false;
@ -320,7 +314,7 @@ public partial class CharacterTemplate : CharacterBody2D
weaponTemplate.Owner = this;
weaponTemplate.Picked = true;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, false);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
weaponTemplate.EnableContactInjury = false;
weaponTemplate.Sleeping = true;
}
@ -354,9 +348,9 @@ public partial class CharacterTemplate : CharacterBody2D
return false;
}
if (_currentItem is WeaponTemplate weaponTemplate)
if (_currentItem is IItem item)
{
weaponTemplate.Fire(this, position);
item.Use(this, position);
}
return true;
@ -407,7 +401,7 @@ public partial class CharacterTemplate : CharacterBody2D
{
if (targetCamp.Id == playerCamp.Id)
{
//If an attack is allowed and you are on the same side, it is displayed as a friendly color (friend damage).
//If an attack is allowed, and you are on the same side, it is displayed as a friendly color (friend damage).
//如果允许攻击,且属于同一阵营,则显示为友好颜色(友伤)
_healthBar.SetFriendlyTones();
}
@ -491,8 +485,8 @@ public partial class CharacterTemplate : CharacterBody2D
/// <param name="lootDataArray"></param>
/// <param name="position"></param>
public void GenerateLootObjects(Node parentNode,
LootData[] lootDataArray,
Vector2 position)
LootData[] lootDataArray,
Vector2 position)
{
LootListManager.GenerateLootObjects(parentNode, lootDataArray, position);
}
@ -507,9 +501,7 @@ public partial class CharacterTemplate : CharacterBody2D
_additionalForce = force;
}
protected virtual void OnHit(DamageTemplate damageTemplate)
{
}
protected virtual void OnHit(DamageTemplate damageTemplate) { }
/// <summary>
/// <para>Handle the event of character death</para>
@ -590,12 +582,12 @@ public partial class CharacterTemplate : CharacterBody2D
{
//If the item container is null, then return
//如果物品容器为null那么返回
if (_itemContainer == null)
if (ItemContainer == null)
{
return;
}
var len = _itemContainer.GetItemSlotCount();
var len = ItemContainer.GetItemSlotCount();
if (len == 0)
{
return;
@ -625,8 +617,8 @@ public partial class CharacterTemplate : CharacterBody2D
/// <para>抛出物品</para>
/// </summary>
/// <param name="index">
///<para>Item slot subscript in item container</para>
///<para>物品容器内的物品槽下标</para>
///<para>Item slot index in item container</para>
///<para>物品容器内的物品槽位置</para>
/// </param>
/// <param name="number">
/// <para>How many to throw</para>
@ -640,22 +632,39 @@ public partial class CharacterTemplate : CharacterBody2D
/// </param>
protected void ThrowItem(int index, int number, Vector2 velocity)
{
if (_itemContainer == null)
{
return;
}
var itemSlotNode = ItemContainer?.GetItemSlotNode(index);
if (itemSlotNode is null) return;
var itemSlotNode = _itemContainer.GetItemSlotNode(index);
if (itemSlotNode == null)
if (number < 0)
{
return;
while (!itemSlotNode.IsEmpty())
{
ThrowOneItem(itemSlotNode, velocity);
}
}
else
{
for (int i = 0; i < number && !itemSlotNode.IsEmpty(); i++)
{
ThrowOneItem(itemSlotNode, velocity);
}
}
}
var item = itemSlotNode.GetItem();
if (item == null)
{
return;
}
/// <summary>
/// <para>Throw item</para>
/// <para>抛出物品</para>
/// </summary>
/// <param name="itemSlotNode"></param>
/// <param name="velocity">
/// <para>The speed to be applied to the item</para>
/// <para>要施加到物品上的速度</para>
/// </param>
private void ThrowOneItem(ItemSlotNode itemSlotNode, Vector2 velocity)
{
//Pick an item from the item container
//从物品容器内取出一个物品
var item = itemSlotNode.PickItem();
if (item is not Node2D node2D)
{
@ -681,7 +690,7 @@ public partial class CharacterTemplate : CharacterBody2D
//We cannot immediately resume the physical collision when the weapon is discharged, which will cause the weapon to collide with the ground and platform earlier, preventing the weapon from flying.
//仍出武器时,我们不能立即恢复物理碰撞,立即恢复会导致武器更早的与地面和平台碰撞,阻止武器的飞行。
weaponTemplate.EnableContactInjury = true;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
@ -706,17 +715,6 @@ public partial class CharacterTemplate : CharacterBody2D
rigidBody2D.LinearVelocity = velocity;
break;
}
//Remove items from the item container
//在物品容器内移除物品
if (number < 0)
{
itemSlotNode.RemoveItem(item.Quantity);
}
else
{
itemSlotNode.RemoveItem(number);
}
}
/// <summary>

View File

@ -1,13 +1,16 @@
using System;
using System.Text;
using System.Threading.Tasks;
using ColdMint.scripts.damage;
using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.item;
using ColdMint.scripts.map.events;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using ColdMint.scripts.item.weapon;
using Godot;
namespace ColdMint.scripts.character;
@ -103,7 +106,7 @@ public partial class Player : CharacterTemplate
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("jump_down"));
}
@ -117,7 +120,7 @@ public partial class Player : CharacterTemplate
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
TranslationServerUtils.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("pick_up"));
operationTipLabel.Text = operationTipBuilder.ToString();
@ -140,7 +143,7 @@ public partial class Player : CharacterTemplate
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
TranslationServerUtils.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("use_item"));
operationTipBuilder.Append(TranslationServerUtils.Translate(item.Name));
@ -244,8 +247,8 @@ public partial class Player : CharacterTemplate
}
_parabola.Points = CurrentItem == null
? _emptyVector2Array
: ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), Gravity, 0.1f);
? _emptyVector2Array
: ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), Gravity, 0.1f);
}
@ -403,8 +406,8 @@ public partial class Player : CharacterTemplate
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)

View File

@ -1,20 +0,0 @@
using System;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Common goods</para>
/// <para>普通的物品</para>
/// </summary>
public class CommonItem : IItem
{
public string? Id { get; set; }
public int Quantity { get; set; }
public int MaxStackQuantity { get; set; }
public Texture2D? Icon { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public Action<IItem>? OnUse { get; set; }
public Func<IItem, Node>? OnInstantiation { get; set; }
}

View File

@ -1,56 +0,0 @@
using System;
using Godot;
namespace ColdMint.scripts.inventory;
public interface IItem
{
/// <summary>
/// <para>Item and ID</para>
/// <para>物品还有ID</para>
/// </summary>
string? Id { get; set; }
/// <summary>
/// <para>Represents the quantity of this item</para>
/// <para>表示此物品的数量</para>
/// </summary>
int Quantity { get; set; }
/// <summary>
/// <para>How many can this item stack up to</para>
/// <para>这个物品最多叠加到多少个</para>
/// </summary>
int MaxStackQuantity { get; set; }
/// <summary>
/// <para>Items can be set with Icon</para>
/// <para>物品可以设置图标</para>
/// </summary>
Texture2D? Icon { get; set; }
/// <summary>
/// <para>Item has a name</para>
/// <para>物品有名称</para>
/// </summary>
string? Name { get; set; }
/// <summary>
/// <para>Description</para>
/// <para>描述</para>
/// </summary>
string? Description { get; set; }
/// <summary>
/// <para>When using items</para>
/// <para>当使用物品时</para>
/// </summary>
Action<IItem>? OnUse { get; set; }
/// <summary>
/// <para>When removing items from the backpack, instantiate them</para>
/// <para>当从背包内取出,实例化物品时</para>
/// </summary>
Func<IItem, Node>? OnInstantiation { get; set; }
}

View File

@ -1,4 +1,11 @@
using Godot;
using System;
using System.Collections;
using System.Collections.Generic;
using ColdMint.scripts.item;
using ColdMint.scripts.item.itemStacks;
using Godot;
namespace ColdMint.scripts.inventory;
@ -10,7 +17,7 @@ namespace ColdMint.scripts.inventory;
///<para>Item containers can store items. Things like backpacks and Hotbars are containers with visual pages.</para>
///<para>物品容器可以储存物品。像背包和hotbar是具有可视化页面的容器。</para>
/// </remarks>
public interface IItemContainer
public interface IItemContainer : IEnumerable<ItemSlotNode>
{
/// <summary>
/// <para>Can the specified item be added to the container?</para>
@ -28,6 +35,17 @@ public interface IItemContainer
/// <returns></returns>
bool AddItem(IItem item);
/// <summary>
/// <para>Add an stack of items to this container</para>
/// <para>向当前容器中存入一堆物品</para>
/// </summary>
/// <param name="itemStack"></param>
/// <returns>
/// <para>If the source item stack is empty after the operation is completed</para>
/// <para>操作完成后,源物品堆是否被取空</para>
/// </returns>
bool AddItemStack(IItemStack itemStack);
/// <summary>
/// <para>Gets the selected location</para>
/// <para>获取选中的位置</para>
@ -42,13 +60,41 @@ public interface IItemContainer
/// <returns></returns>
ItemSlotNode? GetSelectItemSlotNode();
/// <summary>
/// <para>If present, remove an item from the slot at the currently selected location and return it.</para>
/// <para>如果存在,移除当前选中位置的槽位中的一个物品并将其返回</para>
/// </summary>
/// <seealso cref="PickItemFromItemSlot"/><seealso cref="PickItemsFromItemSlotBySelectIndex"/>
IItem? PickItemFromItemSlotBySelectIndex();
/// <summary>
/// <para>Remove the specified number of items from the item slot at the currently selected location, and return them as a new item stack</para>
/// <para>取出当前选中位置的物品槽中指定数量的物品,并作为新的物品堆返回</para>
/// </summary>
/// <param name="value">
/// <para>Quantity to be taken out, inputs below zero represent all items</para>
/// <para>要取出的数量小于0的输入代表全部物品</para>
/// </param>
/// <seealso cref="PickItemsFromItemSlot"/><seealso cref="PickItemFromItemSlotBySelectIndex"/>
IItemStack? PickItemsFromItemSlotBySelectIndex(int value);
/// <summary>
/// <para>Removes an item from the inventory at the currently selected location</para>
/// <para>移除当前选中位置物品栏内的物品</para>
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
bool RemoveItemFromItemSlotBySelectIndex(int number);
/// <param name="number">
/// <para>Quantity to be removed, inputs below zero represent all items</para>
/// <para>要删除的数量小于0的输入代表全部物品</para>
/// </param>
/// <returns>
/// <para>The remaining number, if the number of items in the current item stack is less than the specified number. Otherwise,0</para>
/// <para>若物品槽内物品少于指定的数量返回相差的数量。否则返回0</para>
/// </returns>
/// <remarks>
/// <para>Will remove the removed items from the game, if that is not the intent, consider using the <see cref="PickItemsFromItemSlotBySelectIndex"/></para>
/// <para>会将移除的物品从游戏中删除,如果目的并非如此,请考虑使用<see cref="PickItemsFromItemSlotBySelectIndex"/></para>
/// </remarks>
int RemoveItemFromItemSlotBySelectIndex(int number);
/// <summary>
/// <para>Gets the number of item slots</para>
@ -65,25 +111,98 @@ public interface IItemContainer
/// <returns></returns>
ItemSlotNode? GetItemSlotNode(int index);
/// <summary>
/// <para>Gets the item slot for the specified location, equals to <see cref="GetItemSlotNode"/></para>
/// <para>获取指定位置的物品槽,等同于<see cref="GetItemSlotNode"/></para>
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
ItemSlotNode? this[int index] => GetItemSlotNode(index);
/// <summary>
/// <para>If present, remove an item from the slot in the specified location and return it.</para>
/// <para>如果存在,移除指定位置的槽位中的一个物品并将其返回</para>
/// </summary>
/// <seealso cref="PickItemsFromItemSlot"/>
IItem? PickItemFromItemSlot(int itemSlotIndex);
/// <summary>
/// <para>Remove the specified number of items from the item slot in the specified location, and return them as a new item stack</para>
/// <para>取出指定位置的物品槽中指定数量的物品,并作为新的物品堆返回</para>
/// </summary>
/// <param name="itemSlotIndex"></param>
/// <param name="value">
/// <para>Quantity to be taken out, inputs below zero represent all items</para>
/// <para>要取出的数量小于0的输入代表全部物品</para>
/// </param>
/// <seealso cref="PickItemFromItemSlot"/>
IItemStack? PickItemsFromItemSlot(int itemSlotIndex, int value);
/// <summary>
/// <para>Removes an item from the item slot in the specified location</para>
/// <para>在指定位置的物品槽内移除物品</para>
/// </summary>
/// <param name="itemSlotIndex"></param>
/// <param name="number"></param>
/// <returns></returns>
bool RemoveItemFromItemSlot(int itemSlotIndex, int number);
/// <param name="number">
/// <para>Quantity to be removed, inputs below zero represent all items</para>
/// <para>要删除的数量小于0的输入代表全部物品</para>
/// </param>
/// <returns>
/// <para>The remaining number, if the number of items in the current item stack is less than the specified number. Otherwise,0</para>
/// <para>若物品槽内物品少于指定的数量返回相差的数量。否则返回0</para>
/// </returns>
/// <remarks>
/// <para>Will remove the removed items from the game, if that is not the intent, consider using the <see cref="PickItemsFromItemSlot"/></para>
/// <para>会将移除的物品从游戏中删除,如果目的并非如此,请考虑使用<see cref="PickItemsFromItemSlot"/></para>
/// </remarks>
int RemoveItemFromItemSlot(int itemSlotIndex, int number);
/// <summary>
/// <para>Based on the given item, match the item slots where it can be placed</para>
/// <para>Based on the given item, match the item slots where it can be added to </para>
/// <para>根据给定的物品,匹配可放置它的物品槽</para>
/// </summary>
/// <param name="item"></param>
/// <returns>
///<para>Return null if there is no slot to place the item in</para>
///<para>若没有槽可放置此物品则返回null</para>
/// <para>Return null if there is no slot to place the item in</para>
/// <para>若没有槽可放置此物品则返回null</para>
/// </returns>
ItemSlotNode? Matching(IItem item);
ItemSlotNode? Match(IItem item);
/// <summary>
/// <para>Based on the given item stack, match the item slots where it can be added to</para>
/// <para>根据给定的物品堆,匹配可放置它的物品槽</para>
/// </summary>
/// <param name="stack"></param>
/// <returns>
/// <para>Return null if there is no slot to add the item slot in</para>
/// <para>若没有槽可放置此物品堆则返回null</para>
/// </returns>
ItemSlotNode? Match(IItemStack stack);
/// <summary>
/// <para>Match the first item slot that has item stack that satisfies the predicate</para>
/// <para>匹配首个拥有满足指定条件的物品堆的物品槽</para>
/// </summary>
/// <param name="predicate"></param>
/// <returns>
/// <para>Return null if there is no slot satisfies the predicate</para>
/// <para>若没有满足条件的槽位返回null</para>
/// </returns>
/// <seealso cref="MatchAll"/>
ItemSlotNode? Match(Func<IItemStack?, bool> predicate);
/// <summary>
/// <para>Match all item slots that has item stack that satisfies the predicate</para>
/// <para>匹配所有拥有满足指定条件的物品堆的物品槽</para>
/// </summary>
/// <param name="predicate"></param>
/// <returns>
/// <para>IEnumerable for the item slot matched to, will be empty if there's no slot satisfies the predicate</para>
/// <para>包含匹配到的槽位的IEnumerable当没有满足条件的槽位时为空</para>
/// </returns>
/// <seealso cref="Match(Func{IItemStack?,bool})"/>
IEnumerable<ItemSlotNode> MatchAll(Func<IItemStack?, bool> predicate);
/// <summary>
/// <para>AddItemSlot</para>

View File

@ -1,4 +1,7 @@
using ColdMint.scripts.item;
using ColdMint.scripts.item.itemStacks;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
@ -9,7 +12,8 @@ namespace ColdMint.scripts.inventory;
/// </summary>
public partial class ItemSlotNode : MarginContainer
{
private IItem? _item;
//private IItem? _item;
private IItemStack? _itemStack;
private TextureRect? _backgroundTextureRect;
private TextureRect? _iconTextureRect;
private Label? _quantityLabel;
@ -34,149 +38,217 @@ public partial class ItemSlotNode : MarginContainer
public TextureRect? BackgroundTextureRect => _backgroundTextureRect;
public bool IsEmpty() => _itemStack == null;
/// <summary>
/// <para>Get the items in the item slot</para>
/// <para>获取物品槽内的物品</para>
/// <para>Get the item stack in the item slot</para>
/// <para>获取物品槽内的物品</para>
/// </summary>
/// <returns></returns>
public IItem? GetItem()
public IItemStack? GetItemStack() => _itemStack;
/// <summary>
/// <para>If present, get the item at the top of the item stack in this slot</para>
/// <para>如果存在,获取该槽位中物品堆顶部的物品</para>
/// </summary>
public IItem? GetItem() => _itemStack?.GetItem();
/// <summary>
/// <para>If present, remove an item in this slot and return it.</para>
/// <para>如果存在,移除该槽位中的一个物品并将其返回</para>
/// </summary>
/// <seealso cref="PickItems"/>
public IItem? PickItem()
{
return _item;
if (_itemStack is null) return null;
var result = _itemStack.PickItem();
if (_itemStack.Quantity == 0) _itemStack = null;
UpdateAllDisplay();
return result;
}
/// <summary>
/// <para>Remove the specified number of items and return them as a new item stack</para>
/// <para>取出当前物品槽中指定数量的物品,并作为新的物品堆返回</para>
/// </summary>
/// <param name="value">
/// <para>Quantity to be taken out, inputs below zero represent all items</para>
/// <para>要取出的数量小于0的输入代表全部物品</para>
/// </param>
/// <seealso cref="PickItem"/>
public IItemStack? PickItems(int value)
{
if (_itemStack is null) return null;
var result = _itemStack.PickItems(value);
if (_itemStack.Quantity == 0) _itemStack = null;
UpdateAllDisplay();
return result;
}
/// <summary>
/// <para>Removes the specified number of items from the item slot</para>
/// <para>在物品槽内移除指定数量的物品</para>
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
public bool RemoveItem(int number)
/// <param name="number">
/// <para>Quantity to be removed, inputs below zero represent all items</para>
/// <para>要删除的数量小于0的输入代表全部物品</para>
/// </param>
/// <returns>
/// <para>The remaining number, if the number of items in the current item stack is less than the specified number. Otherwise,0</para>
/// <para>若物品槽内物品少于指定的数量返回相差的数量。否则返回0</para>
/// </returns>
/// <remarks>
/// <para>Will remove the removed items from the game, if that is not the intent, consider using the <see cref="PickItems"/></para>
/// <para>会将移除的物品从游戏中删除,如果目的并非如此,请考虑使用<see cref="PickItems"/></para>
/// </remarks>
public int RemoveItem(int number)
{
if (_item == null)
if (_itemStack == null)
{
return false;
return number;
}
var newNumber = _item.Quantity - number;
if (newNumber <= 0)
{
//If the specified number of items is removed, the number of items is less than or equal to 0. Then we return the removal successful and empty the inventory.
//如果移除指定数量的物品后物品数量小于或等于0。那么我们返回移除成功并清空物品栏。
ClearItem();
return true;
}
else
{
_item.Quantity = newNumber;
UpdateTooltipText(_item);
UpdateQuantityLabel(_item.Quantity);
return true;
}
var result = _itemStack.RemoveItem(number);
//If the specified number of items is removed, the number of items is less than or equal to 0. Then we empty the inventory.
//如果移除指定数量的物品后物品数量小于或等于0。那么我们清空物品栏。
if (_itemStack.Quantity == 0) _itemStack = null;
UpdateAllDisplay();
return result;
}
/// <summary>
/// <para>Empty the items in the item slot</para>
/// <para>清空物品槽内的物品</para>
/// <para>Remove item stack from slot and return it, equivalent to ReplaceItemStack(null)</para>
/// <para>从当前槽位中移出并返回物品堆等价于ReplaceItemStack(null)</para>
/// <seealso cref="ReplaceItemStack"/>
/// </summary>
public IItemStack? RemoveItemStack() => ReplaceItemStack(null);
/// <summary>
/// <para>Empty the item slot</para>
/// <para>清空当前物品槽</para>
/// </summary>
/// <remarks>
///<para>This method does not calculate how many items should be left. If you want to remove a specified number of items, call the RemoveItem method.</para>
///<para>此方法不会计算物品应该剩余多少个。若您希望移除指定数量的物品请调用RemoveItem方法。</para>
///<para>This method will remove all items stored in the item slots from the game, if this is not what you want to do, consider using the <see cref="RemoveItemStack"/> method.</para>
///<para>此方法会从游戏中移除储存于物品槽中的所有物品,若这不是您希望的操作,请考虑使用<see cref="RemoveItemStack"/>方法。</para>
/// </remarks>
public void ClearItem()
public void ClearSlot()
{
_item = null;
if (_iconTextureRect != null)
{
_iconTextureRect.Texture = null;
}
_itemStack?.ClearStack();
_itemStack = null;
if (_control != null)
{
_control.TooltipText = null;
}
if (_quantityLabel != null)
{
_quantityLabel.Hide();
}
UpdateAllDisplay();
}
/// <summary>
/// <para>Can the specified item be placed in the item slot?</para>
/// <para>指定的物品是否可设置在物品槽内?</para>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool CanSetItem(IItem item)
public bool CanAddItem(IItem item)
{
if (_item == null)
{
return true;
}
//This inventory already has items, but the items in this inventory are not the same as the incoming items
//这个物品栏已经有物品了,但是这个物品栏的物品和传入的物品不一样
if (_item.Id != item.Id)
{
return false;
}
var newQuantity = _item.Quantity + item.Quantity;
if (newQuantity > item.MaxStackQuantity)
{
//If the amount of the current item exceeds the maximum stack amount after placing it in this inventory
//如果将当前物品放置到这个物品栏后,数量超过了最大叠加数量
return false;
}
return true;
if (_itemStack == null) return true;
return _itemStack.CanAddItem(item);
}
/// <summary>
/// <para>Sets items for the item slot</para>
/// <para>为物品槽设置物品</para>
/// <para>
/// Set item stack for this slot, this will completely replace current item stack.
/// If you want the item stack to be added to current stack, use the <see cref="AddItemStack"/>.
/// </para>
/// <para>为物品槽设置物品堆,将完全替换掉当前物品堆。如果想要物品堆叠加至该物品堆,请使用<see cref="AddItemStack"/></para>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool SetItem(IItem item)
/// <returns>
/// <para>The item stack that was previously in this slot</para>
/// <para>该槽位中原本的物品堆</para>
/// </returns>
public IItemStack? ReplaceItemStack(IItemStack? newItemStack)
{
if (!CanSetItem(item))
{
return false;
}
var result = _itemStack;
_itemStack = newItemStack;
if (_item == null)
{
if (item.Icon != null && _iconTextureRect != null)
{
_iconTextureRect.Texture = item.Icon;
}
UpdateAllDisplay();
_item = item;
UpdateTooltipText(item);
UpdateQuantityLabel(item.Quantity);
return true;
return result;
}
/// <summary>
/// <para>Try to add an item to this slot, if it can't be added to this slot, return false</para>
/// <para>尝试向当前槽位中加入物品如果该物品不能被放入该槽位返回false</para>
/// </summary>
public bool AddItem(IItem item)
{
bool result;
if (_itemStack is null)
{
_itemStack = IItemStack.FromItem(item);
result = true;
}
else
{
var newQuantity = _item.Quantity + item.Quantity;
_item.Quantity = newQuantity;
UpdateTooltipText(item);
UpdateQuantityLabel(newQuantity);
return true;
result = _itemStack.AddItem(item);
}
if (result)
{
UpdateAllDisplay();
}
return result;
}
/// <summary>
/// <para>Try to combine an item stack into this slot</para>
/// <para>尝试将一个物品堆合并至该槽位中</para>
/// </summary>
/// <returns>
/// <para>If the source item stack is empty after the operation is completed</para>
/// <para>操作完成后,源物品堆是否被取空</para>
/// </returns>
public bool AddItemStack(IItemStack itemStack)
{
bool result;
if (_itemStack is null)
{
_itemStack = itemStack;
result = false;
}
else
{
result = _itemStack.TakeFrom(itemStack);
}
UpdateAllDisplay();
return result;
}
/// <summary>
/// <para>Update all displays of this slot</para>
/// <para>更新该槽位的一切显示信息</para>
/// </summary>
private void UpdateAllDisplay()
{
UpdateIconTexture();
UpdateQuantityLabel();
UpdateTooltipText();
}
/// <summary>
/// <para>Update item tips</para>
/// <para>更新物品的提示内容</para>
/// </summary>
/// <param name="item"></param>
private void UpdateTooltipText(IItem item)
private void UpdateTooltipText()
{
if (_control == null)
if (_control == null) return;
if (_itemStack == null)
{
_control.TooltipText = null;
return;
}
@ -185,16 +257,16 @@ public partial class ItemSlotNode : MarginContainer
var debugText = TranslationServerUtils.Translate("item_prompt_debug");
if (debugText != null)
{
_control.TooltipText = string.Format(debugText, item.Id,
TranslationServerUtils.Translate(item.Name),
item.Quantity, item.MaxStackQuantity, item.GetType().Name,
TranslationServerUtils.Translate(item.Description));
_control.TooltipText = string.Format(debugText, _itemStack.GetItem()?.Id,
TranslationServerUtils.Translate(_itemStack.Name),
_itemStack.Quantity, _itemStack.MaxQuantity, _itemStack.GetType().Name,
TranslationServerUtils.Translate(_itemStack.Description));
}
}
else
{
_control.TooltipText = TranslationServerUtils.Translate(item.Name) + "\n" +
TranslationServerUtils.Translate(item.Description);
_control.TooltipText = TranslationServerUtils.Translate(_itemStack.Name) + "\n" +
TranslationServerUtils.Translate(_itemStack.Description);
}
}
@ -202,28 +274,36 @@ public partial class ItemSlotNode : MarginContainer
/// <para>Update quantity label</para>
/// <para>更新数量标签</para>
/// </summary>
/// <param name="quantity"></param>
private void UpdateQuantityLabel(int? quantity)
private void UpdateQuantityLabel()
{
if (_quantityLabel == null)
{
return;
}
switch (quantity)
switch (_itemStack?.Quantity)
{
case null:
case null or 1:
_quantityLabel.Hide();
return;
case > 1:
//When the quantity is greater than 1, we display the quantity.
//当数量大于1时我们显示数量
_quantityLabel.Text = quantity.ToString();
default:
//When the quantity is not null or 1, we display the quantity.
//当数量不为null或1时我们显示数量
_quantityLabel.Text = _itemStack?.Quantity.ToString();
_quantityLabel.Show();
break;
default:
_quantityLabel.Hide();
break;
}
}
/// <summary>
/// <para>Update texture of the icon rect</para>
/// <para>更新显示的物品图标</para>
/// </summary>
private void UpdateIconTexture()
{
if (_iconTextureRect != null)
{
_iconTextureRect.Texture = _itemStack?.Icon;
}
}

View File

@ -1,40 +0,0 @@
using System;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>packsack</para>
/// <para>背包</para>
/// </summary>
public partial class Packsack : RigidBody2D, IItem
{
public string? Id { get; set; }
public int Quantity { get; set; }
public int MaxStackQuantity { get; set; }
public Texture2D? Icon { get; set; }
public new string? Name { get; set; }
public string? Description { get; set; }
public Action<IItem>? OnUse { get; set; }
public Func<IItem, Node>? OnInstantiation { get; set; }
private IItemContainer? _itemContainer;
public override void _Ready()
{
base._Ready();
Id = GetMeta("ID", "1").AsString();
Quantity = GetMeta("Quantity", "1").AsInt32();
MaxStackQuantity = GetMeta("MaxStackQuantity", Config.MaxStackQuantity).AsInt32();
Icon = GetMeta("Icon", "").As<Texture2D>();
Name = GetMeta("Name", "").AsString();
Description = GetMeta("Description", "").AsString();
_itemContainer = new UniversalItemContainer();
}
public IItemContainer? GetItemContainer()
{
return _itemContainer;
}
}

View File

@ -1,9 +1,18 @@
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
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;
using JetBrains.Annotations;
namespace ColdMint.scripts.inventory;
/// <summary>
@ -33,18 +42,32 @@ public class UniversalItemContainer : IItemContainer
public bool CanAddItem(IItem item)
{
return Matching(item) != null;
return Match(item) != null;
}
public bool AddItem(IItem item)
{
var itemSlotNode = Matching(item);
var itemSlotNode = Match(item);
if (itemSlotNode == null)
{
return false;
}
return itemSlotNode.SetItem(item);
return itemSlotNode.AddItem(item);
}
public bool AddItemStack(IItemStack itemStack)
{
while (true)
{
var itemSlotNode = Match(itemStack);
if (itemSlotNode == null)
return false;
if (itemSlotNode.AddItemStack(itemStack))
return true;
}
}
public int GetSelectIndex()
@ -69,10 +92,11 @@ public class UniversalItemContainer : IItemContainer
return null;
}
public bool RemoveItemFromItemSlotBySelectIndex(int number)
{
return RemoveItemFromItemSlot(_selectIndex, number);
}
public IItem? PickItemFromItemSlotBySelectIndex() => PickItemFromItemSlot(_selectIndex);
public IItemStack? PickItemsFromItemSlotBySelectIndex(int value) => PickItemsFromItemSlot(_selectIndex, value);
public int RemoveItemFromItemSlotBySelectIndex(int number) => RemoveItemFromItemSlot(_selectIndex, number);
public int GetItemSlotCount()
{
@ -95,42 +119,65 @@ public class UniversalItemContainer : IItemContainer
return _itemSlotNodes[safeIndex];
}
public bool RemoveItemFromItemSlot(int itemSlotIndex, int number)
public IItem? PickItemFromItemSlot(int itemSlotIndex)
{
if (_itemSlotNodes == null)
{
return false;
}
if (_itemSlotNodes == null) return null;
var safeIndex = GetSafeIndex(itemSlotIndex);
if (safeIndex == UnknownIndex)
{
return false;
return null;
}
var itemSlot = _itemSlotNodes[safeIndex];
return itemSlot.PickItem();
}
public IItemStack? PickItemsFromItemSlot(int itemSlotIndex, int value)
{
if (_itemSlotNodes == null) return null;
var safeIndex = GetSafeIndex(itemSlotIndex);
if (safeIndex == UnknownIndex)
{
return null;
}
var itemSlot = _itemSlotNodes[safeIndex];
return itemSlot.PickItems(value);
}
public int RemoveItemFromItemSlot(int itemSlotIndex, int number)
{
if (_itemSlotNodes == null) return number;
var safeIndex = GetSafeIndex(itemSlotIndex);
if (safeIndex == UnknownIndex)
{
return number;
}
var itemSlot = _itemSlotNodes[safeIndex];
return itemSlot.RemoveItem(number);
}
public ItemSlotNode? Matching(IItem item)
public ItemSlotNode? Match(IItem item)
{
if (_itemSlotNodes == null || _itemSlotNodes.Count == 0)
{
return null;
}
//Find and return the first slot that can hold this item, if the list is null or not found, return null
//寻找并返回第一个遇到的可放置此物品的物品槽若列表为空或不存在将返回null
return _itemSlotNodes?.FirstOrDefault(itemSlotNode => itemSlotNode.CanAddItem(item));
}
public ItemSlotNode? Match(IItemStack stack)
{
return _itemSlotNodes?.FirstOrDefault(itemSlotNode => itemSlotNode.CanAddItem(stack.GetItem()!));
}
foreach (var itemSlotNode in _itemSlotNodes)
{
if (itemSlotNode.CanSetItem(item))
{
//If there is an item slot to put this item in, then we return it.
//如果有物品槽可放置此物品,那么我们返回它。
return itemSlotNode;
}
}
public ItemSlotNode? Match(Func<IItemStack?, bool> predicate)
{
return _itemSlotNodes?.FirstOrDefault(node => predicate(node.GetItemStack()));
}
return null;
public IEnumerable<ItemSlotNode> MatchAll(Func<IItemStack?, bool> predicate)
{
return from node in _itemSlotNodes where predicate(node.GetItemStack()) select node;
}
@ -140,8 +187,8 @@ public class UniversalItemContainer : IItemContainer
/// </summary>
/// <param name="itemSlotIndex"></param>
/// <returns>
///<para>-1 is returned on failure, and the index that does not result in an out-of-bounds subscript is returned on success</para>
///<para>失败返回-1成功返回不会导致下标越界的索引</para>
/// <para>-1 is returned on failure, and the index that does not result in an out-of-bounds subscript is returned on success</para>
/// <para>失败返回-1成功返回不会导致下标越界的索引</para>
/// </returns>
private int GetSafeIndex(int itemSlotIndex)
{
@ -305,4 +352,17 @@ public class UniversalItemContainer : IItemContainer
_selectIndex = newSelectIndex;
}
[MustDisposeResource]
public IEnumerator<ItemSlotNode> GetEnumerator()
{
return _itemSlotNodes?.GetEnumerator() ?? Enumerable.Empty<ItemSlotNode>().GetEnumerator();
}
[MustDisposeResource]
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

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();
}

74
scripts/item/IItem.cs Normal file
View File

@ -0,0 +1,74 @@
using ColdMint.scripts.item.itemStacks;
using Godot;
namespace ColdMint.scripts.item;
public interface IItem
{
/// <summary>
/// <para>ID of current item</para>
/// </summary>
string Id { get; }
/// <summary>
/// <para>Icon of current item</para>
/// </summary>
Texture2D Icon { get; }
/// <summary>
/// <para>Display name of current item</para>
/// </summary>
string Name { get; }
/// <summary>
/// <para>Description of current item, which may show in inventory</para>
/// </summary>
string? Description { get; }
/// <summary>
/// <para>Execute when current item is used <br/> e.g. when player clicks left mouse button with current item in hand</para>
/// </summary>
/// <param name="owner">Owner of current item, if any</param>
/// <param name="targetGlobalPosition">Target position, such as the position of the cursor when used by the player</param>
void Use(Node2D? owner, Vector2 targetGlobalPosition);
/// <summary>
/// <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;
}

29
scripts/item/ItemType.cs Normal file
View File

@ -0,0 +1,29 @@
using System;
using Godot;
namespace ColdMint.scripts.item;
public readonly struct ItemType(string id, Func<IItem> newItemFunc, Texture2D? icon, int maxStackQuantity)
{
/// <summary>
/// <para>Item id of this type</para>
/// <para>该类型物品的id</para>
/// </summary>
public string Id { get; init; } = id;
/// <summary>
/// <para>A function returns a new item instance of this type</para>
/// <para>用于创建该类型的物品实例的函数</para>
/// </summary>
public Func<IItem> NewItemFunc { get; init; } = newItemFunc;
/// <summary>
/// <para>Default icon of items of this type</para>
/// <para>该类型物品的默认图标</para>
/// </summary>
public Texture2D? Icon { get; init; } = icon;
/// <summary>
/// <para>Max number in item stack of this type</para>
/// <para>该类型物品的最大堆叠数量</para>
/// </summary>
public int MaxStackQuantity { get; init; } = maxStackQuantity;
}

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.item;
public static class ItemTypeManager
{
/// <summary>
/// Register items statically here
/// </summary>
public static void StaticRegister()
{
var staffOfTheUndeadScene = ResourceLoader.Load<PackedScene>("res://prefab/weapons/staffOfTheUndead.tscn");
var staffOfTheUndeadIcon = ResourceLoader.Load<Texture2D>("res://sprites/weapon/staffOfTheUndead.png");
var staffOfTheUndead =
new ItemType("staff_of_the_undead", () => staffOfTheUndeadScene.Instantiate<IItem>(), staffOfTheUndeadIcon, 1);
Register(staffOfTheUndead);
var packsackScene = ResourceLoader.Load<PackedScene>("res://prefab/packsacks/packsack.tscn");
var packsackIcon = ResourceLoader.Load<Texture2D>("res://sprites/Player.png");
var packsack = new ItemType("packsack", () => packsackScene.Instantiate<IItem>(), packsackIcon, 1);
Register(packsack);
}
private static Dictionary<string, ItemType> Registry { get; } = [];
private static Texture2D DefaultTexture { get; } = new PlaceholderTexture2D();
/// <summary>
/// Register an item type.
/// Return false if the item id already exist.
/// </summary>
/// <returns>Whether the registration was successful.</returns>
public static bool Register(ItemType itemType) => Registry.TryAdd(itemType.Id, itemType);
/// <summary>
/// <para>Creates a new instance of the item registered to the given id.</para>
/// <para>Returns null when the id is not registered.</para>
/// </summary>
public static IItem? NewItem(string id) =>
Registry.TryGetValue(id, out var itemType) ? itemType.NewItemFunc() : null;
/// <summary>
/// Get the translated default name of the item type for the given id
/// </summary>
/// <returns>
/// Translated default name of the item id if it exists. Else, return the id itself
/// </returns>
public static string DefaultNameOf(string id) => TranslationServerUtils.Translate($"item_{id}") ?? id;
/// <summary>
/// Get the translated default description of the item type for the given id
/// </summary>
/// <returns>
/// Translated default description of the item id if it exists. Else, return null
/// </returns>
public static string? DefaultDescriptionOf(string id) => TranslationServerUtils.Translate($"item_{id}_desc");
/// <summary>
/// Get the default icon of the item type for the given id
/// </summary>
/// <returns>
/// Translated default icon of the item id if it exists. Else, return a placeholder
/// </returns>
public static Texture2D DefaultIconOf(string id) =>
Registry.TryGetValue(id, out var itemType)
? itemType.Icon ?? DefaultTexture
: DefaultTexture;
public static int MaxStackQuantityOf(string id) => Registry.TryGetValue(id, out var itemType) ? itemType.MaxStackQuantity : 0;
}

51
scripts/item/Packsack.cs Normal file
View File

@ -0,0 +1,51 @@
using ColdMint.scripts.inventory;
using Godot;
namespace ColdMint.scripts.item;
/// <summary>
/// <para>packsack</para>
/// <para>背包</para>
/// </summary>
public partial class Packsack : RigidBody2D, IItem
{
[Export] public string Id { get; protected set; } = "place_holder_id";
protected Texture2D? UniqueIcon { get; set; }
public Texture2D Icon => UniqueIcon ?? ItemTypeManager.DefaultIconOf(Id);
protected string? UniqueName { get; set; }
public new string Name => UniqueName ?? ItemTypeManager.DefaultNameOf(Id);
protected string? UniqueDescription { get; set; }
public string? Description => UniqueDescription ?? ItemTypeManager.DefaultDescriptionOf(Id);
public void Use(Node2D? owner, Vector2 targetGlobalPosition) { }
public void Destroy()
{
if (_itemContainer == null) return;
foreach (var itemSlot in _itemContainer)
{
itemSlot.ClearSlot();
}
QueueFree();
}
public bool CanStackWith(IItem item) => false;
private IItemContainer? _itemContainer;
public override void _Ready()
{
base._Ready();
_itemContainer = new UniversalItemContainer();
}
public IItemContainer? GetItemContainer()
{
return _itemContainer;
}
}

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

@ -0,0 +1,159 @@
using System;
using Godot;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>Item stack in an inventory slot</para>
/// </summary>
public interface IItemStack
{
/// <summary>
/// <para>Max number of current stack</para>
/// <para>当前物品堆的最大物品数量</para>
/// </summary>
int MaxQuantity { get; }
/// <summary>
/// <para>Quantity of current stack</para>
/// <para>当前物品堆的物品数量</para>
/// </summary>
int Quantity { get; }
/// <summary>
/// <para>True if this stack is empty</para>
/// <para>当物品堆空时为真</para>
/// </summary>
/// <remarks>
/// <para>
/// This attribute is used to check if the item stack is empty after the operation for subsequent processing,<br/>
/// i.e. there should not be any item stacks with this attribute true outside of the operation process
/// </para>
/// <para>此属性用于检查操作后该物品堆是否为空以便后续处理也就是说在操作过程以外的时候不应当存在任何该属性为true的物品堆</para>
/// </remarks>
bool Empty { get; }
/// <summary>
/// <para>Icon of current item stack</para>
/// <para>当前物品堆显示的图标</para>
/// </summary>
Texture2D Icon { get; }
/// <summary>
/// <para>Display name of current item stack</para>
/// <para>当前物品堆显示的名称</para>
/// </summary>
string Name { get; }
/// <summary>
/// <para>Description of current item stack, which may show in inventory</para>
/// <para>当前物品堆的描述,可能显示在物品栏中</para>
/// </summary>
string? Description { get; }
/// <summary>
/// <para>Determine whether a specified item can be accommodated</para>
/// <para>判断能否容纳指定物品</para>
/// </summary>
/// <returns></returns>
public bool CanAddItem(IItem item);
/// <summary>
/// <para>Hold a given item</para>
/// </summary>
/// <param name="item">Item to hold by current stack</param>
/// <returns>Whether successful</returns>
public bool AddItem(IItem item);
/// <summary>
/// <para>判断能从指定物品堆中接收的物品数量</para>
/// </summary>
/// <param name="itemStack">
/// <para>向该物品堆中放入物品的物品堆</para>
/// <para>Item stack to add to the current stack</para>
/// </param>
/// <returns></returns>
public int CanTakeFrom(IItemStack itemStack);
/// <summary>
/// <para>将指定物品堆中尽可能多的物品移动至当前物品堆中,被移入当前堆的物品应从原物品堆中移除。</para>
/// </summary>
/// <param name="itemStack">
/// <para>被移入当前堆的物品堆</para>
/// </param>
/// <returns>
/// <para>操作结束后原物品堆是否为空</para>
/// </returns>
public bool TakeFrom(IItemStack itemStack);
/// <summary>
/// <para>Get item instance at the top of current stack without removing it from stack</para>
/// <para>获取当前物品堆顶部的物品实例而不取出该物品</para>
/// <seealso cref="PickItem"/>
/// </summary>
/// <returns></returns>
public IItem? GetItem();
/// <summary>
/// <para>Pop the item instance at the top of current item stack and return it</para>
/// <para>取出当前物品堆顶部的物品实例并返回该物品</para>
/// <seealso cref="GetItem"/><seealso cref="PickItems"/>
/// </summary>
/// <returns></returns>
public IItem? PickItem();
/// <summary>
/// <para>Remove the specified number of items and return them as a new item stack</para>
/// <para>取出当前堆中指定数量的物品,并作为新的物品堆返回</para>
/// <seealso cref="PickItem"/>
/// </summary>
/// <param name="value">
/// <para>Quantity to be taken out, inputs below zero represent all items</para>
/// <para>要取出的数量小于0的输入代表全部物品</para>
/// </param>
/// <returns>
/// <para>The item stack that is taken out, can be null if out nothing, should not be the current item stack itself</para>
/// <para>取出的物品堆没有取出物品时可为null不应是当前物品堆自身</para>
/// </returns>
public IItemStack? PickItems(int value);
/// <summary>
/// <para>
/// Removes the specified number of items from current item stack,removed items should be removed from the game<br/>
/// If you don't want remove them from game, consider <see cref="PickItem"/> and <see cref="PickItems"/>
/// </para>
/// <para>
/// 在当前物品堆移除指定数量的物品,被移除的物品应当从游戏中移除。<br/>
/// 如果您并不打算将它们从游戏中移除,请考虑使用 <see cref="PickItem"/> 和 <see cref="PickItems"/>
/// </para>
/// </summary>
/// <param name="number">
/// <para>Quantity to be removed, inputs below zero represent all items</para>
/// <para>要删除的数量小于0的输入代表全部物品</para>
/// </param>
/// <returns>
/// <para>The remaining number, if the number of items in the current item stack is less than the specified number. Otherwise,0</para>
/// <para>若物品槽内物品少于指定的数量返回相差的数量。否则返回0</para>
/// </returns>
public int RemoveItem(int number);
/// <summary>
/// <para>Clear current stack, which means should remove all items inside current stack from the game here</para>
/// <para>清除当前物品堆,意味着从游戏中移除当前堆中的所有物品</para>。
/// </summary>
public void ClearStack();
/// <summary>
/// <para>Create a new ItemStack with the given item as the first item</para>
/// <para>以给定的物品为第一个物品创建物品堆</para>
/// </summary>
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

@ -0,0 +1,63 @@
using System;
using Godot;
namespace ColdMint.scripts.item.itemStacks;
/// <summary>
/// <para>One of the basic item stacks, there are always one item in stack</para>
/// <para>单身狗物品堆,基础物品堆之一,堆中永远只会有一个物品</para>
/// </summary>
/// <seealso cref="UniqueItemStack"/><seealso cref="CommonItemStack"/>
public class SingleItemStack(IItem item) : IItemStack
{
public IItem Item { get; init; } = item;
public int MaxQuantity => 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;
public bool CanAddItem(IItem item) => false;
public bool AddItem(IItem item) => false;
public int CanTakeFrom(IItemStack itemStack) => 0;
public bool TakeFrom(IItemStack itemStack) => false;
public IItem? GetItem()
{
return Empty ? null : Item;
}
public IItem? PickItem()
{
if (Empty) return null;
Empty = true;
return Item;
}
public IItemStack? PickItems(int value)
{
if (value == 0 || Empty) return null;
Empty = true;
return new SingleItemStack(Item);
}
public int RemoveItem(int number)
{
if (number == 0 || Empty) return 0;
Empty = true;
Item.Destroy();
return Math.Max(number - 1, 0);
}
public void ClearStack()
{
RemoveItem(1);
}
}

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

@ -1,10 +1,12 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.projectile;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.weapon;
namespace ColdMint.scripts.item.weapon;
/// <summary>
/// <para>Projectile weapons</para>
@ -22,31 +24,14 @@ public partial class ProjectileWeapon : WeaponTemplate
/// </summary>
private Marker2D? _marker2D;
/// <summary>
/// <para>List of projectiles</para>
/// <para>抛射体列表</para>
/// </summary>
private string[]? _projectiles;
[Export] protected PackedScene[] ProjectileScenes { get; set; } = [];
private Dictionary<string, PackedScene>? _projectileCache;
private Node2D? _projectileContainer;
public override void _Ready()
{
base._Ready();
_marker2D = GetNode<Marker2D>("Marker2D");
_projectileCache = new Dictionary<string, PackedScene>();
_projectiles = GetMeta("Projectiles", "").AsStringArray();
foreach (var projectileItem in _projectiles)
{
var packedScene = GD.Load<PackedScene>(projectileItem);
if (packedScene == null)
{
continue;
}
_projectileCache.Add(projectileItem, packedScene);
}
_projectileContainer = GetNode("/root/Game/ProjectileContainer") as Node2D;
}
@ -54,13 +39,10 @@ public partial class ProjectileWeapon : WeaponTemplate
protected override void DoFire(Node2D? owner, Vector2 enemyGlobalPosition)
{
if (_projectileCache == null || _projectiles == null || owner == null || _projectileContainer == null ||
_marker2D == null)
{
return;
}
if (owner == null || _projectileContainer == null || _marker2D == null) return;
if (_projectiles.IsEmpty())
//空列表检查
if (ProjectileScenes is [])
{
LogCat.LogError("projectiles_is_empty");
return;
@ -68,7 +50,8 @@ public partial class ProjectileWeapon : WeaponTemplate
//Get the first projectile
//获取第一个抛射体
var projectileScene = _projectileCache[_projectiles[0]];
var projectileScene = ProjectileScenes[0];
// var projectileScene = _projectileCache[_projectiles[0]];
var projectile = NodeUtils.InstantiatePackedScene<ProjectileTemplate>(projectileScene, _projectileContainer);
if (projectile == null) return;
projectile.Owner = owner;

View File

@ -1,28 +1,46 @@
using System;
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.inventory;
using Godot;
namespace ColdMint.scripts.weapon;
namespace ColdMint.scripts.item.weapon;
/// <summary>
/// <para>WeaponTemplate</para>
/// <para>武器模板</para>
/// </summary>
public partial class WeaponTemplate : RigidBody2D, IItem
public abstract partial class WeaponTemplate : RigidBody2D, IItem
{
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public string? Id { get; set; }
public int Quantity { get; set; }
public int MaxStackQuantity { get; set; }
public Texture2D? Icon { get; set; }
public new string? Name { get; set; }
public string? Description { get; set; }
public Action<IItem>? OnUse { get; set; }
public Func<IItem, Node>? OnInstantiation { get; set; }
//Implements IItem
[Export] public virtual string Id { get; private set; } = "ID";
protected Texture2D? UniqueIcon { get; set; }
public Texture2D Icon => UniqueIcon ?? ItemTypeManager.DefaultIconOf(Id);
protected string? UniqueName { get; set; }
public new string Name => UniqueName ?? ItemTypeManager.DefaultNameOf(Id);
protected string? UniqueDescription { get; set; }
public string? Description => UniqueDescription ?? ItemTypeManager.DefaultDescriptionOf(Id);
public void Use(Node2D? owner, Vector2 targetGlobalPosition)
{
Fire(owner, targetGlobalPosition);
}
public virtual void Destroy()
{
QueueFree();
}
public bool CanStackWith(IItem item) => false;
/// <summary>
@ -44,8 +62,8 @@ public partial class WeaponTemplate : RigidBody2D, IItem
/// </summary>
public bool EnableContactInjury;
private int _minContactInjury;
private int _maxContactInjury;
[Export] private int _minContactInjury = 1;
[Export] private int _maxContactInjury = 2;
private DateTime? _lastFiringTime;
@ -54,6 +72,7 @@ public partial class WeaponTemplate : RigidBody2D, IItem
/// <para>开火间隔</para>
/// </summary>
private TimeSpan _firingInterval;
[Export] private long _firingIntervalAsMillisecond = 100;
/// <summary>
@ -64,7 +83,7 @@ public partial class WeaponTemplate : RigidBody2D, IItem
///<para>When the weapon is fired, how much recoil is applied to the user, in units: the number of cells, and the X direction of the force is automatically inferred.</para>
///<para>武器开火要对使用者施加多大的后坐力单位格数力的X方向是自动推断的。</para>
/// </remarks>
private Vector2 _recoil;
[Export] private Vector2 _recoil;
/// <summary>
/// <para>This area represents the collision range of the weapon, and when other nodes enter this area, they will deal damage.</para>
@ -84,16 +103,8 @@ public partial class WeaponTemplate : RigidBody2D, IItem
_damageArea2D = GetNode<Area2D>("DamageArea2D");
_damageArea2D.BodyEntered += OnBodyEnter;
_damageArea2D.BodyExited += OnBodyExited;
Id = GetMeta("ID", "1").AsString();
Quantity = GetMeta("Quantity", "1").AsInt32();
MaxStackQuantity = GetMeta("MaxStackQuantity", Config.MaxStackQuantity).AsInt32();
Icon = GetMeta("Icon", "").As<Texture2D>();
Name = GetMeta("Name", "").AsString();
Description = GetMeta("Description", "").AsString();
_firingInterval = TimeSpan.FromMilliseconds(GetMeta("FiringInterval", "100").AsInt64());
_minContactInjury = GetMeta("MinContactInjury", "1").AsInt32();
_maxContactInjury = GetMeta("MaxContactInjury", "2").AsInt32();
_recoil = GetMeta("Recoil", Vector2.Zero).AsVector2();
_firingInterval = TimeSpan.FromMilliseconds(_firingIntervalAsMillisecond);
}
private void OnBodyExited(Node node)
@ -154,7 +165,7 @@ public partial class WeaponTemplate : RigidBody2D, IItem
//Determine if your side can cause damage
//判断所属的阵营是否可以造成伤害
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
CampManager.GetCamp(characterTemplate.CampId));
if (!canCauseHarm)
{
return;
@ -182,9 +193,7 @@ public partial class WeaponTemplate : RigidBody2D, IItem
/// <para>翻转武器</para>
/// </summary>
/// <param name="facingLeft"></param>
public void Flip(bool facingLeft)
{
}
public void Flip(bool facingLeft) { }
/// <summary>
@ -240,7 +249,5 @@ public partial class WeaponTemplate : RigidBody2D, IItem
/// <para>Execute fire</para>
/// <para>执行开火</para>
/// </summary>
protected virtual void DoFire(Node2D? owner, Vector2 enemyGlobalPosition)
{
}
protected abstract void DoFire(Node2D? owner, Vector2 enemyGlobalPosition);
}

View File

@ -1,12 +1,15 @@
using System;
using System.IO;
using System.Text;
using ColdMint.scripts.camp;
using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.item;
using ColdMint.scripts.map;
using ColdMint.scripts.map.roomInjectionProcessor;
using Godot;
namespace ColdMint.scripts.loader.uiLoader;
@ -65,6 +68,7 @@ public partial class MainMenuLoader : UiLoaderTemplate
testLootList.AddLootEntry(packsack);
LootListManager.RegisterLootList(testLootList);
}
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor());
@ -90,6 +94,10 @@ public partial class MainMenuLoader : UiLoaderTemplate
var aborigines = new Camp(Config.CampId.Aborigines);
CampManager.AddCamp(aborigines);
_gameScene = (PackedScene)GD.Load("res://scenes/game.tscn");
//Temp: Register ItemType
//临时:注册物品类型
ItemTypeManager.StaticRegister();
}
public override void InitializeUi()

View File

@ -1,8 +1,11 @@
using System;
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.weapon;
using ColdMint.scripts.item;
using ColdMint.scripts.item.weapon;
using Godot;
namespace ColdMint.scripts.projectile;
@ -64,10 +67,10 @@ public partial class ProjectileTemplate : CharacterBody2D
Area2D.Monitoring = true;
Area2D.BodyEntered += OnBodyEnter;
Area2D.BodyExited += OnBodyExited;
Durability = GetMeta("Durability", "1").AsDouble();
MaxDamage = GetMeta("MaxDamage", "7").AsInt32();
MinDamage = GetMeta("MinDamage", "5").AsInt32();
DamageType = GetMeta("DamageType", Config.DamageType.Physical).AsInt32();
Durability = GetMeta("Durability", "1").AsDouble();
MaxDamage = GetMeta("MaxDamage", "7").AsInt32();
MinDamage = GetMeta("MinDamage", "5").AsInt32();
DamageType = GetMeta("DamageType", Config.DamageType.Physical).AsInt32();
KnockbackForce = GetMeta("Knockback", Vector2.Zero).AsVector2();
//life(ms)
//子弹的存在时间(毫秒)
@ -111,7 +114,9 @@ public partial class ProjectileTemplate : CharacterBody2D
return true;
}
if (target is WeaponTemplate)
//Match any item now
//现在使它识别任何物品
if (target is IItem)
{
//Bullets are allowed to strike objects.
//允许子弹撞击物品。
@ -126,7 +131,7 @@ public partial class ProjectileTemplate : CharacterBody2D
//First get the owner's camp and compare it with the target camp
//先获取主人的阵营与目标阵营进行比较
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
CampManager.GetCamp(characterTemplate.CampId));
return canCauseHarm;
}
@ -169,7 +174,8 @@ public partial class ProjectileTemplate : CharacterBody2D
force.Y = KnockbackForce.Y * Config.CellSize;
characterTemplate.AddForce(force);
}
}else if (target is WeaponTemplate weaponTemplate)
}
else if (target is WeaponTemplate weaponTemplate)
{
if (KnockbackForce != Vector2.Zero)
{
@ -225,9 +231,7 @@ public partial class ProjectileTemplate : CharacterBody2D
/// <para>当子弹离开节点时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void OnBodyExited(Node2D node)
{
}
protected virtual void OnBodyExited(Node2D node) { }
/// <summary>

View File

@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.weapon;
using ColdMint.scripts.item.weapon;
using Godot;
using Packsack = ColdMint.scripts.item.Packsack;
namespace ColdMint.scripts.utils;
/// <summary>