commit
7f55bd7d53
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,4 +5,3 @@ export_presets.cfg
|
|||
.vs/
|
||||
*.translation
|
||||
*.user
|
||||
*.DotSettings
|
||||
|
|
2
ColdMint.Traveler.sln.DotSettings
Normal file
2
ColdMint.Traveler.sln.DotSettings
Normal 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>
|
|
@ -2,3 +2,15 @@
|
|||
scene_path: res://prefab/weapons/staffOfTheUndead.tscn
|
||||
icon_path: res://sprites/weapon/staffOfTheUndead_icon.png
|
||||
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: 劣化的死灵法杖
|
||||
|
|
|
@ -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_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_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_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_item_register_from_file,从文件{0}中注册物品信息,Registering item information from file {0},ファイル{0}からアイテム情報を登録する
|
||||
log_item_register_find_item_in_file,注册发现的物品{0},Register discovered item {0},見つかったアイテム{0}を登録
|
||||
log_found_files,找到{0}个文件,Found {0} files,{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_wrong_custom_arg,不匹配的参数:类型为{0}而值为{1},Mismatched parameter: type {0} and value {1},パラメータの不一致:型{0}と値{1}。
|
|
|
@ -31,10 +31,10 @@ radius = 129.027
|
|||
collision_layer = 64
|
||||
collision_mask = 38
|
||||
script = ExtResource("1_ubaid")
|
||||
LootListId = "test"
|
||||
metadata/CampId = "Mazoku"
|
||||
metadata/MaxHp = 50
|
||||
metadata/Name = "死灵法师"
|
||||
metadata/LootListId = "Test"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, 4)
|
||||
|
|
|
@ -15,12 +15,8 @@ collision_layer = 8
|
|||
collision_mask = 34
|
||||
script = ExtResource("1_w8hhv")
|
||||
ProjectileScenes = [ExtResource("2_34250")]
|
||||
_firingIntervalAsMillisecond = null
|
||||
_recoil = null
|
||||
FiringIntervalAsMillisecond = 300
|
||||
Id = "staff_of_the_undead"
|
||||
_minContactInjury = null
|
||||
_maxContactInjury = null
|
||||
metadata/Projectiles = PackedStringArray("res://prefab/projectile/curseOfTheUndead.tscn")
|
||||
|
||||
[node name="DamageArea2D" type="Area2D" parent="."]
|
||||
collision_layer = 8
|
||||
|
|
|
@ -53,7 +53,7 @@ offset_left = 10.0
|
|||
offset_top = 13.0
|
||||
offset_right = 97.0
|
||||
offset_bottom = 38.0
|
||||
text = "create_room"
|
||||
text = "ui_create_room"
|
||||
|
||||
[node name="Label3" type="Label" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -61,7 +61,7 @@ offset_left = 12.0
|
|||
offset_top = 106.0
|
||||
offset_right = 72.0
|
||||
offset_bottom = 131.0
|
||||
text = "describe"
|
||||
text = "ui_describe"
|
||||
|
||||
[node name="Label4" type="Label" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -69,7 +69,7 @@ offset_left = 15.0
|
|||
offset_top = 152.0
|
||||
offset_right = 75.0
|
||||
offset_bottom = 177.0
|
||||
text = "room_template_collection_prompt"
|
||||
text = "ui_room_template_collection_prompt"
|
||||
|
||||
[node name="RoomTemplateTipsLabel" type="Label" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -109,7 +109,7 @@ offset_top = 6.0
|
|||
offset_right = -11.0
|
||||
offset_bottom = 39.0
|
||||
grow_horizontal = 0
|
||||
text = "close"
|
||||
text = "ui_close"
|
||||
|
||||
[node name="Label2" type="Label" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -117,7 +117,7 @@ offset_left = 13.0
|
|||
offset_top = 60.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 85.0
|
||||
text = "name"
|
||||
text = "ui_name"
|
||||
|
||||
[node name="CreateRoomButton" type="Button" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -132,7 +132,7 @@ offset_right = -13.0
|
|||
offset_bottom = -13.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
text = "creation"
|
||||
text = "ui_creation"
|
||||
|
||||
[node name="RoomTemplateCollectionTextEdit" type="TextEdit" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -150,7 +150,7 @@ offset_left = 16.0
|
|||
offset_top = 289.0
|
||||
offset_right = 56.0
|
||||
offset_bottom = 314.0
|
||||
text = "tags"
|
||||
text = "ui_tags"
|
||||
|
||||
[node name="TagLineEdit" type="LineEdit" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
@ -168,7 +168,7 @@ offset_left = 14.0
|
|||
offset_top = 368.0
|
||||
offset_right = 283.0
|
||||
offset_bottom = 393.0
|
||||
text = "room_injection_processor"
|
||||
text = "ui_room_injection_processor"
|
||||
|
||||
[node name="RoomInjectionProcessorDataTextEdit" type="TextEdit" parent="CreateOrEditorPanel"]
|
||||
layout_mode = 1
|
||||
|
|
|
@ -31,7 +31,7 @@ public static class Config
|
|||
/// <para>A trophy table for testing</para>
|
||||
/// <para>测试用的战利品表</para>
|
||||
/// </summary>
|
||||
public const string Test = "Test";
|
||||
public const string Test = "test";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -10,7 +10,9 @@ using ColdMint.scripts.inventory;
|
|||
using ColdMint.scripts.item;
|
||||
using ColdMint.scripts.utils;
|
||||
using ColdMint.scripts.item.weapon;
|
||||
using ColdMint.scripts.loot;
|
||||
using ColdMint.scripts.pickable;
|
||||
|
||||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.character;
|
||||
|
@ -107,11 +109,7 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
|
||||
private DamageNumberNodeSpawn? _damageNumber;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Character referenced loot table</para>
|
||||
/// <para>角色引用的战利品表</para>
|
||||
/// </summary>
|
||||
private LootList? _lootList;
|
||||
[Export] public string LootListId { get; private set; } = "";
|
||||
|
||||
private HealthBar? _healthBar;
|
||||
private DateTime _lastDamageTime;
|
||||
|
@ -208,12 +206,6 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
|
||||
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
|
||||
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)
|
||||
{
|
||||
|
@ -461,20 +453,9 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
/// </summary>
|
||||
protected void CreateLootObject()
|
||||
{
|
||||
if (_lootList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lootDataArray = _lootList.GenerateLootData();
|
||||
if (lootDataArray.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var lootData = LootListManager.GenerateLootData(LootListId);
|
||||
var finalGlobalPosition = GlobalPosition;
|
||||
CallDeferred("GenerateLootObjects", this, lootDataArray, finalGlobalPosition);
|
||||
GenerateLootObjects(GetParent(), lootData, finalGlobalPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -482,13 +463,17 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
/// <para>生成战利品对象</para>
|
||||
/// </summary>
|
||||
/// <param name="parentNode"></param>
|
||||
/// <param name="lootDataArray"></param>
|
||||
/// <param name="lootData"></param>
|
||||
/// <param name="position"></param>
|
||||
public void GenerateLootObjects(Node parentNode,
|
||||
LootData[] lootDataArray,
|
||||
IEnumerable<LootDatum> lootData,
|
||||
Vector2 position)
|
||||
{
|
||||
LootListManager.GenerateLootObjects(parentNode, lootDataArray, position);
|
||||
foreach (var lootDatum in lootData)
|
||||
{
|
||||
var (id, amount) = lootDatum.Value;
|
||||
ItemTypeManager.CreateItems(id, amount, parentNode, position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
//生成一个随机数,用于控制抛出物品的水平方向速度(范围为:0.01到1)
|
||||
var percent = GD.Randf() + 0.01f;
|
||||
if (GD.Randf() > 0.5)
|
||||
{
|
||||
ThrowItem(i, -1, new Vector2(horizontalDirection * percent, height));
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowItem(i, -1, new Vector2(-horizontalDirection * percent, height));
|
||||
}
|
||||
var horizontalVelocity = horizontalDirection * percent * GD.Randf() > 0.5 ? 1f : -1f;
|
||||
ThrowItem(i, -1, new Vector2(horizontalVelocity, height));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -671,7 +650,7 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
return;
|
||||
}
|
||||
|
||||
CallDeferred("NodeReparent", node2D);
|
||||
CallDeferred(nameof(NodeReparent), node2D);
|
||||
switch (item)
|
||||
{
|
||||
case PickAbleTemplate pickAbleTemplate:
|
||||
|
|
|
@ -106,7 +106,7 @@ public partial class DamageNumberNodeSpawn : Marker2D
|
|||
return;
|
||||
}
|
||||
|
||||
CallDeferred("AddDamageNumberNode", damageNumber);
|
||||
CallDeferred(nameof(AddDamageNumberNode), damageNumber);
|
||||
damageNumber.Position = GlobalPosition;
|
||||
if (damageTemplate.MoveLeft)
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -67,16 +67,16 @@ public class UniversalItemContainer : IItemContainer
|
|||
|
||||
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))
|
||||
return true;
|
||||
|
||||
itemSlotNode = Match(itemStack);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetSelectIndex()
|
||||
|
|
|
@ -16,9 +16,9 @@ namespace ColdMint.scripts.item;
|
|||
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>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>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ICommonItem CopyInstance();
|
||||
ICommonItem CloneInstance();
|
||||
}
|
|
@ -26,6 +26,14 @@ public interface IItem
|
|||
/// <para>当前项目的描述</para>
|
||||
/// </summary>
|
||||
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>
|
||||
/// <para>Execute when current item is used <br/> e.g. when player clicks left mouse button with current item in hand</para>
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using ColdMint.scripts.debug;
|
||||
using ColdMint.scripts.utils;
|
||||
|
||||
using Godot;
|
||||
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace ColdMint.scripts.item;
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,68 +12,6 @@ namespace ColdMint.scripts.item;
|
|||
/// </summary>
|
||||
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 Texture2D DefaultTexture { get; } = new PlaceholderTexture2D();
|
||||
|
||||
|
@ -101,9 +35,71 @@ public static class ItemTypeManager
|
|||
/// <para>Returns null when the id is not registered.</para>
|
||||
/// <para>当物品id没有注册时返回null</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="NewItems"/><seealso cref="CreateItem"/>
|
||||
public static IItem? NewItem(string id) =>
|
||||
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>
|
||||
/// <para>Get the translated default name of the item type for the given id</para>
|
||||
/// <para>获取指定物品id翻译后的物品名</para>
|
||||
|
|
150
scripts/item/ItemTypeRegister.cs
Normal file
150
scripts/item/ItemTypeRegister.cs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using ColdMint.scripts.inventory;
|
||||
using ColdMint.scripts.pickable;
|
||||
using ColdMint.scripts.utils;
|
||||
|
||||
using Godot;
|
||||
using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi;
|
||||
|
||||
|
@ -15,6 +16,8 @@ public partial class Packsack : PickAbleTemplate
|
|||
private PackedScene? _packedScene;
|
||||
private PacksackUi? _packsackUi;
|
||||
|
||||
public override bool CanPutInPack => false;
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
if (ItemContainer == null) return;
|
||||
|
@ -32,6 +35,7 @@ public partial class Packsack : PickAbleTemplate
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_packsackUi == null)
|
||||
{
|
||||
_packsackUi = NodeUtils.InstantiatePackedScene<PacksackUi>(_packedScene,this);
|
||||
|
@ -41,6 +45,7 @@ public partial class Packsack : PickAbleTemplate
|
|||
_packsackUi.ItemContainer = ItemContainer;
|
||||
}
|
||||
}
|
||||
|
||||
_packsackUi?.Show();
|
||||
}
|
||||
|
||||
|
@ -51,6 +56,5 @@ public partial class Packsack : PickAbleTemplate
|
|||
base._Ready();
|
||||
ItemContainer = new UniversalItemContainer();
|
||||
_packedScene = GD.Load<PackedScene>("res://prefab/ui/packsackUI.tscn");
|
||||
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ public class CommonItemStack(ICommonItem innerItem) : IItemStack
|
|||
{
|
||||
if(Empty) return null;
|
||||
Quantity--;
|
||||
var result = innerItem.CopyInstance();
|
||||
var result = innerItem.CloneInstance();
|
||||
if(Empty) innerItem.Destroy();
|
||||
return result;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class CommonItemStack(ICommonItem innerItem) : IItemStack
|
|||
public IItemStack? PickItems(int value)
|
||||
{
|
||||
if (Empty) return null;
|
||||
var result = new CommonItemStack(innerItem.CopyInstance());
|
||||
var result = new CommonItemStack(innerItem.CloneInstance());
|
||||
var n = Math.Min(Quantity, value);
|
||||
if (n < 0)
|
||||
{
|
||||
|
|
|
@ -18,16 +18,15 @@ public class PacksackStack(Packsack packsack) : IItemStack
|
|||
public string Name => packsack.Name;
|
||||
public string? Description => packsack.Description;
|
||||
|
||||
//todo: 只拒绝是背包的物品是权宜之计,应该为物品加入一个“是否可以放入背包”的属性来实现这个判断。
|
||||
public bool CanAddItem(IItem item)
|
||||
{
|
||||
if (item is Packsack) return false;
|
||||
if (!item.CanPutInPack) return false;
|
||||
return packsack.ItemContainer?.CanAddItem(item) ?? false;
|
||||
}
|
||||
|
||||
public bool AddItem(IItem item)
|
||||
{
|
||||
if (item is Packsack) return false;
|
||||
if (!item.CanPutInPack) return false;
|
||||
return packsack.ItemContainer?.AddItem(item) ?? false;
|
||||
}
|
||||
|
||||
|
@ -45,7 +44,7 @@ public class PacksackStack(Packsack packsack) : IItemStack
|
|||
|
||||
public IItem? GetItem()
|
||||
{
|
||||
return Empty ? packsack : null;
|
||||
return Empty ? null : packsack;
|
||||
}
|
||||
|
||||
public IItem? PickItem()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
|
||||
using ColdMint.scripts.character;
|
||||
using ColdMint.scripts.pickable;
|
||||
using ColdMint.scripts.damage;
|
||||
|
||||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.item.weapon;
|
||||
|
@ -12,6 +15,7 @@ namespace ColdMint.scripts.item.weapon;
|
|||
public abstract partial class WeaponTemplate : PickAbleTemplate
|
||||
{
|
||||
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
|
||||
|
||||
public override void Use(Node2D? owner, Vector2 targetGlobalPosition)
|
||||
{
|
||||
Fire(owner, targetGlobalPosition);
|
||||
|
@ -25,7 +29,16 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
|
|||
/// <para>开火间隔</para>
|
||||
/// </summary>
|
||||
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>
|
||||
|
@ -38,16 +51,7 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
|
|||
/// </remarks>
|
||||
[Export] private Vector2 _recoil;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_firingInterval = TimeSpan.FromMilliseconds(_firingIntervalAsMillisecond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public override void _Ready() { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Discharge of the weapon</para>
|
||||
|
|
|
@ -8,9 +8,11 @@ using ColdMint.scripts.deathInfo;
|
|||
using ColdMint.scripts.debug;
|
||||
using ColdMint.scripts.inventory;
|
||||
using ColdMint.scripts.item;
|
||||
using ColdMint.scripts.loot;
|
||||
using ColdMint.scripts.map;
|
||||
using ColdMint.scripts.map.roomInjectionProcessor;
|
||||
using ColdMint.scripts.utils;
|
||||
|
||||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.loader.uiLoader;
|
||||
|
@ -47,31 +49,6 @@ public partial class MainMenuLoader : UiLoaderTemplate
|
|||
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();
|
||||
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
|
||||
|
@ -87,6 +64,7 @@ public partial class MainMenuLoader : UiLoaderTemplate
|
|||
Directory.CreateDirectory(dataPath);
|
||||
}
|
||||
|
||||
|
||||
//Registered camp
|
||||
//注册阵营
|
||||
var defaultCamp = new Camp(Config.CampId.Default)
|
||||
|
@ -104,10 +82,13 @@ public partial class MainMenuLoader : UiLoaderTemplate
|
|||
|
||||
//Register ItemTypes from file
|
||||
//从文件注册物品类型
|
||||
ItemTypeManager.RegisterFromFile();
|
||||
ItemTypeRegister.RegisterFromFile();
|
||||
//Hardcoded ItemTypes Register
|
||||
//硬编码注册物品类型
|
||||
ItemTypeManager.StaticRegister();
|
||||
ItemTypeRegister.StaticRegister();
|
||||
|
||||
//静态注册掉落表
|
||||
LootRegister.StaticRegister();
|
||||
}
|
||||
|
||||
public override void InitializeUi()
|
||||
|
|
8
scripts/loot/LootDatum.cs
Normal file
8
scripts/loot/LootDatum.cs
Normal 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
67
scripts/loot/LootEntry.cs
Normal 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
53
scripts/loot/LootList.cs
Normal 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();
|
||||
}
|
||||
}
|
51
scripts/loot/LootListManager.cs
Normal file
51
scripts/loot/LootListManager.cs
Normal 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();
|
||||
}
|
||||
}
|
30
scripts/loot/LootRegister.cs
Normal file
30
scripts/loot/LootRegister.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
|
||||
using ColdMint.scripts.camp;
|
||||
using ColdMint.scripts.character;
|
||||
using ColdMint.scripts.damage;
|
||||
using ColdMint.scripts.item;
|
||||
|
||||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.pickable;
|
||||
|
@ -14,11 +16,12 @@ namespace ColdMint.scripts.pickable;
|
|||
public partial class PickAbleTemplate : RigidBody2D, IItem
|
||||
{
|
||||
[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);
|
||||
protected string? UniqueName { get; set; }
|
||||
[Export] protected string? UniqueName { get; set; }
|
||||
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>
|
||||
/// <para>Owner</para>
|
||||
|
@ -55,9 +58,7 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
|
|||
/// </summary>
|
||||
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()
|
||||
{
|
||||
|
|
8
scripts/utils/GodotStringNameUtils.cs
Normal file
8
scripts/utils/GodotStringNameUtils.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.utils;
|
||||
|
||||
public static class GodotStringNameUtils
|
||||
{
|
||||
public static StringName AddChild { get; } = new("add_child");
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ColdMint.scripts.debug;
|
||||
using ColdMint.scripts.inventory;
|
||||
using ColdMint.scripts.item;
|
||||
using ColdMint.scripts.item.weapon;
|
||||
|
||||
using Godot;
|
||||
using PacksackUi = ColdMint.scripts.loader.uiLoader.PacksackUi;
|
||||
|
||||
|
@ -160,14 +162,13 @@ public static class NodeUtils
|
|||
/// <para>默认父节点,如果传入null,则不会将实例化后的节点放入父节点内。您需要手动放置,这对于延迟设置父节点相当有用。</para>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static T? InstantiatePackedScene<T>(PackedScene packedScene, Node? defaultParentNode = null,
|
||||
bool assignedByRootNodeType = true) where T : Node
|
||||
/// <seealso cref="InstantiatePackedScene{T}"/>
|
||||
public static Node InstantiatePackedScene(PackedScene packedScene, Node? defaultParentNode = null,
|
||||
bool assignedByRootNodeType = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
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.
|
||||
//只有设定了默认父节点后才会尝试将实例化的节点放置到夫节点下。
|
||||
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.
|
||||
//只有设定了默认父节点后才会尝试将实例化的节点放置到父节点下。
|
||||
if (defaultParentNode != null)
|
||||
{
|
||||
if (assignedByRootNodeType)
|
||||
|
@ -185,12 +186,44 @@ public static class NodeUtils
|
|||
|
||||
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<T>() 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<T>()调用,使用此方法实例化场景,可选择将其分配到与根节点类型相匹配的容器内。</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.
|
||||
//在类型转换失败时,返回null。
|
||||
LogCat.WhenCaughtException(e);
|
||||
var node = InstantiatePackedScene(packedScene, defaultParentNode, assignedByRootNodeType);
|
||||
// 检查类型转化,成功返回结果
|
||||
if (node is T result) return result;
|
||||
//如果转型失败,释放所创建的节点
|
||||
LogCat.LogWarningWithFormat("warning_node_cannot_cast_to",node,nameof(T));
|
||||
node.QueueFree();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
8
scripts/utils/RandomUtils.cs
Normal file
8
scripts/utils/RandomUtils.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace ColdMint.scripts.utils;
|
||||
|
||||
public static class RandomUtils
|
||||
{
|
||||
public static Random Instance { get; } = new();
|
||||
}
|
Loading…
Reference in New Issue
Block a user