Merge branch 'refs/heads/master' into item_refactory_combined

# Conflicts:
#	locals/Slogan.ja.translation
#	scripts/character/CharacterTemplate.cs
#	scripts/character/Player.cs
#	scripts/inventory/HotBar.cs
#	scripts/inventory/IItemContainer.cs
#	scripts/item/weapon/ProjectileWeapon.cs
#	scripts/item/weapon/WeaponTemplate.cs
This commit is contained in:
霧雨烨 2024-06-12 22:13:55 +08:00
commit 76daa88bac
34 changed files with 795 additions and 519 deletions

View File

@ -1,6 +1,6 @@
[![Star History Chart](https://api.star-history.com/svg?repos=Cold-Mint/Traveller&type=Date)](https://star-history.com/#Cold-Mint/Traveller&Date)
English [简体中文](README_ZH.md) [にほんご](README_JP.md)
English [简体中文](README_ZH.md) [にほんご](README_JA.md)
## Intro
@ -13,8 +13,8 @@ A pixel cross-platform roguelite game.
| Task | status |
| ----------------------------------------------------------- | ------------------ |
| Randomly generated map | complete |
| loot | In progress |
| Support still out of the knapsack system | await |
| loot | complete |
| Support still out of the knapsack system | In progress |
| Add AI agents to creatures | await |
## Screenshot

View File

@ -13,8 +13,8 @@
| ミッション | じょうたい |
| ----------------------------------------------------------- | ------------------ |
| マップをランダムに生成します | 成し遂げる |
| 戦利品 | 進行中です |
| バックパックのシステムをサポートしています | すたんばい |
| 戦利品 | 成し遂げる |
| バックパックのシステムをサポートしています | 進行中です |
| 生物にAIエージェントを追加します | すたんばい |
## スクリーンショットです
@ -50,7 +50,7 @@ git clone https://github.com/Cold-Mint/Traveller.git
[GPL-3.0 license](LICENSE)
プロトコルの日本語訳を見ます:[GPL-3.0 license にほんご](LICENSE_JP)
プロトコルの日本語訳を見ます:[GPL-3.0 license にほんご](LICENSE_JA)
商用に対応しており、誰でも修正、構築、販売、無料配布が可能です。このプロジェクトのすべての派生バージョンについて、GPLプロトコルに基づいて、あなたは**作者の著作権**を保持し、**ソースコードの修正を公開します**。

View File

@ -1,6 +1,6 @@
[![Star History Chart](https://api.star-history.com/svg?repos=Cold-Mint/Traveller&type=Date)](https://star-history.com/#Cold-Mint/Traveller&Date)
[English](README.md) 简体中文 [にほんご](README_JP.md)
[English](README.md) 简体中文 [にほんご](README_JA.md)
## 简介
@ -10,11 +10,11 @@
## 近期研发进度
| 任务 | 状态 |
| ----------------------------------------------------------- | ------------------ |
| 随机生成地图 | 完成 |
| 战利品 | 进行中 |
| 支持仍出的背包系统 | 等待 |
| 任务 | 状态 |
| ----------------------------------------------------------- |----|
| 随机生成地图 | 完成 |
| 战利品 | 完成 |
| 支持仍出的背包系统 | 进行中 |
| 为生物添加AI代理 | 等待 |
## 屏幕截图

View File

@ -22,4 +22,7 @@ player_spawn_debug,玩家{0}生成在{1}。,"Player {0} spawned at {1}.",プレ
player_packed_scene_not_exist,玩家预制场景不存在。,Player packed scene does not exist.,プレイヤーのパックされたシーンが存在しません。
exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。
enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。
death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。
death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。
loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。
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}内にありません。
loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。
1 id zh en ja
22 player_packed_scene_not_exist 玩家预制场景不存在。 Player packed scene does not exist. プレイヤーのパックされたシーンが存在しません。
23 exit_the_room_debug 节点{0}退出房间{1}。 Node {0} exits room {1}. ノード{0}が部屋{1}を退出します。
24 enter_the_room_debug 节点{0}进入房间{1}。 Node {0} enters room {1}. ノード{0}が部屋{1}に入ります。
25 death_info 生物{0}被{1}击败。 Creature {0} was defeated by {1}. 生物{0}が{1}によって打ち負かされました。
26 loot_list_has_no_entries ID为{0}的战利品表,没有指定条目。 Loot list with ID {0}, no entry specified. ID{0}の戦利品テーブルは、エントリ指定されていません。
27 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}内にありません。
28 loot_data_quantity 有{0}个战利品数据被返回。 {0} loot data was returned. {0}個の戦利品データが返されます。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,7 @@
id,zh,en,ja
slogan_0,游戏属于每一个人。,The game belongs to everyone.,ゲームはすべての人のものです。
slogan_1,如果你想要得到爱,你就播种爱。,"If you want love, you sow love.",愛を手に入れたければ、愛の種をまきます。
slogan_2,快乐?伤心?痛苦?,Happy? Sad? Pain?,楽しいですか?悲しいですか?苦しいですか?
slogan_3,Kawaii!,Kawaii!,Kawaii!
slogan_4,魔法是想象的世界。,Magic is an imaginary world.,魔法は想像の世界です。
slogan_5,也试试Minecraft,Also try Minecraft!,Minecraftもやってみて
slogan_6,也试试Terraria,Also try Terraria!,Terrariaもやってみて
slogan_1,Kawaii!,Kawaii!,Kawaii!
slogan_2,魔法是想象的世界。,Magic is an imaginary world.,魔法は想像の世界です。
slogan_3,也试试《Minecraft》,Also try 'Minecraft'!,「Minecraft」もやってみて
slogan_4,也试试《Terraria》,Also try 'Terraria'!,「Terraria」もやってみて
slogan_5,你好,Hello,こんにちは
1 id zh en ja
2 slogan_0 游戏属于每一个人。 The game belongs to everyone. ゲームはすべての人のものです。
3 slogan_1 如果你想要得到爱,你就播种爱。 Kawaii! If you want love, you sow love. Kawaii! 愛を手に入れたければ、愛の種をまきます。 Kawaii!
4 slogan_2 快乐?伤心?痛苦? 魔法是想象的世界。 Happy? Sad? Pain? Magic is an imaginary world. 楽しいですか?悲しいですか?苦しいですか? 魔法は想像の世界です。
5 slogan_3 Kawaii! 也试试《Minecraft》! Kawaii! Also try 'Minecraft'! Kawaii! 「Minecraft」もやってみて!
6 slogan_4 魔法是想象的世界。 也试试《Terraria》! Magic is an imaginary world. Also try 'Terraria'! 魔法は想像の世界です。 「Terraria」もやってみて!
7 slogan_5 也试试Minecraft! 你好 Also try Minecraft! Hello Minecraftもやってみて! こんにちは
slogan_6 也试试Terraria! Also try Terraria! Terrariaもやってみて!

Binary file not shown.

Binary file not shown.

View File

@ -34,6 +34,7 @@ script = ExtResource("1_ubaid")
metadata/CampId = "Mazoku"
metadata/MaxHp = 50
metadata/Name = "死灵法师"
metadata/LootListId = "Test"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4)

View File

@ -23,11 +23,11 @@ metadata/ID = "StaffOfTheUndead"
metadata/MaxStackQuantity = 1
metadata/Description = "staff_of_the_undead_desc"
[node name="Area2D" type="Area2D" parent="."]
[node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8
collision_mask = 68
collision_mask = 71
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea2D"]
position = Vector2(25.5, 0.5)
shape = SubResource("RectangleShape2D_obcq2")
@ -41,11 +41,3 @@ shape = SubResource("RectangleShape2D_14m1g")
[node name="Marker2D" type="Marker2D" parent="."]
position = Vector2(65, 0)
[node name="RayCast2D" type="RayCast2D" parent="."]
position = Vector2(26, -8)
target_position = Vector2(0, 20)
collision_mask = 34
[node name="RayCast2D2" type="RayCast2D" parent="."]
target_position = Vector2(0, -29)

View File

@ -161,7 +161,3 @@ locale/translations=PackedStringArray("res://locals/DeathInfo.en.translation", "
[physics]
2d/default_gravity=480.0
[rendering]
renderer/rendering_method="mobile"

View File

@ -18,7 +18,20 @@ public static class Config
/// <para>巡逻</para>
/// <para>Patrol</para>
/// </summary>
public const string? Patrol = "Patrol";
public const string Patrol = "Patrol";
}
/// <summary>
/// <para>Loot table ID</para>
/// <para>战利品表ID</para>
/// </summary>
public static class LootListId
{
/// <summary>
/// <para>A trophy table for testing</para>
/// <para>测试用的战利品表</para>
/// </summary>
public const string Test = "Test";
}
/// <summary>
@ -72,6 +85,12 @@ public static class Config
public const string Aborigines = "Aborigines";
}
/// <summary>
/// <para>The percentage of speed reduced after a thrown item hits an enemy</para>
/// <para>抛出的物品击中敌人后减少的速度百分比</para>
/// </summary>
public const float ThrownItemsHitEnemiesReduceSpeedByPercentage = 0.5f;
/// <summary>
/// <para>How much blood does a heart represent</para>
/// <para>一颗心代表多少血量</para>

View File

@ -106,6 +106,7 @@ public partial class CharacterTemplate : CharacterBody2D
public string CampId = null!;
private DamageNumberNodeSpawn? _damageNumber;
/// <summary>
/// <para>Character referenced loot table</para>
/// <para>角色引用的战利品表</para>
@ -206,7 +207,7 @@ public partial class CharacterTemplate : CharacterBody2D
CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
string 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.
@ -311,6 +312,7 @@ public partial class CharacterTemplate : CharacterBody2D
if (pickAbleItem is WeaponTemplate weaponTemplate)
{
weaponTemplate.Owner = this;
weaponTemplate.Picked = true;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, false);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
weaponTemplate.EnableContactInjury = false;
@ -445,6 +447,7 @@ public partial class CharacterTemplate : CharacterBody2D
//角色死亡
OnDie(damageTemplate);
ThrowAllItemOnDie();
return true;
}
@ -452,6 +455,42 @@ public partial class CharacterTemplate : CharacterBody2D
return true;
}
/// <summary>
/// <para>Create Loot Object</para>
/// <para>创建战利品对象</para>
/// </summary>
protected void CreateLootObject()
{
if (_lootList == null)
{
return;
}
var lootDataArray = _lootList.GenerateLootData();
if (lootDataArray.Length == 0)
{
return;
}
var finalGlobalPosition = GlobalPosition;
CallDeferred("GenerateLootObjects", this, lootDataArray, finalGlobalPosition);
}
/// <summary>
/// <para>GenerateLootObjects</para>
/// <para>生成战利品对象</para>
/// </summary>
/// <param name="parentNode"></param>
/// <param name="lootDataArray"></param>
/// <param name="position"></param>
public void GenerateLootObjects(Node parentNode,
LootData[] lootDataArray,
Vector2 position)
{
LootListManager.GenerateLootObjects(parentNode, lootDataArray, position);
}
/// <summary>
/// <para>Add power to the character</para>
/// <para>在角色身上添加力</para>
@ -486,6 +525,7 @@ public partial class CharacterTemplate : CharacterBody2D
}
}
CreateLootObject();
QueueFree();
return Task.CompletedTask;
}
@ -609,6 +649,7 @@ public partial class CharacterTemplate : CharacterBody2D
return;
}
weaponTemplate.Picked = false;
CallDeferred("WeaponTemplateReparent", weaponTemplate);
var timer = new Timer();
weaponTemplate.AddChild(timer);
@ -708,5 +749,17 @@ public partial class CharacterTemplate : CharacterBody2D
}
protected virtual void HookPhysicsProcess(ref Vector2 velocity, double delta) { }
protected virtual void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
//The cost of applying force in the X direction.
//对X方向施加力消耗。
if ((int)velocity.X == 0)
{
velocity.X = 0;
}
else
{
velocity.X *= 0.95f;
}
}
}

View File

@ -398,28 +398,29 @@ public partial class Player : CharacterTemplate
//If there is a scene of floating text, then we generate floating text.
//如果有悬浮文本的场景,那么我们生成悬浮文本。
_floatLabel?.QueueFree();
_floatLabel = (Control)_floatLabelPackedScene.Instantiate();
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
_floatLabel = NodeUtils.InstantiatePackedScene<Control>(_floatLabelPackedScene, node);
if (_floatLabel != null)
{
var stringBuilder = new StringBuilder();
if (weapon.Owner is CharacterTemplate characterTemplate)
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
var stringBuilder = new StringBuilder();
if (weapon.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
}
stringBuilder.Append(TranslationServerUtils.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
}
stringBuilder.Append(TranslationServerUtils.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
}
node.AddChild(_floatLabel);
}
UpdateOperationTip();

View File

@ -1,3 +1,4 @@
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.damage;
@ -99,7 +100,8 @@ public partial class DamageNumberNodeSpawn : Marker2D
return;
}
if (_damageNumberPackedScene.Instantiate() is not DamageNumber damageNumber)
var damageNumber = NodeUtils.InstantiatePackedScene<DamageNumber>(_damageNumberPackedScene);
if (damageNumber == null)
{
return;
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using ColdMint.scripts.character;
using ColdMint.scripts.utils;
using Godot;
@ -8,87 +8,24 @@ namespace ColdMint.scripts.inventory;
/// <para>HotBar</para>
/// <para>快捷物品栏</para>
/// </summary>
public partial class HotBar : HBoxContainer, IItemContainer
public partial class HotBar : HBoxContainer
{
private PackedScene? _itemSlotPackedScene;
private List<ItemSlotNode>? _itemSlotNodes;
/// <summary>
/// <para>UnknownIndex</para>
/// <para>未知位置</para>
/// </summary>
private const int UnknownIndex = -1;
//_selectIndex默认为0.
private int _selectIndex;
private UniversalItemContainer? _universalItemContainer;
public override void _Ready()
{
base._Ready();
_universalItemContainer = new UniversalItemContainer
{
CharacterTemplate = new Player()
};
NodeUtils.DeleteAllChild(this);
_itemSlotNodes = new List<ItemSlotNode>();
_itemSlotPackedScene = GD.Load<PackedScene>("res://prefab/ui/ItemSlot.tscn");
for (var i = 0; i < Config.HotBarSize; i++)
{
AddItemSlot(i);
_universalItemContainer.AddItemSlot(this, i);
}
}
/// <summary>
/// <para>Select the next item slot</para>
/// <para>选择下一个物品槽</para>
/// </summary>
private void SelectTheNextItemSlot()
{
if (_itemSlotNodes == null)
{
return;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
return;
}
var oldSelectIndex = _selectIndex;
_selectIndex++;
if (_selectIndex >= count)
{
_selectIndex = 0;
}
SelectItemSlot(oldSelectIndex, _selectIndex);
}
/// <summary>
/// <para>Select the previous item slot</para>
/// <para>选择上一个物品槽</para>
/// </summary>
private void SelectThePreviousItemSlot()
{
if (_itemSlotNodes == null)
{
return;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
return;
}
var oldSelectIndex = _selectIndex;
_selectIndex--;
if (_selectIndex < 0)
{
_selectIndex = count - 1;
}
SelectItemSlot(oldSelectIndex, _selectIndex);
}
public override void _Process(double delta)
{
base._Process(delta);
@ -96,14 +33,14 @@ public partial class HotBar : HBoxContainer, IItemContainer
{
//Mouse wheel down
//鼠标滚轮向下
SelectTheNextItemSlot();
_universalItemContainer?.SelectTheNextItemSlot();
}
if (Input.IsActionJustPressed("hotbar_previous"))
{
//Mouse wheel up
//鼠标滚轮向上
SelectThePreviousItemSlot();
_universalItemContainer?.SelectThePreviousItemSlot();
}
if (Input.IsActionJustPressed("hotbar_1"))
@ -161,248 +98,15 @@ public partial class HotBar : HBoxContainer, IItemContainer
/// <param name="shortcutKeyIndex"></param>
private void SelectItemSlotByHotBarShortcutKey(int shortcutKeyIndex)
{
if (_itemSlotNodes == null)
if (_universalItemContainer == null)
{
return;
}
var safeIndex = GetSafeIndex(shortcutKeyIndex);
if (safeIndex == UnknownIndex)
{
return;
}
SelectItemSlot(_selectIndex, safeIndex);
_selectIndex = safeIndex;
_universalItemContainer.SelectItemSlot(shortcutKeyIndex);
}
/// <summary>
/// <para>Removes an item from the currently selected inventory</para>
/// <para>移除当前选中的物品栏内的物品</para>
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
public bool RemoveItemFromItemSlotBySelectIndex(int number)
public IItemContainer? GetItemContainer()
{
return RemoveItemFromItemSlot(_selectIndex, number);
}
public int GetItemSlotCount()
{
if (_itemSlotNodes == null)
{
return 0;
}
return _itemSlotNodes.Count;
}
public ItemSlotNode? GetItemSlotNode(int index)
{
if (_itemSlotNodes == null)
{
return null;
}
var safeIndex = GetSafeIndex(index);
return _itemSlotNodes[safeIndex];
}
/// <summary>
/// <para>Remove items from the item slot</para>
/// <para>从物品槽内移除物品</para>
/// </summary>
/// <param name="itemSlotIndex">
///<para>When this number is greater than the number of item slots, residual filtering is used.</para>
///<para>当此数量大于物品槽的数量时,会使用余数筛选。</para>
/// </param>
/// <param name="number">
///<para>The number of items removed</para>
///<para>移除物品的数量</para>
/// </param>
public bool RemoveItemFromItemSlot(int itemSlotIndex, int number)
{
if (_itemSlotNodes == null)
{
return false;
}
var safeIndex = GetSafeIndex(itemSlotIndex);
if (safeIndex == UnknownIndex)
{
return false;
}
var itemSlot = _itemSlotNodes[safeIndex];
return itemSlot.RemoveItem(number);
}
/// <summary>
/// <para>Gets a secure subscript index</para>
/// <para>获取安全的下标索引</para>
/// </summary>
/// <param name="itemSlotIndex"></param>
/// <returns>
///<para>-1 is returned on failure, and the index that does not result in an out-of-bounds subscript is returned on success</para>
///<para>失败返回-1成功返回不会导致下标越界的索引</para>
/// </returns>
private int GetSafeIndex(int itemSlotIndex)
{
if (_itemSlotNodes == null)
{
return UnknownIndex;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
//Prevents the dividend from being 0
//防止被除数为0
return UnknownIndex;
}
return itemSlotIndex % count;
}
/// <summary>
/// <para>Select an item slot</para>
/// <para>选中某个物品槽</para>
/// </summary>
private void SelectItemSlot(int oldSelectIndex, int newSelectIndex)
{
if (oldSelectIndex == newSelectIndex)
{
return;
}
if (_itemSlotNodes == null)
{
return;
}
_itemSlotNodes[oldSelectIndex].IsSelect = false;
_itemSlotNodes[newSelectIndex].IsSelect = true;
var oldItem = _itemSlotNodes[oldSelectIndex].GetItem();
if (oldItem != null && oldItem is Node2D oldNode2D)
{
oldNode2D.ProcessMode = ProcessModeEnum.Disabled;
oldNode2D.Hide();
}
var item = _itemSlotNodes[newSelectIndex].GetItem();
if (item == null)
{
if (GameSceneNodeHolder.Player != null)
{
GameSceneNodeHolder.Player.CurrentItem = null;
}
}
else
{
if (item is Node2D node2D)
{
node2D.ProcessMode = ProcessModeEnum.Inherit;
node2D.Show();
if (GameSceneNodeHolder.Player != null)
{
GameSceneNodeHolder.Player.CurrentItem = node2D;
}
}
else
{
if (GameSceneNodeHolder.Player != null)
{
GameSceneNodeHolder.Player.CurrentItem = null;
}
}
}
}
public bool CanAddItem(IItem item)
{
return Matching(item) != null;
}
/// <summary>
/// <para>Add an item to the HotBar</para>
/// <para>在HotBar内添加一个物品</para>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool AddItem(IItem item)
{
var itemSlotNode = Matching(item);
if (itemSlotNode == null)
{
return false;
}
return itemSlotNode.ReplaceItemStack(item);
}
public int GetSelectIndex()
{
return _selectIndex;
}
public ItemSlotNode? GetSelectItemSlotNode()
{
if (_itemSlotNodes == null || _itemSlotNodes.Count == 0)
{
return null;
}
if (_selectIndex < _itemSlotNodes.Count)
{
//Prevent subscripts from going out of bounds.
//防止下标越界。
return _itemSlotNodes[_selectIndex];
}
return null;
}
public ItemSlotNode? Matching(IItem item)
{
if (_itemSlotNodes == null || _itemSlotNodes.Count == 0)
{
return null;
}
foreach (var itemSlotNode in _itemSlotNodes)
{
if (itemSlotNode.CanAddItem(item))
{
//If there is an item slot to put this item in, then we return it.
//如果有物品槽可放置此物品,那么我们返回它。
return itemSlotNode;
}
}
return null;
}
/// <summary>
/// <para>Add items tank</para>
/// <para>添加物品槽</para>
/// </summary>
private void AddItemSlot(int index)
{
if (_itemSlotNodes == null || _itemSlotPackedScene == null)
{
return;
}
if (_itemSlotPackedScene.Instantiate() is not ItemSlotNode itemSlotNode)
{
return;
}
AddChild(itemSlotNode);
itemSlotNode.IsSelect = index == _selectIndex;
_itemSlotNodes.Add(itemSlotNode);
return _universalItemContainer;
}
}

View File

@ -1,4 +1,6 @@
using ColdMint.scripts.item;
using ColdMint.scripts.item;
using Godot;
namespace ColdMint.scripts.inventory;
@ -56,7 +58,7 @@ public interface IItemContainer
/// </summary>
/// <returns></returns>
int GetItemSlotCount();
/// <summary>
/// <para>Gets the item slot for the specified location</para>
/// <para>获取指定位置的物品槽</para>
@ -84,4 +86,31 @@ public interface IItemContainer
///<para>若没有槽可放置此物品则返回null</para>
/// </returns>
ItemSlotNode? Matching(IItem_New item);
/// <summary>
/// <para>AddItemSlot</para>
/// <para>添加物品槽</para>
/// </summary>
/// <param name="rootNode"></param>
/// <param name="index"></param>
void AddItemSlot(Node rootNode, int index);
/// <summary>
/// <para>SelectTheNextItemSlot</para>
/// <para>选择下一个物品槽</para>
/// </summary>
void SelectTheNextItemSlot();
/// <summary>
/// <para>SelectThePreviousItemSlot</para>
/// <para>选择上一个物品槽</para>
/// </summary>
void SelectThePreviousItemSlot();
/// <summary>
/// <para>选择物品槽</para>
/// <para>SelectItemSlot</para>
/// </summary>
/// <param name="newSelectIndex"></param>
void SelectItemSlot(int newSelectIndex);
}

View File

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

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.inventory;
@ -14,7 +16,7 @@ public class LootList
/// <para>战利品表的Id</para>
/// </summary>
public string? Id { get; set; }
private List<LootEntry>? _lootEntrieList;
/// <summary>
@ -31,19 +33,20 @@ public class LootList
_lootEntrieList.Add(lootEntry);
}
/// <summary>
/// <para>GenerateLootData</para>
/// <para>生成战利品数据</para>
/// </summary>
/// <returns></returns>
public List<LootData> GenerateLootData()
public LootData[] GenerateLootData()
{
var lootDataList = new List<LootData>();
if (_lootEntrieList == null)
{
return lootDataList;
LogCat.LogWithFormat("loot_list_has_no_entries", Id);
return lootDataList.ToArray();
}
foreach (var lootEntry in _lootEntrieList)
@ -53,8 +56,10 @@ public class LootList
{
//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);
@ -66,9 +71,10 @@ public class LootList
lootDataList.Add(lootData);
}
return lootDataList;
LogCat.LogWithFormat("loot_data_quantity", lootDataList.Count);
return lootDataList.ToArray();
}
/// <summary>
/// <para>Remove loot entry</para>

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
@ -16,10 +18,15 @@ public static class LootListManager
/// <para>Register loot table</para>
/// <para>注册战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <param name="lootList"></param>
public static bool RegisterLootList(string id, LootList lootList)
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;
@ -40,25 +47,19 @@ public static class LootListManager
/// <para>Generate loot objects</para>
/// <para>生成战利品对象</para>
/// </summary>
/// <param name="lootDataArray">
///<para>lootDataArray</para>
///<para>战利品数组</para>
/// </param>
/// <param name="parentNode">
///<para>parentNode</para>
///<para>父节点</para>
/// </param>
public static void GenerateLootObjects(LootData[] lootDataArray, Node parentNode)
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))
if (string.IsNullOrEmpty(lootData.ResPath) || lootData.Quantity == 0)
{
continue;
}
@ -69,24 +70,34 @@ public static class LootListManager
packedSceneDictionary.TryAdd(lootData.ResPath, packedScene);
}
CreateLootObject(packedScene, parentNode);
for (var i = 0; i < lootData.Quantity; i++)
{
//Generate as many loot instance objects as there are loot.
//有多少个战利品就生成多少个战利品实例对象。
CreateLootInstanceObject(parentNode, packedScene, position);
}
}
}
private static void CreateLootObject(PackedScene? packedScene, Node parent)
/// <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 = packedScene.Instantiate();
if (lootObject is not Node2D node2D)
var lootObject = NodeUtils.InstantiatePackedScene<Node2D>(packedScene, parent);
if (lootObject == null)
{
return;
}
parent.AddChild(node2D);
lootObject.Position = position;
}
/// <summary>

View File

@ -0,0 +1,307 @@
using System.Collections.Generic;
using ColdMint.scripts.character;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>UniversalItemContainer</para>
/// <para>通用的物品容器</para>
/// </summary>
public class UniversalItemContainer : IItemContainer
{
private readonly PackedScene? _itemSlotPackedScene = GD.Load<PackedScene>("res://prefab/ui/ItemSlot.tscn");
private readonly List<ItemSlotNode>? _itemSlotNodes = new();
/// <summary>
/// <para>Character</para>
/// <para>角色</para>
/// </summary>
public CharacterTemplate? CharacterTemplate { get; set; }
/// <summary>
/// <para>UnknownIndex</para>
/// <para>未知位置</para>
/// </summary>
private const int UnknownIndex = -1;
//_selectIndex默认为0.
private int _selectIndex;
public bool CanAddItem(IItem item)
{
return Matching(item) != null;
}
public bool AddItem(IItem item)
{
var itemSlotNode = Matching(item);
if (itemSlotNode == null)
{
return false;
}
return itemSlotNode.SetItem(item);
}
public int GetSelectIndex()
{
return _selectIndex;
}
public ItemSlotNode? GetSelectItemSlotNode()
{
if (_itemSlotNodes == null || _itemSlotNodes.Count == 0)
{
return null;
}
if (_selectIndex < _itemSlotNodes.Count)
{
//Prevent subscripts from going out of bounds.
//防止下标越界。
return _itemSlotNodes[_selectIndex];
}
return null;
}
public bool RemoveItemFromItemSlotBySelectIndex(int number)
{
return RemoveItemFromItemSlot(_selectIndex, number);
}
public int GetItemSlotCount()
{
if (_itemSlotNodes == null)
{
return 0;
}
return _itemSlotNodes.Count;
}
public ItemSlotNode? GetItemSlotNode(int index)
{
if (_itemSlotNodes == null)
{
return null;
}
var safeIndex = GetSafeIndex(index);
return _itemSlotNodes[safeIndex];
}
public bool RemoveItemFromItemSlot(int itemSlotIndex, int number)
{
if (_itemSlotNodes == null)
{
return false;
}
var safeIndex = GetSafeIndex(itemSlotIndex);
if (safeIndex == UnknownIndex)
{
return false;
}
var itemSlot = _itemSlotNodes[safeIndex];
return itemSlot.RemoveItem(number);
}
public ItemSlotNode? Matching(IItem item)
{
if (_itemSlotNodes == null || _itemSlotNodes.Count == 0)
{
return null;
}
foreach (var itemSlotNode in _itemSlotNodes)
{
if (itemSlotNode.CanSetItem(item))
{
//If there is an item slot to put this item in, then we return it.
//如果有物品槽可放置此物品,那么我们返回它。
return itemSlotNode;
}
}
return null;
}
/// <summary>
/// <para>Gets a secure subscript index</para>
/// <para>获取安全的下标索引</para>
/// </summary>
/// <param name="itemSlotIndex"></param>
/// <returns>
///<para>-1 is returned on failure, and the index that does not result in an out-of-bounds subscript is returned on success</para>
///<para>失败返回-1成功返回不会导致下标越界的索引</para>
/// </returns>
private int GetSafeIndex(int itemSlotIndex)
{
if (_itemSlotNodes == null)
{
return UnknownIndex;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
//Prevents the dividend from being 0
//防止被除数为0
return UnknownIndex;
}
return itemSlotIndex % count;
}
/// <summary>
/// <para>Add items tank</para>
/// <para>添加物品槽</para>
/// </summary>
public void AddItemSlot(Node rootNode, int index)
{
if (_itemSlotNodes == null || _itemSlotPackedScene == null)
{
return;
}
var itemSlotNode = NodeUtils.InstantiatePackedScene<ItemSlotNode>(_itemSlotPackedScene, rootNode);
if (itemSlotNode == null)
{
return;
}
itemSlotNode.IsSelect = index == _selectIndex;
_itemSlotNodes.Add(itemSlotNode);
}
public void SelectTheNextItemSlot()
{
if (_itemSlotNodes == null)
{
return;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
return;
}
var oldSelectIndex = _selectIndex;
var newSelectIndex = _selectIndex + 1;
if (newSelectIndex >= count)
{
newSelectIndex = 0;
}
PrivateSelectItemSlot(oldSelectIndex, newSelectIndex);
}
public void SelectThePreviousItemSlot()
{
if (_itemSlotNodes == null)
{
return;
}
var count = _itemSlotNodes.Count;
if (count == 0)
{
return;
}
var oldSelectIndex = _selectIndex;
var newSelectIndex = _selectIndex - 1;
if (newSelectIndex < 0)
{
newSelectIndex = count - 1;
}
PrivateSelectItemSlot(oldSelectIndex, newSelectIndex);
}
public void SelectItemSlot(int newSelectIndex)
{
if (newSelectIndex == _selectIndex)
{
return;
}
var safeIndex = GetSafeIndex(newSelectIndex);
if (safeIndex == UnknownIndex)
{
return;
}
PrivateSelectItemSlot(_selectIndex, newSelectIndex);
}
/// <summary>
/// <para>Select an item slot</para>
/// <para>选中某个物品槽</para>
/// </summary>
private void PrivateSelectItemSlot(int oldSelectIndex, int newSelectIndex)
{
if (oldSelectIndex == newSelectIndex)
{
return;
}
if (_itemSlotNodes == null)
{
return;
}
_itemSlotNodes[oldSelectIndex].IsSelect = false;
_itemSlotNodes[newSelectIndex].IsSelect = true;
var oldItem = _itemSlotNodes[oldSelectIndex].GetItem();
if (oldItem is Node2D oldNode2D)
{
oldNode2D.ProcessMode = Node.ProcessModeEnum.Disabled;
oldNode2D.Hide();
}
var item = _itemSlotNodes[newSelectIndex].GetItem();
switch (item)
{
case null:
{
if (CharacterTemplate != null)
{
CharacterTemplate.CurrentItem = null;
}
break;
}
case Node2D node2D:
{
node2D.ProcessMode = Node.ProcessModeEnum.Inherit;
node2D.Show();
if (CharacterTemplate != null)
{
CharacterTemplate.CurrentItem = node2D;
}
break;
}
default:
{
if (CharacterTemplate != null)
{
CharacterTemplate.CurrentItem = null;
}
break;
}
}
_selectIndex = newSelectIndex;
}
}

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.projectile;
using ColdMint.scripts.utils;
using Godot;
@ -22,12 +23,13 @@ public partial class ProjectileWeapon : WeaponTemplate
/// <para>抛射体的生成位置</para>
/// </summary>
private Marker2D? _marker2D;
/// <summary>
/// <para>List of projectiles</para>
/// <para>抛射体列表</para>
/// </summary>
private string[]? _projectiles;
private Dictionary<string, PackedScene>? _projectileCache;
private Node2D? _projectileContainer;
@ -44,6 +46,7 @@ public partial class ProjectileWeapon : WeaponTemplate
{
continue;
}
_projectileCache.Add(projectileItem, packedScene);
}
@ -58,23 +61,20 @@ public partial class ProjectileWeapon : WeaponTemplate
{
return;
}
if (_projectiles.IsEmpty())
{
LogCat.LogError("projectiles_is_empty");
return;
}
//Get the first projectile
//获取第一个抛射体
var projectileScene = _projectileCache[_projectiles[0]];
var projectile = projectileScene.Instantiate() as ProjectileTemplate;
if (projectile != null)
{
projectile.Owner = owner;
projectile.Velocity = (enemyGlobalPosition - _marker2D.GlobalPosition).Normalized() * projectile.Speed;
projectile.Position = _marker2D.GlobalPosition;
}
_projectileContainer.AddChild(projectile);
var projectile = NodeUtils.InstantiatePackedScene<ProjectileTemplate>(projectileScene, _projectileContainer);
if (projectile == null) return;
projectile.Owner = owner;
projectile.Velocity = (enemyGlobalPosition - _marker2D.GlobalPosition).Normalized() * projectile.Speed;
projectile.Position = _marker2D.GlobalPosition;
}
}

View File

@ -35,6 +35,13 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem_New
Fire(owner, targetGlobalPosition);
}
/// <summary>
/// <para>Whether the weapon is currently picked up</para>
/// <para>当前武器是否被捡起了</para>
/// </summary>
public bool Picked { get; set; }
/// <summary>
/// <para>Owner</para>
/// <para>主人</para>
@ -75,16 +82,21 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem_New
/// <para>This area represents the collision range of the weapon, and when other nodes enter this area, they will deal damage.</para>
/// <para>这个区域表示武器的碰撞范围,当其他节点进入此区域时,会造成伤害。</para>
/// </summary>
private Area2D? _area2D;
private Area2D? _damageArea2D;
protected RayCast2D? RayCast2D;
/// <summary>
/// <para>The number of tile maps in contact with this weapon</para>
/// <para>与此武器接触的瓦片地图数量</para>
/// </summary>
private int _tileMapNumber;
public override void _Ready()
{
RayCast2D = GetNode<RayCast2D>("RayCast2D");
_area2D = GetNode<Area2D>("Area2D");
_area2D.BodyEntered += OnBodyEnter;
_damageArea2D = GetNode<Area2D>("DamageArea2D");
_damageArea2D.BodyEntered += OnBodyEnter;
_damageArea2D.BodyExited += OnBodyExited;
// Id = GetMeta("ID", "1").AsString();
// Quantity = GetMeta("Quantity", "1").AsInt32();
// MaxStackQuantity = GetMeta("MaxStackQuantity", Config.MaxStackQuantity).AsInt32();
@ -99,6 +111,27 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem_New
_firingInterval = TimeSpan.FromMilliseconds(_firingIntervalAsMillisecond);
}
private void OnBodyExited(Node node)
{
if (Picked)
{
return;
}
//If it leaves the ground or walls.
//如果离开了地面或墙壁。
if (node is TileMap tileMap)
{
_tileMapNumber--;
if (_tileMapNumber == 0)
{
//No longer in contact with any shingles can cause injury
//不再与任何瓦片接触后,可以造成伤害
EnableContactInjury = true;
SetCollisionMaskValue(Config.LayerNumber.Player, false);
}
}
}
/// <summary>
/// <para>Use weapons against the enemy</para>
@ -107,50 +140,57 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem_New
/// <param name="node"></param>
private void OnBodyEnter(Node node)
{
if (!EnableContactInjury)
if (Picked)
{
return;
}
if (Owner == null)
if (node is TileMap tileMap)
{
return;
_tileMapNumber++;
EnableContactInjury = false;
//Items can be pushed by the player when they are on the ground
//当物品在地面上时,可被玩家推动
SetCollisionMaskValue(Config.LayerNumber.Player, true);
}
if (Owner is not CharacterTemplate ownerCharacterTemplate)
else if (node is CharacterTemplate characterTemplate)
{
return;
if (!EnableContactInjury)
{
return;
}
if (Owner is not CharacterTemplate ownerCharacterTemplate)
{
return;
}
//Determine if your side can cause damage
//判断所属的阵营是否可以造成伤害
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
if (!canCauseHarm)
{
return;
}
//If allowed to cause harm
//如果允许造成伤害
var damage = new Damage
{
MaxDamage = Math.Abs(_maxContactInjury),
MinDamage = Math.Abs(_minContactInjury),
Attacker = ownerCharacterTemplate
};
damage.CreateDamage();
damage.MoveLeft = LinearVelocity.X < 0;
damage.Type = Config.DamageType.Physical;
characterTemplate.Damage(damage);
//Reduce speed after hitting enemies.
//击中敌人后减少速度。
LinearVelocity *= 1 - Config.ThrownItemsHitEnemiesReduceSpeedByPercentage;
}
if (node is not CharacterTemplate characterTemplate)
{
return;
}
//Determine if your side can cause damage
//判断所属的阵营是否可以造成伤害
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
if (!canCauseHarm)
{
return;
}
//If allowed to cause harm
//如果允许造成伤害
var damage = new Damage
{
MaxDamage = Math.Abs(_maxContactInjury),
MinDamage = Math.Abs(_minContactInjury),
Attacker = ownerCharacterTemplate
};
damage.CreateDamage();
damage.MoveLeft = LinearVelocity.X < 0;
damage.Type = Config.DamageType.Physical;
characterTemplate.Damage(damage);
//Can only cause one collision damage.
//仅能造成一次碰撞伤害。
EnableContactInjury = false;
}
/// <summary>
@ -159,27 +199,6 @@ public abstract partial class WeaponTemplate : RigidBody2D, IItem_New
/// <param name="facingLeft"></param>
public void Flip(bool facingLeft) { }
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
if (RayCast2D != null)
{
if (RayCast2D.IsColliding())
{
//If the weapon hits the ground, we disable physical damage.
//如果武器落到地面了,我们禁用物理伤害。
EnableContactInjury = false;
//Items can be pushed by the player when they are on the ground
//当物品在地面上时,可被玩家推动
SetCollisionMaskValue(Config.LayerNumber.Player, true);
}
else
{
SetCollisionMaskValue(Config.LayerNumber.Player, false);
}
}
}
/// <summary>
/// <para>Discharge of the weapon</para>

View File

@ -19,7 +19,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
{
private string? _defaultRoomName;
private readonly LevelGraphEditorBinding _nodeBinding = new LevelGraphEditorBinding();
private readonly LevelGraphEditorBinding _nodeBinding = new();
/// <summary>
/// <para>Index of the room</para>
@ -35,7 +35,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
private PackedScene? _roomNodeScene;
private readonly List<Node> _selectedNodes = new List<Node>();
private readonly List<Node> _selectedNodes = new();
/// <summary>
/// <para>Displays the time to enter the suggestion</para>
@ -47,7 +47,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
/// <para>Offset to append when a new node is created.</para>
/// <para>创建新节点时追加的偏移量。</para>
/// </summary>
private Vector2 _positionOffset = new Vector2(100, 100);
private Vector2 _positionOffset = new(100, 100);
/// <summary>
/// <para>Is the press event of an active button saved?</para>
@ -99,7 +99,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
return null;
}
var node = _roomNodeScene.Instantiate();
var node = NodeUtils.InstantiatePackedScene<Node>(_roomNodeScene);
if (node == null)
{
return null;

View File

@ -4,6 +4,7 @@ using System.Text;
using ColdMint.scripts.camp;
using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.map;
using ColdMint.scripts.map.roomInjectionProcessor;
using Godot;
@ -39,6 +40,23 @@ public partial class MainMenuLoader : UiLoaderTemplate
LogCat.MinLogLevel = LogCat.DisableAllLogLevel;
}
//注册测试使用的战利品表
if (Config.IsDebug())
{
var testLootList = new LootList
{
Id = Config.LootListId.Test
};
var lootEntry = new LootEntry
{
Chance = 1f,
MaxQuantity = 5,
MinQuantity = 1,
ResPath = "res://prefab/weapons/staffOfTheUndead.tscn"
};
testLootList.AddLootEntry(lootEntry);
LootListManager.RegisterLootList(testLootList);
}
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor());

View File

@ -1,6 +1,7 @@
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.map.events;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.map;
@ -32,18 +33,16 @@ public partial class AiCharacterSpawn : Marker2D
/// <param name="aiCharacterGenerateEvent"></param>
public void OnAiCharacterGenerateEvent(AiCharacterGenerateEvent aiCharacterGenerateEvent)
{
var node = _packedScene?.Instantiate();
if (node is not AiCharacter aiCharacter)
if (_packedScene == null)
{
return;
}
if (GameSceneNodeHolder.AiCharacterContainer == null)
var aiCharacter = NodeUtils.InstantiatePackedScene<AiCharacter>(_packedScene,GameSceneNodeHolder.AiCharacterContainer);
if (aiCharacter == null)
{
return;
}
GameSceneNodeHolder.AiCharacterContainer.AddChild(aiCharacter);
aiCharacter.Position = GlobalPosition;
}

View File

@ -1,6 +1,7 @@
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.map.events;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.map;
@ -30,6 +31,7 @@ public partial class PlayerSpawn : Marker2D
GameSceneNodeHolder.Player.Revive(GameSceneNodeHolder.Player.MaxHp);
return;
}
SpawnPlayer();
}
@ -50,17 +52,16 @@ public partial class PlayerSpawn : Marker2D
return;
}
var playerNode = _playerPackedScene.Instantiate();
if (playerNode is not Player player)
var playerNode =
NodeUtils.InstantiatePackedScene<Player>(_playerPackedScene, GameSceneNodeHolder.PlayerContainer);
if (playerNode == null)
{
return;
}
player.ItemContainer = GameSceneNodeHolder.HotBar;
GameSceneNodeHolder.PlayerContainer.AddChild(player);
GameSceneNodeHolder.Player = player;
player.Position = GlobalPosition;
LogCat.LogWithFormat("player_spawn_debug", player.ReadOnlyCharacterName, player.Position);
playerNode.ItemContainer = GameSceneNodeHolder.HotBar?.GetItemContainer();
GameSceneNodeHolder.Player = playerNode;
playerNode.Position = GlobalPosition;
LogCat.LogWithFormat("player_spawn_debug", playerNode.ReadOnlyCharacterName, playerNode.Position);
}
private void MapGenerationCompleteEvent(MapGenerationCompleteEvent mapGenerationCompleteEvent)

View File

@ -104,9 +104,14 @@ public class Room
/// <param name="packedScene"></param>
private void AnalyzeRoomData(PackedScene? packedScene)
{
var node = packedScene?.Instantiate();
if (node is not Node2D node2D)
if (packedScene == null)
{
return;
}
var node2D = NodeUtils.InstantiatePackedScene<Node2D>(packedScene);
if (node2D == null)
{
//The room node is not of Node2D type. An exception is thrown
//房间节点不是Node2D类型抛出异常
LogCat.LogError("room_root_node_must_be_node2d");
return;

View File

@ -2,6 +2,7 @@ using System;
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.projectile;
@ -105,8 +106,15 @@ public partial class ProjectileTemplate : CharacterBody2D
if (target is TileMap)
{
//When we hit the tile, we return true in order to place the bullet through the tile.
//撞击到瓦片时我们返回true是为了放置子弹穿透瓦片。
//When we hit the tile, we return true to prevent the bullet from penetrating the tile.
//撞击到瓦片时我们返回true是为了防止子弹穿透瓦片。
return true;
}
if (target is WeaponTemplate)
{
//Bullets are allowed to strike objects.
//允许子弹撞击物品。
return true;
}
@ -130,39 +138,56 @@ public partial class ProjectileTemplate : CharacterBody2D
/// <param name="target"></param>
private void DoDamage(Node2D? owner, Node2D target)
{
if (target is not CharacterTemplate characterTemplate)
if (target is CharacterTemplate characterTemplate)
{
return;
}
//Allow damage to be caused
//允许造成伤害
var damage = new Damage
{
Attacker = owner,
MaxDamage = MaxDamage,
MinDamage = MinDamage
};
damage.CreateDamage();
damage.MoveLeft = Velocity.X < 0;
damage.Type = DamageType;
characterTemplate.Damage(damage);
if (KnockbackForce != Vector2.Zero)
{
//If we set the attack force, then apply the force to the object
//如果我们设置了攻退力,那么将力应用到对象上
var force = new Vector2();
var forceX = Math.Abs(KnockbackForce.X);
if (Velocity.X < 0)
//Allow damage to be caused
//允许造成伤害
var damage = new Damage
{
//Beat back to port
//向左击退
forceX = -forceX;
}
Attacker = owner,
MaxDamage = MaxDamage,
MinDamage = MinDamage
};
damage.CreateDamage();
damage.MoveLeft = Velocity.X < 0;
damage.Type = DamageType;
characterTemplate.Damage(damage);
if (KnockbackForce != Vector2.Zero)
{
//If we set the attack force, then apply the force to the object
//如果我们设置了攻退力,那么将力应用到对象上
var force = new Vector2();
var forceX = Math.Abs(KnockbackForce.X);
if (Velocity.X < 0)
{
//Beat back to port
//向左击退
forceX = -forceX;
}
force.X = forceX * Config.CellSize;
force.Y = KnockbackForce.Y * Config.CellSize;
characterTemplate.AddForce(force);
force.X = forceX * Config.CellSize;
force.Y = KnockbackForce.Y * Config.CellSize;
characterTemplate.AddForce(force);
}
}else if (target is WeaponTemplate weaponTemplate)
{
if (KnockbackForce != Vector2.Zero)
{
//If we set the attack force, then apply the force to the object
//如果我们设置了攻退力,那么将力应用到对象上
var force = new Vector2();
var forceX = Math.Abs(KnockbackForce.X);
if (Velocity.X < 0)
{
//Beat back to port
//向左击退
forceX = -forceX;
}
force.X = forceX * Config.CellSize;
force.Y = KnockbackForce.Y * Config.CellSize;
weaponTemplate.ApplyImpulse(force);
}
}
}

View File

@ -1,5 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ColdMint.scripts.debug;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.utils;
@ -95,4 +98,85 @@ public static class NodeUtils
return closestNode;
}
/// <summary>
/// <para>Find the corresponding container node based on the child node</para>
/// <para>根据子节点查找对应的容器节点</para>
/// </summary>
/// <remarks>
///<para>We want child nodes to be placed under a specific parent node to facilitate the same management. For example, the weapon node should be placed inside the Weapon container node. We call a parent node of the same type as a child node a "container". This method is used to find the corresponding node container based on the type of the child node.</para>
///<para>我们希望子节点被放置在特定的父节点下,方便同一管理。例如:武器节点应该被放置在“武器容器”节点内。我们将子节点的类型相同的父节点叫做“容器”。此方法用于根据子节点的类型查找对应的节点容器。</para>
/// </remarks>
/// <param name="childNode">
///<para>childNode</para>
///<para>子节点</para>
/// </param>
/// <param name="defaultParentNode">
///<para>Default parent, which returns the default node if it cannot be matched by type.</para>
///<para>默认父节点,当按照类型无法匹配时,将返回默认节点。</para>
/// </param>
/// <returns></returns>
public static Node FindContainerNode(Node childNode, Node defaultParentNode)
{
if (GameSceneNodeHolder.WeaponContainer != null && childNode is WeaponTemplate)
{
return GameSceneNodeHolder.WeaponContainer;
}
return defaultParentNode;
}
/// <summary>
/// <para>Instantiate Packed Scene</para>
/// <para>实例化场景</para>
/// </summary>
/// <remarks>
///<para>This method is recommended in place of all packedScene.Instantiate() 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()调用,使用此方法实例化场景,可选择将其分配到与根节点类型相匹配的容器内。</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></returns>
public static T? InstantiatePackedScene<T>(PackedScene packedScene, Node? defaultParentNode = null,
bool assignedByRootNodeType = true) where T : Node
{
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.
//只有设定了默认父节点后才会尝试将实例化的节点放置到夫节点下。
if (defaultParentNode != null)
{
if (assignedByRootNodeType)
{
var containerNode = FindContainerNode(instantiateNode, defaultParentNode);
containerNode.AddChild(instantiateNode);
}
else
{
//If you do not need to assign by type, place it under the default parent node.
//如果不需要按照类型分配,那么将其放到默认父节点下。
defaultParentNode.AddChild(instantiateNode);
}
}
return instantiateNode;
}
catch (InvalidCastException e)
{
//null is returned if the type conversion fails.
//在类型转换失败时返回null。
LogCat.WhenCaughtException(e);
return null;
}
}
}