Merge pull request #7 from Web13234/loot_rebuild

Loot rebuild-Done
This commit is contained in:
Cold-Mint 2024-06-16 20:22:58 +08:00 committed by GitHub
commit 7f55bd7d53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 613 additions and 468 deletions

1
.gitignore vendored
View File

@ -5,4 +5,3 @@ export_presets.cfg
.vs/ .vs/
*.translation *.translation
*.user *.user
*.DotSettings

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=packsack/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -2,3 +2,15 @@
scene_path: res://prefab/weapons/staffOfTheUndead.tscn scene_path: res://prefab/weapons/staffOfTheUndead.tscn
icon_path: res://sprites/weapon/staffOfTheUndead_icon.png icon_path: res://sprites/weapon/staffOfTheUndead_icon.png
max_stack_value: 1 max_stack_value: 1
- id: degraded_staff_of_the_undead
scene_path: res://prefab/weapons/staffOfTheUndead.tscn
icon_path: res://sprites/weapon/staffOfTheUndead_icon.png
max_stack_value: 1
custom_args:
- name: FiringIntervalAsMillisecond
type: int
value: 1000
- name: UniqueName
type: string
value: 劣化的死灵法杖

View File

@ -23,10 +23,17 @@ log_player_packed_scene_not_exist,玩家预制场景不存在。,Player packed s
log_exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。 log_exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。
log_enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。 log_enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。
log_death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。 log_death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。
log_loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。 log_loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。
log_not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。 log_not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
log_loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。 log_loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。
log_loot_data_add,生成战利品{0},Add loot {0},戦利品{0}を生成する
log_warning_node_cannot_cast_to,创建的物品{0}无法被转型为类型{1},Created items {0} cannot be cast into type {1},作成されたアイテム {0} をタイプ {1} にキャストすることはできません。
log_start_item_register_from_file,开始从文件注册物品信息,Start registering item information from files,アイテム情報をファイルから登録開始 log_start_item_register_from_file,开始从文件注册物品信息,Start registering item information from files,アイテム情報をファイルから登録開始
log_item_register_from_file,从文件{0}中注册物品信息,Registering item information from file {0},ファイル{0}からアイテム情報を登録する log_found_files,找到{0}个文件,Found {0} files,{0}ファイルが見つかりました
log_item_register_find_item_in_file,注册发现的物品{0},Register discovered item {0},見つかったアイテム{0}を登録 log_found_item_types,从文件中找到{0}个物品类型,Found {0} item types in files,ファイルから{0}個のアイテム・タイプが見つかった
log_register_item,注册物品类型{0}结果为{1},Registered item type {0}; results in {1},登録されたアイテム・タイプ {0} の結果は {1} です。
log_error_when_open_item_regs_dir,尝试打开物品信息目录时发生错误,Error when opening itemRegs dir,アイテム情報カタログを開こうとしてエラーが発生しました。 log_error_when_open_item_regs_dir,尝试打开物品信息目录时发生错误,Error when opening itemRegs dir,アイテム情報カタログを開こうとしてエラーが発生しました。
log_wrong_custom_arg,不匹配的参数:类型为{0}而值为{1},Mismatched parameter: type {0} and value {1},パラメータの不一致:型{0}と値{1}。
1 id zh en ja
23 log_exit_the_room_debug 节点{0}退出房间{1}。 Node {0} exits room {1}. ノード{0}が部屋{1}を退出します。
24 log_enter_the_room_debug 节点{0}进入房间{1}。 Node {0} enters room {1}. ノード{0}が部屋{1}に入ります。
25 log_death_info 生物{0}被{1}击败。 Creature {0} was defeated by {1}. 生物{0}が{1}によって打ち負かされました。
26 log_loot_list_has_no_entries ID为{0}的战利品表,没有指定条目。 Loot list with ID {0}, no entry specified. ID{0}の戦利品テーブルは、エントリ指定されていません。
27 log_loot_list_has_no_entries log_not_within_the_loot_spawn_range ID为{0}的战利品表,没有指定条目。 给定的数值{0}没有在战利品{1}的生成范围{2}内。 Loot list with ID {0}, no entry specified. The given value {0} is not within the spawn range {2} of loot {1}. ID{0}の戦利品テーブルは、エントリ指定されていません。 与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
28 log_not_within_the_loot_spawn_range log_loot_data_quantity 给定的数值{0}没有在战利品{1}的生成范围{2}内。 有{0}个战利品数据被返回。 The given value {0} is not within the spawn range {2} of loot {1}. {0} loot data was returned. 与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。 {0}個の戦利品データが返されます。
29 log_loot_data_quantity log_loot_data_add 有{0}个战利品数据被返回。 生成战利品{0} {0} loot data was returned. Add loot {0} {0}個の戦利品データが返されます。 戦利品{0}を生成する
30 log_warning_node_cannot_cast_to 创建的物品{0}无法被转型为类型{1} Created items {0} cannot be cast into type {1} 作成されたアイテム {0} をタイプ {1} にキャストすることはできません。
31 log_start_item_register_from_file 开始从文件注册物品信息 Start registering item information from files アイテム情報をファイルから登録開始
32 log_found_files 找到{0}个文件 Found {0} files {0}ファイルが見つかりました
33 log_found_item_types 从文件中找到{0}个物品类型 Found {0} item types in files ファイルから{0}個のアイテム・タイプが見つかった
34 log_start_item_register_from_file log_register_item 开始从文件注册物品信息 注册物品类型{0}结果为{1} Start registering item information from files Registered item type {0}; results in {1} アイテム情報をファイルから登録開始 登録されたアイテム・タイプ {0} の結果は {1} です。
35 log_item_register_from_file log_error_when_open_item_regs_dir 从文件{0}中注册物品信息 尝试打开物品信息目录时发生错误 Registering item information from file {0} Error when opening itemRegs dir ファイル{0}からアイテム情報を登録する アイテム情報カタログを開こうとしてエラーが発生しました。
36 log_item_register_find_item_in_file log_wrong_custom_arg 注册发现的物品{0} 不匹配的参数:类型为{0}而值为{1} Register discovered item {0} Mismatched parameter: type {0} and value {1} 見つかったアイテム{0}を登録 パラメータの不一致:型{0}と値{1}。
37
38
39

View File

@ -31,10 +31,10 @@ radius = 129.027
collision_layer = 64 collision_layer = 64
collision_mask = 38 collision_mask = 38
script = ExtResource("1_ubaid") script = ExtResource("1_ubaid")
LootListId = "test"
metadata/CampId = "Mazoku" metadata/CampId = "Mazoku"
metadata/MaxHp = 50 metadata/MaxHp = 50
metadata/Name = "死灵法师" metadata/Name = "死灵法师"
metadata/LootListId = "Test"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4) position = Vector2(0, 4)

View File

@ -15,12 +15,8 @@ collision_layer = 8
collision_mask = 34 collision_mask = 34
script = ExtResource("1_w8hhv") script = ExtResource("1_w8hhv")
ProjectileScenes = [ExtResource("2_34250")] ProjectileScenes = [ExtResource("2_34250")]
_firingIntervalAsMillisecond = null FiringIntervalAsMillisecond = 300
_recoil = null
Id = "staff_of_the_undead" Id = "staff_of_the_undead"
_minContactInjury = null
_maxContactInjury = null
metadata/Projectiles = PackedStringArray("res://prefab/projectile/curseOfTheUndead.tscn")
[node name="DamageArea2D" type="Area2D" parent="."] [node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8 collision_layer = 8

View File

@ -53,7 +53,7 @@ offset_left = 10.0
offset_top = 13.0 offset_top = 13.0
offset_right = 97.0 offset_right = 97.0
offset_bottom = 38.0 offset_bottom = 38.0
text = "create_room" text = "ui_create_room"
[node name="Label3" type="Label" parent="CreateOrEditorPanel"] [node name="Label3" type="Label" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -61,7 +61,7 @@ offset_left = 12.0
offset_top = 106.0 offset_top = 106.0
offset_right = 72.0 offset_right = 72.0
offset_bottom = 131.0 offset_bottom = 131.0
text = "describe" text = "ui_describe"
[node name="Label4" type="Label" parent="CreateOrEditorPanel"] [node name="Label4" type="Label" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -69,7 +69,7 @@ offset_left = 15.0
offset_top = 152.0 offset_top = 152.0
offset_right = 75.0 offset_right = 75.0
offset_bottom = 177.0 offset_bottom = 177.0
text = "room_template_collection_prompt" text = "ui_room_template_collection_prompt"
[node name="RoomTemplateTipsLabel" type="Label" parent="CreateOrEditorPanel"] [node name="RoomTemplateTipsLabel" type="Label" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -109,7 +109,7 @@ offset_top = 6.0
offset_right = -11.0 offset_right = -11.0
offset_bottom = 39.0 offset_bottom = 39.0
grow_horizontal = 0 grow_horizontal = 0
text = "close" text = "ui_close"
[node name="Label2" type="Label" parent="CreateOrEditorPanel"] [node name="Label2" type="Label" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -117,7 +117,7 @@ offset_left = 13.0
offset_top = 60.0 offset_top = 60.0
offset_right = 48.0 offset_right = 48.0
offset_bottom = 85.0 offset_bottom = 85.0
text = "name" text = "ui_name"
[node name="CreateRoomButton" type="Button" parent="CreateOrEditorPanel"] [node name="CreateRoomButton" type="Button" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -132,7 +132,7 @@ offset_right = -13.0
offset_bottom = -13.0 offset_bottom = -13.0
grow_horizontal = 0 grow_horizontal = 0
grow_vertical = 0 grow_vertical = 0
text = "creation" text = "ui_creation"
[node name="RoomTemplateCollectionTextEdit" type="TextEdit" parent="CreateOrEditorPanel"] [node name="RoomTemplateCollectionTextEdit" type="TextEdit" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -150,7 +150,7 @@ offset_left = 16.0
offset_top = 289.0 offset_top = 289.0
offset_right = 56.0 offset_right = 56.0
offset_bottom = 314.0 offset_bottom = 314.0
text = "tags" text = "ui_tags"
[node name="TagLineEdit" type="LineEdit" parent="CreateOrEditorPanel"] [node name="TagLineEdit" type="LineEdit" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1
@ -168,7 +168,7 @@ offset_left = 14.0
offset_top = 368.0 offset_top = 368.0
offset_right = 283.0 offset_right = 283.0
offset_bottom = 393.0 offset_bottom = 393.0
text = "room_injection_processor" text = "ui_room_injection_processor"
[node name="RoomInjectionProcessorDataTextEdit" type="TextEdit" parent="CreateOrEditorPanel"] [node name="RoomInjectionProcessorDataTextEdit" type="TextEdit" parent="CreateOrEditorPanel"]
layout_mode = 1 layout_mode = 1

View File

@ -31,7 +31,7 @@ public static class Config
/// <para>A trophy table for testing</para> /// <para>A trophy table for testing</para>
/// <para>测试用的战利品表</para> /// <para>测试用的战利品表</para>
/// </summary> /// </summary>
public const string Test = "Test"; public const string Test = "test";
} }
/// <summary> /// <summary>

View File

@ -10,7 +10,9 @@ using ColdMint.scripts.inventory;
using ColdMint.scripts.item; using ColdMint.scripts.item;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using ColdMint.scripts.item.weapon; using ColdMint.scripts.item.weapon;
using ColdMint.scripts.loot;
using ColdMint.scripts.pickable; using ColdMint.scripts.pickable;
using Godot; using Godot;
namespace ColdMint.scripts.character; namespace ColdMint.scripts.character;
@ -107,11 +109,7 @@ public partial class CharacterTemplate : CharacterBody2D
private DamageNumberNodeSpawn? _damageNumber; private DamageNumberNodeSpawn? _damageNumber;
/// <summary> [Export] public string LootListId { get; private set; } = "";
/// <para>Character referenced loot table</para>
/// <para>角色引用的战利品表</para>
/// </summary>
private LootList? _lootList;
private HealthBar? _healthBar; private HealthBar? _healthBar;
private DateTime _lastDamageTime; private DateTime _lastDamageTime;
@ -208,12 +206,6 @@ public partial class CharacterTemplate : CharacterBody2D
CampId = GetMeta("CampId", Config.CampId.Default).AsString(); CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32(); MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
var lootListId = GetMeta("LootListId", string.Empty).AsString(); var lootListId = GetMeta("LootListId", string.Empty).AsString();
if (!string.IsNullOrEmpty(lootListId))
{
//If a loot table is specified, get the loot table.
//如果指定了战利品表,那么获取战利品表。
_lootList = LootListManager.GetLootList(lootListId);
}
if (MaxHp <= 0) if (MaxHp <= 0)
{ {
@ -461,20 +453,9 @@ public partial class CharacterTemplate : CharacterBody2D
/// </summary> /// </summary>
protected void CreateLootObject() protected void CreateLootObject()
{ {
if (_lootList == null) var lootData = LootListManager.GenerateLootData(LootListId);
{
return;
}
var lootDataArray = _lootList.GenerateLootData();
if (lootDataArray.Length == 0)
{
return;
}
var finalGlobalPosition = GlobalPosition; var finalGlobalPosition = GlobalPosition;
CallDeferred("GenerateLootObjects", this, lootDataArray, finalGlobalPosition); GenerateLootObjects(GetParent(), lootData, finalGlobalPosition);
} }
/// <summary> /// <summary>
@ -482,13 +463,17 @@ public partial class CharacterTemplate : CharacterBody2D
/// <para>生成战利品对象</para> /// <para>生成战利品对象</para>
/// </summary> /// </summary>
/// <param name="parentNode"></param> /// <param name="parentNode"></param>
/// <param name="lootDataArray"></param> /// <param name="lootData"></param>
/// <param name="position"></param> /// <param name="position"></param>
public void GenerateLootObjects(Node parentNode, public void GenerateLootObjects(Node parentNode,
LootData[] lootDataArray, IEnumerable<LootDatum> lootData,
Vector2 position) Vector2 position)
{ {
LootListManager.GenerateLootObjects(parentNode, lootDataArray, position); foreach (var lootDatum in lootData)
{
var (id, amount) = lootDatum.Value;
ItemTypeManager.CreateItems(id, amount, parentNode, position);
}
} }
/// <summary> /// <summary>
@ -601,14 +586,8 @@ public partial class CharacterTemplate : CharacterBody2D
//Generates a random number that controls the horizontal velocity of thrown items (range: 0.01 to 1) //Generates a random number that controls the horizontal velocity of thrown items (range: 0.01 to 1)
//生成一个随机数,用于控制抛出物品的水平方向速度(范围为0.01到1) //生成一个随机数,用于控制抛出物品的水平方向速度(范围为0.01到1)
var percent = GD.Randf() + 0.01f; var percent = GD.Randf() + 0.01f;
if (GD.Randf() > 0.5) var horizontalVelocity = horizontalDirection * percent * GD.Randf() > 0.5 ? 1f : -1f;
{ ThrowItem(i, -1, new Vector2(horizontalVelocity, height));
ThrowItem(i, -1, new Vector2(horizontalDirection * percent, height));
}
else
{
ThrowItem(i, -1, new Vector2(-horizontalDirection * percent, height));
}
} }
} }
@ -671,7 +650,7 @@ public partial class CharacterTemplate : CharacterBody2D
return; return;
} }
CallDeferred("NodeReparent", node2D); CallDeferred(nameof(NodeReparent), node2D);
switch (item) switch (item)
{ {
case PickAbleTemplate pickAbleTemplate: case PickAbleTemplate pickAbleTemplate:

View File

@ -106,7 +106,7 @@ public partial class DamageNumberNodeSpawn : Marker2D
return; return;
} }
CallDeferred("AddDamageNumberNode", damageNumber); CallDeferred(nameof(AddDamageNumberNode), damageNumber);
damageNumber.Position = GlobalPosition; damageNumber.Position = GlobalPosition;
if (damageTemplate.MoveLeft) if (damageTemplate.MoveLeft)
{ {

View File

@ -1,9 +0,0 @@
using Godot;
namespace ColdMint.scripts.inventory;
public partial class LootData : GodotObject
{
public string? ResPath { get; set; }
public int Quantity { get; set; }
}

View File

@ -1,32 +0,0 @@
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Loot entry</para>
/// <para>战利品条目</para>
/// </summary>
public class LootEntry
{
/// <summary>
/// <para>generation probability</para>
/// <para>生成概率</para>
/// </summary>
public double? Chance { get; set; }
/// <summary>
/// <para>Minimum number of generated</para>
/// <para>最小生成多少个</para>
/// </summary>
public int MinQuantity { get; set; }
/// <summary>
/// <para>The maximum number of files to be generated</para>
/// <para>最多生成多少个</para>
/// </summary>
public int MaxQuantity { get; set; }
/// <summary>
/// <para>resources path</para>
/// <para>资源路径</para>
/// </summary>
public string? ResPath { get; set; }
}

View File

@ -1,93 +0,0 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Loot list</para>
/// <para>战利品表</para>
/// </summary>
public class LootList
{
/// <summary>
/// <para>Id</para>
/// <para>战利品表的Id</para>
/// </summary>
public string? Id { get; set; }
private List<LootEntry>? _lootEntrieList;
/// <summary>
/// <para>Add loot entry</para>
/// <para>添加战利品条目</para>
/// </summary>
/// <param name="lootEntry"></param>
public void AddLootEntry(LootEntry lootEntry)
{
if (_lootEntrieList == null)
{
_lootEntrieList = new List<LootEntry>();
}
_lootEntrieList.Add(lootEntry);
}
/// <summary>
/// <para>GenerateLootData</para>
/// <para>生成战利品数据</para>
/// </summary>
/// <returns></returns>
public LootData[] GenerateLootData()
{
var lootDataList = new List<LootData>();
if (_lootEntrieList == null)
{
LogCat.LogWithFormat("loot_list_has_no_entries", Id);
return lootDataList.ToArray();
}
foreach (var lootEntry in _lootEntrieList)
{
var chance = GD.Randf();
if (chance > lootEntry.Chance)
{
//If the random number is greater than the generation probability, skip the current loop.
//如果随机数大于生成概率,则跳过当前循环。
LogCat.LogWithFormat("not_within_the_loot_spawn_range", chance, lootEntry.ResPath, lootEntry.Chance);
continue;
}
//We generate a loot data for each loot entry.
//我们为每个战利品条目生成一个战利品数据。
var quantity = GD.RandRange(lootEntry.MinQuantity, lootEntry.MaxQuantity);
var lootData = new LootData
{
ResPath = lootEntry.ResPath,
Quantity = quantity
};
lootDataList.Add(lootData);
}
LogCat.LogWithFormat("loot_data_quantity", lootDataList.Count);
return lootDataList.ToArray();
}
/// <summary>
/// <para>Remove loot entry</para>
/// <para>移除战利品条目</para>
/// </summary>
/// <param name="lootEntry"></param>
/// <returns></returns>
public bool RemoveLootEntry(LootEntry lootEntry)
{
if (_lootEntrieList == null)
{
return false;
}
return _lootEntrieList.Remove(lootEntry);
}
}

View File

@ -1,117 +0,0 @@
using System.Collections.Generic;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>LootListManager</para>
/// <para>战利品表管理器</para>
/// </summary>
public static class LootListManager
{
private static Dictionary<string, LootList>? _lootListDictionary;
/// <summary>
/// <para>Register loot table</para>
/// <para>注册战利品表</para>
/// </summary>
/// <param name="lootList"></param>
public static bool RegisterLootList(LootList lootList)
{
var id = lootList.Id;
if (id == null)
{
return false;
}
if (_lootListDictionary != null) return _lootListDictionary.TryAdd(id, lootList);
_lootListDictionary = new Dictionary<string, LootList> { { id, lootList } };
return true;
}
/// <summary>
/// <para>Get Loot List</para>
/// <para>获取战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static LootList? GetLootList(string id)
{
return _lootListDictionary?.GetValueOrDefault(id);
}
/// <summary>
/// <para>Generate loot objects</para>
/// <para>生成战利品对象</para>
/// </summary>
public static void GenerateLootObjects(Node parentNode, LootData[] lootDataArray, Vector2 position)
{
if (lootDataArray.Length == 0)
{
return;
}
//Cache the loaded PackedScene object.
//缓存已加载的PackedScene对象。
Dictionary<string, PackedScene> packedSceneDictionary = new();
foreach (var lootData in lootDataArray)
{
if (string.IsNullOrEmpty(lootData.ResPath) || lootData.Quantity == 0)
{
continue;
}
if (!packedSceneDictionary.TryGetValue(lootData.ResPath, out var packedScene))
{
packedScene = GD.Load<PackedScene>(lootData.ResPath);
packedSceneDictionary.TryAdd(lootData.ResPath, packedScene);
}
for (var i = 0; i < lootData.Quantity; i++)
{
//Generate as many loot instance objects as there are loot.
//有多少个战利品就生成多少个战利品实例对象。
CreateLootInstanceObject(parentNode, packedScene, position);
}
}
}
/// <summary>
/// <para>Create a loot instance object</para>
/// <para>创建战利品实例对象</para>
/// </summary>
private static void CreateLootInstanceObject(Node parent, PackedScene? packedScene, Vector2 position)
{
if (packedScene == null)
{
return;
}
var lootObject = NodeUtils.InstantiatePackedScene<Node2D>(packedScene, parent);
if (lootObject == null)
{
return;
}
lootObject.Position = position;
}
/// <summary>
/// <para>Remove loot list</para>
/// <para>移除战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool UnregisterLootList(string id)
{
if (_lootListDictionary == null)
{
return false;
}
return _lootListDictionary.Remove(id);
}
}

View File

@ -67,16 +67,16 @@ public class UniversalItemContainer : IItemContainer
public bool AddItemStack(IItemStack itemStack) public bool AddItemStack(IItemStack itemStack)
{ {
while (true) ItemSlotNode? itemSlotNode = Match(itemStack);
while (itemSlotNode is not null)
{ {
var itemSlotNode = Match(itemStack);
if (itemSlotNode == null)
return false;
if (itemSlotNode.AddItemStack(itemStack)) if (itemSlotNode.AddItemStack(itemStack))
return true; return true;
itemSlotNode = Match(itemStack);
} }
return false;
} }
public int GetSelectIndex() public int GetSelectIndex()

View File

@ -16,9 +16,9 @@ namespace ColdMint.scripts.item;
public interface ICommonItem : IItem public interface ICommonItem : IItem
{ {
/// <summary> /// <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>Method to clone an instance same with self. Will be used to pick out item instance from a <see cref="CommonItemStack"/></para>
/// <para>复制与自身相同的实例的方法。将用于从 <see cref="CommonItemStack"/> 中拿取新的物品实例。</para> /// <para>复制与自身相同的实例的方法。将用于从 <see cref="CommonItemStack"/> 中拿取新的物品实例。</para>
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
ICommonItem CopyInstance(); ICommonItem CloneInstance();
} }

View File

@ -26,6 +26,14 @@ public interface IItem
/// <para>当前项目的描述</para> /// <para>当前项目的描述</para>
/// </summary> /// </summary>
string? Description { get; } string? Description { get; }
/// <summary>
/// <para>
/// Whether the current item can be put into item containers like packsack.<br/>
/// This attribute is usually set to false for items of the backpack class to avoid pack nesting.
/// </para>
/// <para>当前物品是否可以放入背包类的容器中。一般将背包类物品的该属性设为false来避免背包嵌套。</para>
/// </summary>
bool CanPutInPack { get; }
/// <summary> /// <summary>
/// <para>Execute when current item is used <br/> e.g. when player clicks left mouse button with current item in hand</para> /// <para>Execute when current item is used <br/> e.g. when player clicks left mouse button with current item in hand</para>

View File

@ -1,13 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using Godot; using Godot;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace ColdMint.scripts.item; namespace ColdMint.scripts.item;
/// <summary> /// <summary>
@ -16,68 +12,6 @@ namespace ColdMint.scripts.item;
/// </summary> /// </summary>
public static class ItemTypeManager public static class ItemTypeManager
{ {
//用于yaml反序列化
//Use for yaml deserialization
private record struct ItemTypeInfo(string Id, string ScenePath, string IconPath, int MaxStackValue) { }
/// <summary>
/// <para>Register items from yaml file</para>
/// <para>从文件注册物品</para>
/// </summary>
public static void RegisterFromFile()
{
LogCat.Log("start_item_register_from_file");
// 初始化yaml反序列化器
// initialize yaml deserializer
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance) // convent snake_case
.Build();
//初始化文件目录
//initialize file dir
var itemRegsDirPath = "res://data/itemRegs/";
var itemRegsDir = DirAccess.Open(itemRegsDirPath);
if (DirAccess.GetOpenError() is not Error.Ok)
{
LogCat.LogError("error_when_open_item_regs_dir");
}
//遍历目录,找到要注册的文件
//traverse the dir, find files to register
foreach (var file in itemRegsDir.GetFiles())
{
if (file is null) continue;
LogCat.LogWithFormat("item_register_from_file", file);
//读取文件解析为类型为info的IEnumerable
//read file, parse to an IEnumerable of type infos
var yamlFile = FileAccess.Open($"{itemRegsDirPath}/{file}", FileAccess.ModeFlags.Read);
var yamlString = yamlFile.GetAsText();
var typeInfos = deserializer.Deserialize<IEnumerable<ItemTypeInfo>>(yamlString);
yamlFile.Close();
//遍历类型信息并注册它们。
//traverse type infos and register them.
foreach (var typeInfo in typeInfos)
{
LogCat.LogWithFormat("item_register_find_item_in_file", typeInfo.Id);
var scene = ResourceLoader.Load<PackedScene>(typeInfo.ScenePath);
var icon = ResourceLoader.Load<Texture2D>(typeInfo.IconPath);
var itemType = new ItemType(typeInfo.Id,
() => NodeUtils.InstantiatePackedScene<Packsack>(scene),
icon, typeInfo.MaxStackValue);
Register(itemType);
}
}
}
/// <summary>
/// <para>Register items here</para>
/// <para>在这里注册物品</para>
/// </summary>
public static void StaticRegister() { }
private static Dictionary<string, ItemType> Registry { get; } = []; private static Dictionary<string, ItemType> Registry { get; } = [];
private static Texture2D DefaultTexture { get; } = new PlaceholderTexture2D(); private static Texture2D DefaultTexture { get; } = new PlaceholderTexture2D();
@ -101,9 +35,71 @@ public static class ItemTypeManager
/// <para>Returns null when the id is not registered.</para> /// <para>Returns null when the id is not registered.</para>
/// <para>当物品id没有注册时返回null</para> /// <para>当物品id没有注册时返回null</para>
/// </returns> /// </returns>
/// <seealso cref="NewItems"/><seealso cref="CreateItem"/>
public static IItem? NewItem(string id) => public static IItem? NewItem(string id) =>
Registry.TryGetValue(id, out var itemType) ? itemType.NewItemFunc() : null; Registry.TryGetValue(id, out var itemType) ? itemType.NewItemFunc() : null;
/// <summary>
/// <para>Creates new instances in given amount of the item registered to the given id.</para>
/// <para>创建给定数量的注册到给定 id 的物品的新实例。</para>
/// </summary>
/// <returns></returns>
/// <seealso cref="NewItem"/><seealso cref="CreateItems"/>
public static IList<IItem> NewItems(string id, int amount)
{
IList<IItem> result = [];
for (int i = 0; i < amount; i++)
{
if (NewItem(id) is { } item) result.Add(item);
}
return result;
}
/// <summary>
/// <para>Creates new instance of the item registered to the given id, and put it into given position in both node tree and 2D space</para>
/// <para>创建以给定 id 注册的物品的新实例,并将其放到节点树和二维空间中的给定位置</para>
/// </summary>
/// <param name="id"></param>
/// <param name="parent"></param>
/// <param name="position">
/// <para>Position in global coordinate</para>
/// <para>全局坐标中的位置</para>
/// </param>
/// <seealso cref="NewItem"/><seealso cref="CreateItems"/>
public static IItem? CreateItem(string id, Node? parent = null, Vector2? position = null)
{
var item = NewItem(id);
parent?.CallDeferred(GodotStringNameUtils.AddChild, (item as Node)!);
if (item is not Node2D node) return item;
if (position is { } pos) node.GlobalPosition = pos;
return item;
}
/// <summary>
/// <para>Creates new instances in given amount of the item registered to the given id, and put them into given position in both node tree and 2D space</para>
/// <para>创建以给定 id 注册的物品的给定数量的新实例,并将其放到节点树和二维空间中的给定位置</para>
/// </summary>
/// <param name="id"></param>
/// <param name="amount"></param>
/// <param name="parent"></param>
/// <param name="position">
/// <para>Position in global coordinate</para>
/// <para>全局坐标中的位置</para>
/// </param>
/// <seealso cref="NewItems"/><seealso cref="CreateItem"/>
public static IList<IItem> CreateItems(string id, int amount, Node? parent = null, Vector2? position = null)
{
IList<IItem> result = [];
for (int i = 0; i < amount; i++)
{
if (CreateItem(id, parent, position) is { } item)
result.Add(item);
}
return result;
}
/// <summary> /// <summary>
/// <para>Get the translated default name of the item type for the given id</para> /// <para>Get the translated default name of the item type for the given id</para>
/// <para>获取指定物品id翻译后的物品名</para> /// <para>获取指定物品id翻译后的物品名</para>

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;
using Godot;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace ColdMint.scripts.item;
/// <summary>
/// 负责从文件注册物品
/// </summary>
public static class ItemTypeRegister
{
/// <summary>
/// <para>Register items here</para>
/// <para>在这里注册物品</para>
/// </summary>
public static void StaticRegister() { }
/// <summary>
/// <para>Register items from yaml file</para>
/// <para>从文件注册物品</para>
/// </summary>
public static void RegisterFromFile()
{
LogCat.Log("start_item_register_from_file");
// initialize yaml deserializer
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance) // convent snake_case
.Build();
// initialize file dir
string itemRegsDirPath = "res://data/itemRegs/";
var itemRegsDir = DirAccess.Open(itemRegsDirPath);
if (DirAccess.GetOpenError() is not Error.Ok)
{
LogCat.LogError("error_when_open_item_regs_dir");
}
// find files
var files = itemRegsDir.GetFiles();
LogCat.LogWithFormat("found_files", files.Length);
// parse files to item type infos
IEnumerable<ItemTypeInfo> typeInfos =
files.SelectMany(file => ParseFile(deserializer, $"{itemRegsDirPath}/{file}")).ToList();
LogCat.LogWithFormat("found_item_types", typeInfos.Count());
// traverse type infos and register them.
foreach (var typeInfo in typeInfos)
{
RegisterTypeInfo(typeInfo);
}
}
private static IList<ItemTypeInfo> ParseFile(IDeserializer deserializer, string filePath)
{
var yamlFile = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
//Read & deserialize
var yamlString = yamlFile.GetAsText();
var typeInfos = deserializer.Deserialize<IList<ItemTypeInfo>>(yamlString);
yamlFile.Close();
return typeInfos;
}
private static void RegisterTypeInfo(ItemTypeInfo typeInfo)
{
//Load scene and icon
var scene = ResourceLoader.Load<PackedScene>(typeInfo.ScenePath);
var icon = ResourceLoader.Load<Texture2D>(typeInfo.IconPath);
//Create init delegate
Func<IItem?> newItemFunc;
if (typeInfo.CustomArgs is null or [])
{
newItemFunc = () => NodeUtils.InstantiatePackedScene<IItem>(scene);
}
else
{
Action<Node?>? setArgs = null;
foreach (var arg in typeInfo.CustomArgs)
{
setArgs +=
node => node?.SetDeferred(arg.Name, arg.ParseValue());
}
newItemFunc = () =>
{
var newItem = NodeUtils.InstantiatePackedScene<IItem>(scene);
setArgs?.Invoke(newItem as Node);
return newItem;
};
}
//construct item type, register
var itemType = new ItemType(typeInfo.Id,
newItemFunc,
icon, typeInfo.MaxStackValue);
var succeed = ItemTypeManager.Register(itemType);
LogCat.LogWithFormat("register_item", itemType.Id, succeed);
}
//Use for yaml deserialization
private record struct ItemTypeInfo(
string Id, string ScenePath, string IconPath, int MaxStackValue,
IList<CustomArg>? CustomArgs) { }
private readonly record struct CustomArg(string Name, CustomArgType Type, string Value)
{
public Variant ParseValue() =>
Type switch
{
CustomArgType.String => Value,
CustomArgType.Int => int.Parse(Value),
CustomArgType.Float => double.Parse(Value),
CustomArgType.Vector2 => ParseVector2FromString(Value),
CustomArgType.Texture => ResourceLoader.Load<Texture2D>("res://sprites/" + Value),
_ => throw new ArgumentOutOfRangeException($"Unknown Arg Type {Type}")
};
private Vector2 ParseVector2FromString(string s)
{
var ss = s.Split(',');
if (ss.Length != 2)
{
LogCat.LogErrorWithFormat("wrong_custom_arg", "Vector2", s);
return Vector2.Zero;
}
return new Vector2(float.Parse(ss[0]), float.Parse(ss[1]));
}
}
private enum CustomArgType
{
String,
Int,
Float,
Vector2,
Texture,
}
}

View File

@ -1,6 +1,7 @@
using ColdMint.scripts.inventory; using ColdMint.scripts.inventory;
using ColdMint.scripts.pickable; using ColdMint.scripts.pickable;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using Godot; using Godot;
using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi; using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi;
@ -15,6 +16,8 @@ public partial class Packsack : PickAbleTemplate
private PackedScene? _packedScene; private PackedScene? _packedScene;
private PacksackUi? _packsackUi; private PacksackUi? _packsackUi;
public override bool CanPutInPack => false;
public override void Destroy() public override void Destroy()
{ {
if (ItemContainer == null) return; if (ItemContainer == null) return;
@ -32,6 +35,7 @@ public partial class Packsack : PickAbleTemplate
{ {
return; return;
} }
if (_packsackUi == null) if (_packsackUi == null)
{ {
_packsackUi = NodeUtils.InstantiatePackedScene<PacksackUi>(_packedScene,this); _packsackUi = NodeUtils.InstantiatePackedScene<PacksackUi>(_packedScene,this);
@ -41,6 +45,7 @@ public partial class Packsack : PickAbleTemplate
_packsackUi.ItemContainer = ItemContainer; _packsackUi.ItemContainer = ItemContainer;
} }
} }
_packsackUi?.Show(); _packsackUi?.Show();
} }
@ -51,6 +56,5 @@ public partial class Packsack : PickAbleTemplate
base._Ready(); base._Ready();
ItemContainer = new UniversalItemContainer(); ItemContainer = new UniversalItemContainer();
_packedScene = GD.Load<PackedScene>("res://prefab/ui/packsackUI.tscn"); _packedScene = GD.Load<PackedScene>("res://prefab/ui/packsackUI.tscn");
} }
} }

View File

@ -67,7 +67,7 @@ public class CommonItemStack(ICommonItem innerItem) : IItemStack
{ {
if(Empty) return null; if(Empty) return null;
Quantity--; Quantity--;
var result = innerItem.CopyInstance(); var result = innerItem.CloneInstance();
if(Empty) innerItem.Destroy(); if(Empty) innerItem.Destroy();
return result; return result;
} }
@ -75,7 +75,7 @@ public class CommonItemStack(ICommonItem innerItem) : IItemStack
public IItemStack? PickItems(int value) public IItemStack? PickItems(int value)
{ {
if (Empty) return null; if (Empty) return null;
var result = new CommonItemStack(innerItem.CopyInstance()); var result = new CommonItemStack(innerItem.CloneInstance());
var n = Math.Min(Quantity, value); var n = Math.Min(Quantity, value);
if (n < 0) if (n < 0)
{ {

View File

@ -18,16 +18,15 @@ public class PacksackStack(Packsack packsack) : IItemStack
public string Name => packsack.Name; public string Name => packsack.Name;
public string? Description => packsack.Description; public string? Description => packsack.Description;
//todo: 只拒绝是背包的物品是权宜之计,应该为物品加入一个“是否可以放入背包”的属性来实现这个判断。
public bool CanAddItem(IItem item) public bool CanAddItem(IItem item)
{ {
if (item is Packsack) return false; if (!item.CanPutInPack) return false;
return packsack.ItemContainer?.CanAddItem(item) ?? false; return packsack.ItemContainer?.CanAddItem(item) ?? false;
} }
public bool AddItem(IItem item) public bool AddItem(IItem item)
{ {
if (item is Packsack) return false; if (!item.CanPutInPack) return false;
return packsack.ItemContainer?.AddItem(item) ?? false; return packsack.ItemContainer?.AddItem(item) ?? false;
} }
@ -45,7 +44,7 @@ public class PacksackStack(Packsack packsack) : IItemStack
public IItem? GetItem() public IItem? GetItem()
{ {
return Empty ? packsack : null; return Empty ? null : packsack;
} }
public IItem? PickItem() public IItem? PickItem()

View File

@ -1,6 +1,9 @@
using System; using System;
using ColdMint.scripts.character; using ColdMint.scripts.character;
using ColdMint.scripts.pickable; using ColdMint.scripts.pickable;
using ColdMint.scripts.damage;
using Godot; using Godot;
namespace ColdMint.scripts.item.weapon; namespace ColdMint.scripts.item.weapon;
@ -12,6 +15,7 @@ namespace ColdMint.scripts.item.weapon;
public abstract partial class WeaponTemplate : PickAbleTemplate public abstract partial class WeaponTemplate : PickAbleTemplate
{ {
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle(); private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public override void Use(Node2D? owner, Vector2 targetGlobalPosition) public override void Use(Node2D? owner, Vector2 targetGlobalPosition)
{ {
Fire(owner, targetGlobalPosition); Fire(owner, targetGlobalPosition);
@ -25,7 +29,16 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
/// <para>开火间隔</para> /// <para>开火间隔</para>
/// </summary> /// </summary>
private TimeSpan _firingInterval; private TimeSpan _firingInterval;
[Export] private long _firingIntervalAsMillisecond = 100; private long _firingIntervalAsMillisecond = 100;
[Export] protected long FiringIntervalAsMillisecond
{
get => _firingIntervalAsMillisecond;
set
{
_firingIntervalAsMillisecond = value;
_firingInterval = TimeSpan.FromMilliseconds(_firingIntervalAsMillisecond);
}
}
/// <summary> /// <summary>
@ -38,16 +51,7 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
/// </remarks> /// </remarks>
[Export] private Vector2 _recoil; [Export] private Vector2 _recoil;
public override void _Ready() public override void _Ready() { }
{
_firingInterval = TimeSpan.FromMilliseconds(_firingIntervalAsMillisecond);
}
/// <summary> /// <summary>
/// <para>Discharge of the weapon</para> /// <para>Discharge of the weapon</para>

View File

@ -8,9 +8,11 @@ using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.inventory; using ColdMint.scripts.inventory;
using ColdMint.scripts.item; using ColdMint.scripts.item;
using ColdMint.scripts.loot;
using ColdMint.scripts.map; using ColdMint.scripts.map;
using ColdMint.scripts.map.roomInjectionProcessor; using ColdMint.scripts.map.roomInjectionProcessor;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.loader.uiLoader; namespace ColdMint.scripts.loader.uiLoader;
@ -47,31 +49,6 @@ public partial class MainMenuLoader : UiLoaderTemplate
LogCat.MinLogLevel = LogCat.DisableAllLogLevel; LogCat.MinLogLevel = LogCat.DisableAllLogLevel;
} }
//注册测试使用的战利品表
if (Config.IsDebug())
{
var testLootList = new LootList
{
Id = Config.LootListId.Test
};
var staffOfTheUndead = new LootEntry
{
Chance = 0.05f,
MaxQuantity = 5,
MinQuantity = 1,
ResPath = "res://prefab/weapons/staffOfTheUndead.tscn"
};
testLootList.AddLootEntry(staffOfTheUndead);
var packsack = new LootEntry
{
Chance = 1f,
MaxQuantity = 1,
MinQuantity = 1,
ResPath = "res://prefab/packsacks/packsack.tscn"
};
testLootList.AddLootEntry(packsack);
LootListManager.RegisterLootList(testLootList);
}
ContributorDataManager.RegisterAllContributorData(); ContributorDataManager.RegisterAllContributorData();
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler()); DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
@ -87,6 +64,7 @@ public partial class MainMenuLoader : UiLoaderTemplate
Directory.CreateDirectory(dataPath); Directory.CreateDirectory(dataPath);
} }
//Registered camp //Registered camp
//注册阵营 //注册阵营
var defaultCamp = new Camp(Config.CampId.Default) var defaultCamp = new Camp(Config.CampId.Default)
@ -104,10 +82,13 @@ public partial class MainMenuLoader : UiLoaderTemplate
//Register ItemTypes from file //Register ItemTypes from file
//从文件注册物品类型 //从文件注册物品类型
ItemTypeManager.RegisterFromFile(); ItemTypeRegister.RegisterFromFile();
//Hardcoded ItemTypes Register //Hardcoded ItemTypes Register
//硬编码注册物品类型 //硬编码注册物品类型
ItemTypeManager.StaticRegister(); ItemTypeRegister.StaticRegister();
//静态注册掉落表
LootRegister.StaticRegister();
} }
public override void InitializeUi() public override void InitializeUi()

View File

@ -0,0 +1,8 @@
using Godot;
namespace ColdMint.scripts.loot;
public readonly record struct LootDatum(string ItemId, int Quantity)
{
public (string id, int quantity) Value => (ItemId, Quantity);
}

67
scripts/loot/LootEntry.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.utils;
namespace ColdMint.scripts.loot;
/// <summary>
/// <para>Loot entry</para>
/// <para>战利品条目</para>
/// </summary>
public readonly struct LootEntry(string itemId,int minQuantity=1,int maxQuantity = 1,int weight = 1)
{
/// <summary>
/// <para>ID of item</para>
/// <para>物品ID</para>
/// </summary>
public string ItemId { get; init; } = itemId;
/// <summary>
/// <para>Minimum number of generated</para>
/// <para>最小生成多少个</para>
/// </summary>
public int MinQuantity { get; init; } = minQuantity;
/// <summary>
/// <para>The maximum number of files to be generated</para>
/// <para>最多生成多少个</para>
/// </summary>
public int MaxQuantity { get; init; } = maxQuantity;
/// <summary>
/// <para>Weight of probability within the drop group</para>
/// <para>在掉落组内的生成权重</para>
/// </summary>
public int Weight { get; init; } = weight;
}
/// <summary>
/// <para>Loot Group</para>
/// <para>战利品分组</para>
/// </summary>
/// <param name="Chance"></param>
/// <param name="Entries"></param>
public readonly record struct LootGroup(double Chance, IEnumerable<LootEntry> Entries)
{
private int WeightSum { get; } = Entries.Sum(entry => entry.Weight);
public LootDatum GenerateLootData()
{
var random = RandomUtils.Instance;
var w = random.Next(WeightSum);
LootEntry entry = default;
foreach (var e in Entries)
{
w -= e.Weight;
if (w >= 0) continue;
entry = e;
break;
}
var quantity = random.Next(entry.MinQuantity, entry.MaxQuantity + 1);
return new(entry.ItemId, quantity);
}
}

53
scripts/loot/LootList.cs Normal file
View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.loot;
/// <summary>
/// <para>Loot list</para>
/// <para>战利品表</para>
/// </summary>
public readonly struct LootList(string id, IList<LootGroup> groups)
{
public string Id { get; } = id;
private IList<LootGroup> Groups { get; } = groups;
private static Random Random => RandomUtils.Instance;
/// <summary>
/// <para>GenerateLootData</para>
/// <para>生成战利品数据</para>
/// </summary>
/// <returns></returns>
public LootDatum[] GenerateLootData()
{
if (Groups is [])
{
LogCat.LogWithFormat("loot_list_has_no_entries", Id);
return [];
}
var lootDataList = new List<LootDatum>();
foreach (var group in Groups)
{
//If the random number is greater than the generation probability, skip the current loop.
//如果随机数大于生成概率,则跳过当前循环。
var rd = Random.NextDouble();
if (rd > group.Chance) continue;
//We generate a loot data for each loot entry.
//我们为每个战利品条目生成一个战利品数据。
var datum = group.GenerateLootData();
lootDataList.Add(datum);
LogCat.LogWithFormat("loot_data_add", datum);
}
LogCat.LogWithFormat("loot_data_quantity", lootDataList.Count);
return lootDataList.ToArray();
}
}

View File

@ -0,0 +1,51 @@
using System.Collections.Generic;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.loot;
/// <summary>
/// <para>LootListManager</para>
/// <para>战利品表管理器</para>
/// </summary>
public static class LootListManager
{
private static Dictionary<string, LootList> LootListDictionary { get; } = [];
/// <summary>
/// <para>Register loot table</para>
/// <para>注册战利品表</para>
/// </summary>
/// <param name="lootList"></param>
public static bool RegisterLootList(LootList lootList)
{
var id = lootList.Id;
if (id is "") return false;
return LootListDictionary.TryAdd(id, lootList);
}
/// <summary>
/// <para>Remove loot list</para>
/// <para>移除战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool UnregisterLootList(string id)
{
return LootListDictionary.Remove(id);
}
/// <summary>
/// <para>Generate an loot data.</para>
/// <para>获取掉落物品</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static IEnumerable<LootDatum> GenerateLootData(string id)
{
if (!LootListDictionary.TryGetValue(id, out var list)) return [];
return list.GenerateLootData();
}
}

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace ColdMint.scripts.loot;
public static class LootRegister
{
/// <summary>
/// <para>Register loots hardcoded here</para>
/// <para>在这里硬编码地注册掉落表</para>
/// </summary>
public static void StaticRegister()
{
//注册测试使用的战利品表
if (Config.IsDebug())
{
IList<LootGroup> lootGroups = [];
lootGroups.Add(new LootGroup(0.8,
[
new LootEntry("degraded_staff_of_the_undead", weight: 2), new LootEntry("staff_of_the_undead")
]));
lootGroups.Add(new LootGroup(1,
[
new LootEntry("packsack", minQuantity: 2, maxQuantity: 4)
]));
var testLootList = new LootList(Config.LootListId.Test, lootGroups);
LootListManager.RegisterLootList(testLootList);
}
}
}

View File

@ -1,8 +1,10 @@
using System; using System;
using ColdMint.scripts.camp; using ColdMint.scripts.camp;
using ColdMint.scripts.character; using ColdMint.scripts.character;
using ColdMint.scripts.damage; using ColdMint.scripts.damage;
using ColdMint.scripts.item; using ColdMint.scripts.item;
using Godot; using Godot;
namespace ColdMint.scripts.pickable; namespace ColdMint.scripts.pickable;
@ -14,11 +16,12 @@ namespace ColdMint.scripts.pickable;
public partial class PickAbleTemplate : RigidBody2D, IItem public partial class PickAbleTemplate : RigidBody2D, IItem
{ {
[Export] public virtual string Id { get; set; } = "ID"; [Export] public virtual string Id { get; set; } = "ID";
protected Texture2D? UniqueIcon { get; set; } [Export] protected Texture2D? UniqueIcon { get; set; }
public Texture2D Icon => UniqueIcon ?? ItemTypeManager.DefaultIconOf(Id); public Texture2D Icon => UniqueIcon ?? ItemTypeManager.DefaultIconOf(Id);
protected string? UniqueName { get; set; } [Export] protected string? UniqueName { get; set; }
public new string Name => UniqueName ?? ItemTypeManager.DefaultNameOf(Id); public new string Name => UniqueName ?? ItemTypeManager.DefaultNameOf(Id);
protected string? UniqueDescription { get; set; } [Export] protected string? UniqueDescription { get; set; }
public virtual bool CanPutInPack => true;
/// <summary> /// <summary>
/// <para>Owner</para> /// <para>Owner</para>
@ -55,9 +58,7 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
/// </summary> /// </summary>
public bool Picked { get; set; } public bool Picked { get; set; }
public virtual void Use(Node2D? owner, Vector2 targetGlobalPosition) public virtual void Use(Node2D? owner, Vector2 targetGlobalPosition) { }
{
}
public override void _Ready() public override void _Ready()
{ {

View File

@ -0,0 +1,8 @@
using Godot;
namespace ColdMint.scripts.utils;
public static class GodotStringNameUtils
{
public static StringName AddChild { get; } = new("add_child");
}

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.inventory; using ColdMint.scripts.inventory;
using ColdMint.scripts.item; using ColdMint.scripts.item;
using ColdMint.scripts.item.weapon; using ColdMint.scripts.item.weapon;
using Godot; using Godot;
using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi; using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi;
@ -160,14 +162,13 @@ public static class NodeUtils
/// <para>默认父节点如果传入null则不会将实例化后的节点放入父节点内。您需要手动放置这对于延迟设置父节点相当有用。</para> /// <para>默认父节点如果传入null则不会将实例化后的节点放入父节点内。您需要手动放置这对于延迟设置父节点相当有用。</para>
/// </param> /// </param>
/// <returns></returns> /// <returns></returns>
public static T? InstantiatePackedScene<T>(PackedScene packedScene, Node? defaultParentNode = null, /// <seealso cref="InstantiatePackedScene{T}"/>
bool assignedByRootNodeType = true) where T : Node public static Node InstantiatePackedScene(PackedScene packedScene, Node? defaultParentNode = null,
bool assignedByRootNodeType = true)
{ {
try var instantiateNode = packedScene.Instantiate();
{ //An attempt is made to place an instantiated node under a parent node only after the default parent node is set.
var instantiateNode = packedScene.Instantiate<T>(); //只有设定了默认父节点后才会尝试将实例化的节点放置到父节点下。
//An attempt is made to place an instantiated node under a husband node only after the default parent node is set.
//只有设定了默认父节点后才会尝试将实例化的节点放置到夫节点下。
if (defaultParentNode != null) if (defaultParentNode != null)
{ {
if (assignedByRootNodeType) if (assignedByRootNodeType)
@ -185,12 +186,44 @@ public static class NodeUtils
return instantiateNode; return instantiateNode;
} }
catch (InvalidCastException e)
/// <summary>
/// <para>
/// Generic version of <see cref="InstantiatePackedScene"/> that transforms the created node to <see cref="T"/> type,
/// if the created node can't be transformed to the specified type, return null and release it.
/// </para>
/// <para><see cref="InstantiatePackedScene"/>的泛型版本,转换创建的节点至<see cref="T"/>类型如果创建的节点无法转型至指定的类型返回null并释放它</para>
/// </summary>
/// <remarks>
///<para>This method is recommended in place of all packedScene.Instantiate&lt;T&gt;() calls within a project, using it to instantiate a scene, optionally assigned to a container that matches the type of the root node.</para>
///<para>推荐使用此方法代替项目内所有的packedScene.Instantiate&lt;T&gt;()调用,使用此方法实例化场景,可选择将其分配到与根节点类型相匹配的容器内。</para>
/// </remarks>
/// <param name="packedScene">
///<para>packedScene</para>
///<para>打包的场景</para>
/// </param>
/// <param name="assignedByRootNodeType">
///<para>Enabled by default, whether to place a node into a container node that matches the type of the root node after it is instantiated. If the assignment fails by type, it is placed under the default parent node.</para>
///<para>默认启用,实例化节点后,是否将其放置到与根节点类型相匹配的容器节点内。如果按照类型分配失败,则放置在默认父节点下。</para>
/// </param>
/// <param name="defaultParentNode">
/// <para>Default parent. If null is passed, the instantiated node is not put into the parent. You need to place it manually, which is quite useful for delaying setting the parent node.</para>
/// <para>默认父节点如果传入null则不会将实例化后的节点放入父节点内。您需要手动放置这对于延迟设置父节点相当有用。</para>
/// </param>
/// <returns>
/// <para>If new node cannot cast to given type, return null</para>
/// <para>如果创建的节点无法转型至指定的类型返回null</para>
/// </returns>
public static T? InstantiatePackedScene<T>(PackedScene packedScene, Node? defaultParentNode = null,
bool assignedByRootNodeType = true)
where T : class
{ {
//null is returned if the type conversion fails. var node = InstantiatePackedScene(packedScene, defaultParentNode, assignedByRootNodeType);
//在类型转换失败时返回null。 // 检查类型转化,成功返回结果
LogCat.WhenCaughtException(e); if (node is T result) return result;
//如果转型失败,释放所创建的节点
LogCat.LogWarningWithFormat("warning_node_cannot_cast_to",node,nameof(T));
node.QueueFree();
return null; return null;
} }
} }
}

View File

@ -0,0 +1,8 @@
using System;
namespace ColdMint.scripts.utils;
public static class RandomUtils
{
public static Random Instance { get; } = new();
}