Preliminary rewrite of the loot system

初步重写掉落系统
This commit is contained in:
霧雨烨 2024-06-16 16:56:45 +08:00
parent edaa490b5b
commit abf03c00a1
18 changed files with 312 additions and 326 deletions

View File

@ -1,4 +1,8 @@
- id: staff_of_the_undead - id: staff_of_the_undead
scene_path: res://prefab/weapons/staffOfTheUndead.tscn scene_path: res://prefab/weapons/staffOfTheUndead.tscn
icon_path: res://sprites/weapon/staffOfTheUndead.png icon_path: res://sprites/weapon/staffOfTheUndead.png
max_stack_value: 1 max_stack_value: 1
custom_args:
- name: FiringIntervalAsMillisecond
type: int
value: 1000

View File

@ -23,9 +23,11 @@ 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_warning_node_cannot_cast_to,创建的物品{0}无法被转型为类型{1},Created items {0} cannot be cast into type {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_warning_node_cannot_cast_to log_start_item_register_from_file 创建的物品{0}无法被转型为类型{1} 开始从文件注册物品信息 Created items {0} cannot be cast into type {1} Start registering item information from files 作成されたアイテム {0} をタイプ {1} にキャストすることはできません。 アイテム情報をファイルから登録開始
32 log_start_item_register_from_file log_found_files 开始从文件注册物品信息 找到{0}个文件 Start registering item information from files Found {0} files アイテム情報をファイルから登録開始 {0}ファイルが見つかりました
33 log_found_files log_found_item_types 找到{0}个文件 从文件中找到{0}个物品类型 Found {0} files Found {0} item types in files {0}ファイルが見つかりました ファイルから{0}個のアイテム・タイプが見つかった

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

@ -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,6 +10,7 @@ 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 Godot; using Godot;
@ -107,11 +108,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 +205,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 +452,11 @@ 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); //Todo : change name str to nameof(), like this
// CallDeferred(nameof(GenerateLootObjects), this, lootData, finalGlobalPosition);
GenerateLootObjects(GetParent(), lootData, finalGlobalPosition);
} }
/// <summary> /// <summary>
@ -482,13 +464,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 +587,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));
}
} }
} }

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

@ -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,12 +12,6 @@ namespace ColdMint.scripts.item;
/// </summary> /// </summary>
public static class ItemTypeManager public static class ItemTypeManager
{ {
/// <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();
@ -45,9 +35,66 @@ 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 void CreateItem(string id, Node? parent = null, Vector2? position = null)
{
var item = NewItem(id);
parent?.AddChild(item as Node);
if (item is not Node2D node) return;
if (position is { } pos) node.GlobalPosition = pos;
}
/// <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 void CreateItems(string id, int amount, Node? parent = null, Vector2? position = null)
{
for (int i = 0; i < amount; i++)
{
CreateItem(id, parent, position);
}
}
/// <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

@ -17,6 +17,12 @@ namespace ColdMint.scripts.item;
/// </summary> /// </summary>
public static class ItemTypeRegister public static class ItemTypeRegister
{ {
/// <summary>
/// <para>Register items here</para>
/// <para>在这里注册物品</para>
/// </summary>
public static void StaticRegister() { }
/// <summary> /// <summary>
/// <para>Register items from yaml file</para> /// <para>Register items from yaml file</para>
/// <para>从文件注册物品</para> /// <para>从文件注册物品</para>

View File

@ -7,6 +7,7 @@ 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;
@ -43,31 +44,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);
}
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler()); DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor()); MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
@ -82,6 +58,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)
@ -94,13 +71,16 @@ public partial class MainMenuLoader : UiLoaderTemplate
var aborigines = new Camp(Config.CampId.Aborigines); var aborigines = new Camp(Config.CampId.Aborigines);
CampManager.AddCamp(aborigines); CampManager.AddCamp(aborigines);
_gameScene = (PackedScene)GD.Load("res://scenes/game.tscn"); _gameScene = (PackedScene)GD.Load("res://scenes/game.tscn");
//Register ItemTypes from file //Register ItemTypes from file
//从文件注册物品类型 //从文件注册物品类型
ItemTypeRegister.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);
}

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

@ -0,0 +1,68 @@
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)
{
entry = e;
}
}
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(1,
[
new LootEntry("packsack", weight: 2), new LootEntry("staff_of_the_undead", minQuantity: 2, maxQuantity: 4)
]));
lootGroups.Add(new LootGroup(0.3,
[
new LootEntry("packsack")
]));
var testLootList = new LootList(Config.LootListId.Test, lootGroups);
LootListManager.RegisterLootList(testLootList);
}
}
}

View File

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