Compare commits

...

23 Commits

Author SHA1 Message Date
1429b879e8
Can generate a preview and place it in the mini-map.
能够生成预览图并将其放置到迷你地图内了。
2024-09-01 23:24:35 +08:00
c1a3bfa266
Improved weapon recoil effect.
改进武器的后座力效果。
2024-09-01 22:25:20 +08:00
1ef0f08e2f
Create a map preview image.
制作地图预览图。
2024-08-27 23:06:33 +08:00
5b0803f85f
After the player leaves the room, the previous room vision is deprived and enemies are frozen.
玩家离开房间后上个房间视野将被剥夺,敌人被冻结。
2024-08-25 18:49:48 +08:00
38c0bdff2a
Join the fog of war.
加入战争迷雾。
2024-08-24 22:49:59 +08:00
4f16214885
Modified the knockback algorithm.
修改击退算法。
2024-08-22 22:03:44 +08:00
d3f2076b11
Fix the issue of picking up lost attributes after losing weapons.
修复失去武器后再捡起丢失属性的问题。
2024-08-20 22:17:07 +08:00
cff0507d25
Bullet tracking will no longer penetrate the wall.
子弹跟踪不再会穿透墙壁了。
2024-08-20 00:15:12 +08:00
2a5629fc86
Projectiles can now track enemies.
抛射体可以跟踪敌人了。
2024-08-18 16:09:31 +08:00
8c02a0548c
Upgrade to 4.3.
升级到4.3。
2024-08-15 22:58:28 +08:00
5d9643476f
Bullets can now track enemies.
子弹可以跟踪敌人了。
2024-08-14 23:08:07 +08:00
451d6d0849
Add bullet arc and number of bullets generated.
加入子弹弧度和产生子弹的数量
2024-08-13 23:07:05 +08:00
3b2ba7440c
Improved the collision logic of bullets.
改进子弹的碰撞逻辑。
2024-08-12 22:30:13 +08:00
ef58f3885b
The projectile weapon allows the projectile queue to be fired sequentially.
抛射体武器,允许循序发射抛射体队列了。
2024-08-11 23:29:23 +08:00
05ac2dec60
Join Mr. Raven.
加入乌鸦先生。
2024-08-11 00:25:00 +08:00
e10e22e3bd
Update Readme.
更新Readme。
2024-08-10 22:01:49 +08:00
56d3ae4964
Restore the database.
恢复数据库。
2024-08-06 00:27:51 +08:00
cceda0bd1f
Remove the GetMeta method.
移除GetMeta方法。
2024-08-05 22:52:20 +08:00
4459b6a88b
Added the game difficulty configuration class and added the ability to generate scenes after projectiles kill enemies.
加入游戏难度的配置类,加入抛射体杀死敌人后生成场景的功能。
2024-08-04 00:32:49 +08:00
bac2ae1992
Modifies the return value of the damage Api to whether fatal damage has been inflicted. Add the projectile decorator.
修改伤害Api的返回值为是否造成了致命伤害。加入抛射体装饰器。
2024-08-02 23:57:11 +08:00
e54b62cb82
Item ids can only be specified in yaml files.
物品的id只能在yaml文件内指定了。
2024-08-01 23:30:28 +08:00
074f11e971
Ignore the.vscode directory.
忽略.vscode目录。
2024-07-31 22:52:29 +08:00
219f207472
Remove custom parameters in yaml. Rename the resource file.
移除在yaml内的自定义参数。重命名资源文件。
2024-07-31 22:14:40 +08:00
89 changed files with 2065 additions and 753 deletions

3
.gitignore vendored
View File

@ -1,8 +1,9 @@
# Godot 4+ specific ignores
.godot/
export_presets.cfg
.idea/
.vs/
.vscode/
export_presets.cfg
*.translation
*.user
AppConfig.yaml

View File

@ -1,4 +1,4 @@
<Project Sdk="Godot.NET.Sdk/4.3.0-rc.1">
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
@ -11,5 +11,7 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="YamlDotNet" Version="15.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.29" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ A pixel cross-platform roguelite game.
## Recent Development progress
| Task | status |
| ----------------------------------------------------------- | ------------------ |
|------------------------------------------|-------------|
| Randomly generated map | complete |
| loot | complete |
| Support still out of the knapsack system | complete |
@ -30,12 +30,20 @@ Level graph editor
## Run the project locally
#### Download engine
1. Download [Godot Engine .Net](https://godotengine.org/).
After downloading the engine, you will need to download an additional export template to export as an executable program.
After downloading the engine, you will need to download an additional export template to export as an executable
program.
2. Download [.NetSDK](https://dotnet.microsoft.com/download).
Ubuntu or Linux Mint install the .net 8.0 Sdk.
```
apt install dotnet-sdk-8.0
```
#### Clone project
Enter the following command in your working directory:
@ -55,7 +63,17 @@ data/*
#### Custom feature
- **disableVersionIsolation** Disable version isolation.
- **enableMod** Experimental feature, the game loads dll files and pck files in the mod directory when the mod is enabled. Due to the isolation of AssemblyLoadContext, the main game content cannot be accessed from within the Mod for the time being.
- **enableMod** Experimental feature, the game loads dll files and pck files in the mod directory when the mod is
enabled. Due to the isolation of AssemblyLoadContext, the main game content cannot be accessed from within the Mod for
the time being.
#### Run the console on Linux
Enter the following command in the directory where the game is located:
```
./Traveler.sh
```
## Configuring Openobserve
@ -87,13 +105,15 @@ After you have set up an openobserve server, follow the following steps to confi
## Participate in translation
The project is prepared for localization at the beginning of writing. You can edit the csv file in the locals directory. To modify and add new translations.
The project is prepared for localization at the beginning of writing. You can edit the csv file in the locals directory.
To modify and add new translations.
## License
[GPL-3.0 license](LICENSE)
Support commercial, anyone can modify, build, and sell or distribute for free. For all derivative versions of this project, under the GPL, you shall **retain the author copyright** and **publish the modified source code**.
Support commercial, anyone can modify, build, and sell or distribute for free. For all derivative versions of this
project, under the GPL, you shall **retain the author copyright** and **publish the modified source code**.
> Note: You have the right to sell the modified version, but not the original.
>

View File

@ -34,6 +34,13 @@
2. ダウンロード [.NetSDK](https://dotnet.microsoft.com/download).
UbuntuやLinux Mintに。net8.0Sdkをインストールします。
```
apt install dotnet-sdk-8.0
```
#### クローンプロジェクトです
作業リストに次の指示を入力します。
@ -55,6 +62,14 @@ data/*
- **disableVersionIsolation** 版孤立を無効化する。
- **enableMod**実験的な機能、modが有効になっている場合、ゲームはmodディレクトリにdllファイルとpckファイルをロードします。assemblyloadcontextが分離されているため、当面の間、mod内からメインゲームのコンテンツにアクセスすることはできません。
#### Linux上でコンソールを動かします
ゲームの存在するディレクトリに以下のコマンドを入力します:
```
./Traveler.sh
```
## はいちOpenobserve
> これはオプションなので、Openobserveを設定しなくてもゲームは正常に動作します。

View File

@ -11,7 +11,7 @@
## 近期研发进度
| 任务 | 状态 |
| ----------------------------------------------------------- |----|
|-----------|-----|
| 随机生成地图 | 完成 |
| 战利品 | 完成 |
| 支持仍出的背包系统 | 完成 |
@ -30,12 +30,19 @@
## 在本地运行项目
#### 下载引擎
1. 下载[Godot Engine .Net](https://godotengine.org/)。
下载引擎后,您需要额外下载导出模板才能导出为可执行程序。
2. 下载 [.NetSDK](https://dotnet.microsoft.com/download).
Ubuntu或Linux Mint安装.net8.0Sdk。
```
apt install dotnet-sdk-8.0
```
#### 克隆项目
在您的工作目录输入以下指令:
@ -57,6 +64,14 @@ data/*
- **disableVersionIsolation** 禁用版本隔离。
- **enableMod** 实验性功能当启用模组时游戏会在mod目录加载dll文件和pck文件。由于AssemblyLoadContext的隔离性暂时不能从Mod内访问主游戏内容。
#### 在Linux上运行控制台
在游戏所在目录输入以下命令:
```
./Traveler.sh
```
## 配置Openobserve
> 这是可选的操作即使您不配置Openobserve游戏也能正常运行。
@ -95,7 +110,8 @@ openobserve用于在游戏发布后持续收集日志和报警信息。
查看协议的中文翻译版本:[GPL-3.0 license 简体中文](LICENSE_ZH)
支持商用任何人可修改构建并用于售卖或免费发布。对于此项目的所有衍生版本根据GPL协议您应当**保留作者版权**,以及**公开修改后的源代码**。
支持商用任何人可修改构建并用于售卖或免费发布。对于此项目的所有衍生版本根据GPL协议您应当**保留作者版权**,以及*
*公开修改后的源代码**。
> 注意:您有权售卖修改后的版本,但不能售卖原版。

View File

@ -1,4 +1,13 @@
- id: packsack
scene_path: res://prefab/packsacks/packsack.tscn
icon_path: res://sprites/packsack.png
#Register an item container here.
#在这里注册物品容器。
#Note: The id must be the same as the item id in the scene. Otherwise, an ArgumentException will be thrown.
#备注id必须和场景内的物品id保持一致。否则会抛出ArgumentException。
#After you declare the id of the item, add the corresponding localized text to the csv file in the locals' folder. For example, if the id is a corresponding name is item_a and the corresponding description is item_a_desc.
#当您声明物品的id后请在locals文件夹中的csv文件中添加相应的本地化文本。例如id为a则对应的名称为item_a对应的描述为item_a_desc。
#portable_backpacks
#便携式背包
- id: portable_backpacks
scene_path: res://prefab/packsacks/PortableBackpacks.tscn
icon_path: res://sprites/PortableBackpacks.png
max_stack_value: 1

View File

@ -1,20 +1,17 @@
- id: 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: UniqueName
type: string
value: 爱你!
#Register your weapon here.
#在这里注册你的武器。
#Note: The id must be the same as the item id in the scene. Otherwise, an ArgumentException will be thrown.
#备注id必须和场景内的物品id保持一致。否则会抛出ArgumentException。
#After you declare the id of the item, add the corresponding localized text to the csv file in the locals' folder. For example, if the id is a corresponding name is item_a and the corresponding description is item_a_desc.
#当您声明物品的id后请在locals文件夹中的csv文件中添加相应的本地化文本。例如id为a则对应的名称为item_a对应的描述为item_a_desc。
- id: degraded_staff_of_the_undead
scene_path: res://prefab/weapons/staffOfTheUndead.tscn
icon_path: res://sprites/weapon/staffOfTheUndead_icon.png
#staff_necromancy
#死灵法杖
# The Necromancy Staff will act differently on different difficulties.
#死灵法杖在不同的难度下会有不同的行为。
#The probability of generating monsters is as follows:Hard mode 100%, Normal mode 75%, easy mode 5%
#以下是在击杀敌人后生成怪物的概率困难模式100%普通模式75%简单模式5%
- id: staff_necromancy
scene_path: res://prefab/weapons/StaffNecromancy.tscn
icon_path: res://sprites/weapon/StaffNecromancy_Icon.png
max_stack_value: 1
custom_args:
- name: FiringIntervalAsMillisecond
type: int
value: 1000
- name: UniqueName
type: string
value: item_staff_of_the_undead_desc

View File

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/ark-pixel-12px-proportional-zh_cn.ttf-478ad47
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

3
locals/Character.csv Normal file
View File

@ -0,0 +1,3 @@
id,zh,en,ja
character_necromancer,死灵法师,Necromancer,ネクロマンサー
character_evil_crow,邪恶的乌鸦先生,Evil Mr. Raven,悪のミスター・レイヴン
1 id zh en ja
2 character_necromancer 死灵法师 Necromancer ネクロマンサー
3 character_evil_crow 邪恶的乌鸦先生 Evil Mr. Raven 悪のミスター・レイヴン

View File

@ -0,0 +1,17 @@
[remap]
importer="csv_translation"
type="Translation"
uid="uid://dwrygxaehoybb"
[deps]
files=["res://locals/Character.zh.translation", "res://locals/Character.en.translation", "res://locals/Character.ja.translation"]
source_file="res://locals/Character.csv"
dest_files=["res://locals/Character.zh.translation", "res://locals/Character.en.translation", "res://locals/Character.ja.translation"]
[params]
compress=true
delimiter=0

View File

@ -1,5 +1,5 @@
id,zh,en,ja
item_staff_of_the_undead,死灵法杖,StaffOfTheUndead,ネクロポリスの杖です
item_staff_of_the_undead_desc,发射诅咒,可将敌人转化为邪恶的怪物。,Cast a curse that transforms enemies into evil monsters.,呪いを発射して、敵を邪悪な怪物に変えることができます。
item_packsack,背包,Packsack,背嚢
item_packsack_desc,可以装载更多物品的背包。,A backpack that can hold more items.,より多くのアイテムを保持できるバックパックです。
item_staff_necromancy,死灵法杖,staffNecromancy,ネクロポリスの杖です
item_staff_necromancy_desc,发射诅咒,可将敌人转化为邪恶的怪物。,Cast a curse that transforms enemies into evil monsters.,呪いを発射して、敵を邪悪な怪物に変えることができます。
item_portable_backpacks,便携式背包,PortableBackpacks,ポータブルバックパック
item_portable_backpacks_desc,为玩家提供9个物品槽。,Provides 9 item slots for the player.,プレイヤーに9つのアイテムスロットを提供します。
1 id zh en ja
2 item_staff_of_the_undead item_staff_necromancy 死灵法杖 StaffOfTheUndead staffNecromancy ネクロポリスの杖です
3 item_staff_of_the_undead_desc item_staff_necromancy_desc 发射诅咒,可将敌人转化为邪恶的怪物。 Cast a curse that transforms enemies into evil monsters. 呪いを発射して、敵を邪悪な怪物に変えることができます。
4 item_packsack item_portable_backpacks 背包 便携式背包 Packsack PortableBackpacks 背嚢 ポータブルバックパック
5 item_packsack_desc item_portable_backpacks_desc 可以装载更多物品的背包。 为玩家提供9个物品槽。 A backpack that can hold more items. Provides 9 item slots for the player. より多くのアイテムを保持できるバックパックです。 プレイヤーに9つのアイテムスロットを提供します。

View File

@ -110,3 +110,11 @@ log_dll_does_not_register_lifecycle_processor,位于{0}的dll文件未注册
log_mod_lifecycle_handler_not_implement_interface,位于{0}的dll文件生命周期处理器未实现IModLifecycleHandler接口。,"The DLL file located at {0}, the lifecycle processor does not implement the IModLifecycleHandler interface.",{0}にあるDLLファイル、ライフサイクルプロセッサはIModLifecycleHandlerインターフェースを実装していません。
log_dll_no_parameterless_constructor,位于{0}的dll文件没有无参构造函数。,"The DLL file located at {0} does not have a parameterless constructor.",{0}にあるDLLファイルにはパラメータのないコンストラクタがありません。
log_dll_type_length,位于{0}的dll文件内共包含{1}个类型。,"The DLL file located at {0} contains a total of {1} types.",{0}にあるDLLファイルには合計{1}個のタイプが含まれています。
log_item_pickup_disables_collision_damage,物品捡起禁用碰撞伤害。,Item pickup disables collision damage.,アイテムのピックアップが衝突によるダメージを無効にする
log_item_thrown_restore_collision_damage,物品扔出恢复碰撞伤害。,Item Throw restores collision damage.,アイテム投げは衝突ダメージを回復する。
log_after_no_longer_in_contact_with_any_tiles,不再与任何瓦片接触后,可以造成伤害。,"After no longer coming into contact with any tiles, it can cause damage",タイルと接触しなくなった後、損傷を引き起こす可能性があります。
log_contact_with_tiles_disables_damage,与瓦片接触禁用伤害。,"Disabling damage on contact with tiles",タイルとの接触によるダメージの無効化
log_hide_all_node,隐藏{0}个节点。,Hide {0} nodes.,{0}ノードを非表示にします。
log_show_all_node,显示{0}个节点。,Show {0} nodes.,{0}ノードが表示されます。
log_enter_the_screen,进入屏幕。,Enter screen,画面に移動。
log_exit_the_screen,退出屏幕。,Exit screen,画面を終了します。
1 id zh en ja
110 log_mod_lifecycle_handler_not_implement_interface 位于{0}的dll文件,生命周期处理器未实现IModLifecycleHandler接口。 The DLL file located at {0}, the lifecycle processor does not implement the IModLifecycleHandler interface. {0}にあるDLLファイル、ライフサイクルプロセッサはIModLifecycleHandlerインターフェースを実装していません。
111 log_dll_no_parameterless_constructor 位于{0}的dll文件,没有无参构造函数。 The DLL file located at {0} does not have a parameterless constructor. {0}にあるDLLファイルにはパラメータのないコンストラクタがありません。
112 log_dll_type_length 位于{0}的dll文件内,共包含{1}个类型。 The DLL file located at {0} contains a total of {1} types. {0}にあるDLLファイルには合計{1}個のタイプが含まれています。
113 log_item_pickup_disables_collision_damage 物品捡起禁用碰撞伤害。 Item pickup disables collision damage. アイテムのピックアップが衝突によるダメージを無効にする
114 log_item_thrown_restore_collision_damage 物品扔出恢复碰撞伤害。 Item Throw restores collision damage. アイテム投げは衝突ダメージを回復する。
115 log_after_no_longer_in_contact_with_any_tiles 不再与任何瓦片接触后,可以造成伤害。 After no longer coming into contact with any tiles, it can cause damage タイルと接触しなくなった後、損傷を引き起こす可能性があります。
116 log_contact_with_tiles_disables_damage 与瓦片接触禁用伤害。 Disabling damage on contact with tiles タイルとの接触によるダメージの無効化
117 log_hide_all_node 隐藏{0}个节点。 Hide {0} nodes. {0}ノードを非表示にします。
118 log_show_all_node 显示{0}个节点。 Show {0} nodes. {0}ノードが表示されます。
119 log_enter_the_screen 进入屏幕。 Enter screen 画面に移動。
120 log_exit_the_screen 退出屏幕。 Exit screen 画面を終了します。

View File

@ -1,4 +1,3 @@
id,zh,en,ja
de,的,"'s",の
default_player_name,白纸,blankPaper,しらかみ
item_prompt_debug,ID{0}\n名称{1}\n数量{2}\n最大叠加数量{3}\n数据类型{4}\n描述{5},ID: {0}\nName: {1}\nQuantity: {2}\nMaximum stacking quantity: {3}\nData type: {4}\nDescription{5},id:{0}\n名称:{1}\nの数は最大{2}\nシナジー数:{3}\nデータタイプ:{4}\n述べ表わす:{5}
1 id zh en ja
de 's
2 default_player_name 白纸 blankPaper しらかみ
3 item_prompt_debug ID:{0}\n名称:{1}\n数量:{2}\n最大叠加数量:{3}\n数据类型:{4}\n描述:{5} ID: {0}\nName: {1}\nQuantity: {2}\nMaximum stacking quantity: {3}\nData type: {4}\nDescription:{5} id:{0}\n名称:{1}\nの数は最大{2}\nシナジー数:{3}\nデータタイプ:{4}\n述べ表わす:{5}

17
locals/Slogan.csv.import Normal file
View File

@ -0,0 +1,17 @@
[remap]
importer="csv_translation"
type="Translation"
uid="uid://m1i5vqn3aq7e"
[deps]
files=["res://locals/Slogan.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation"]
source_file="res://locals/Slogan.csv"
dest_files=["res://locals/Slogan.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation"]
[params]
compress=true
delimiter=0

3
prefab/MainCamera.tscn Normal file
View File

@ -0,0 +1,3 @@
[gd_scene format=3 uid="uid://briehpw3eg1i6"]
[node name="Camera2D" type="Camera2D"]

View File

@ -0,0 +1,103 @@
[gd_scene load_steps=11 format=3 uid="uid://dxx1vqpecig7i"]
[ext_resource type="Script" path="res://scripts/character/AiCharacter.cs" id="1_ubaid"]
[ext_resource type="Texture2D" uid="uid://c1yxhtiqrfvvp" path="res://sprites/character/BlackenedAboriginalWarrior.png" id="2_pcmq1"]
[ext_resource type="Script" path="res://scripts/damage/DamageNumberNodeSpawn.cs" id="3_kiam3"]
[ext_resource type="PackedScene" uid="uid://sqqfrmikmk5v" path="res://prefab/ui/HealthBar.tscn" id="4_gt388"]
[ext_resource type="Script" path="res://scripts/bubble/BubbleMarker.cs" id="5_y2fh5"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_f8frw"]
size = Vector2(40, 46)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_a3myh"]
size = Vector2(46, 65)
[sub_resource type="SpriteFrames" id="SpriteFrames_qumby"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2_pcmq1")
}],
"loop": true,
"name": &"default",
"speed": 5.0
}]
[sub_resource type="CircleShape2D" id="CircleShape2D_c61vr"]
radius = 153.0
[sub_resource type="CircleShape2D" id="CircleShape2D_fowd5"]
radius = 172.29
[node name="BlackenedAboriginalWarrior" type="CharacterBody2D"]
collision_layer = 64
collision_mask = 38
script = ExtResource("1_ubaid")
CharacterName = "character_evil_crow"
CanMutateAfterDeath = false
MaxHp = 50
CampId = "Mazoku"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(2, 10)
shape = SubResource("RectangleShape2D_f8frw")
[node name="Area2DPickingArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickingArea"]
position = Vector2(0, 5.5)
shape = SubResource("RectangleShape2D_a3myh")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
sprite_frames = SubResource("SpriteFrames_qumby")
[node name="ItemMarker2D" type="Marker2D" parent="."]
position = Vector2(15, 20)
[node name="AttackObstacleDetection" type="RayCast2D" parent="ItemMarker2D"]
collision_mask = 2
[node name="DamageNumber" type="Marker2D" parent="."]
position = Vector2(0, -32)
script = ExtResource("3_kiam3")
[node name="HealthBar" parent="." instance=ExtResource("4_gt388")]
visible = false
offset_left = -46.0
offset_top = 41.0
offset_right = 50.0
offset_bottom = 53.0
[node name="WallDetection" type="RayCast2D" parent="."]
position = Vector2(3, -1)
target_position = Vector2(50, 0)
collision_mask = 6
[node name="AttackArea2D" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 68
[node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea2D"]
shape = SubResource("CircleShape2D_c61vr")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
[node name="BubbleMarker" type="Marker2D" parent="."]
position = Vector2(0, -79)
script = ExtResource("5_y2fh5")
[node name="ScoutArea2D" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 76
[node name="CollisionShape2D" type="CollisionShape2D" parent="ScoutArea2D"]
shape = SubResource("CircleShape2D_fowd5")
[node name="TipLabel" type="Label" parent="."]
offset_left = -20.0
offset_top = 46.0
offset_right = 15.0
offset_bottom = 71.0
horizontal_alignment = 1

View File

@ -23,19 +23,19 @@ animations = [{
}]
[node name="Player" type="CharacterBody2D"]
light_mask = 2
collision_layer = 4
collision_mask = 34
collision_mask = 162
platform_floor_layers = 4294967042
platform_wall_layers = 128
script = ExtResource("1_1dlls")
metadata/CampId = "Default"
metadata/MaxHp = 32
MaxHp = 32
CampId = "Default"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CapsuleShape2D_bb8wt")
debug_color = Color(0.886275, 0, 0.803922, 0.419608)
[node name="Camera2D" type="Camera2D" parent="."]
position_smoothing_enabled = true
[node name="Area2DPickingArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 8

View File

@ -34,18 +34,17 @@ radius = 172.29
collision_layer = 64
collision_mask = 38
script = ExtResource("1_ubaid")
InitWeaponRes = null
CharacterName = "character_necromancer"
MaxHp = 50
CampId = "Mazoku"
LootListId = "test"
metadata/CampId = "Mazoku"
metadata/MaxHp = 50
metadata/Name = "死灵法师"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4)
shape = SubResource("CapsuleShape2D_bb8wt")
[node name="Area2DPickingArea" type="Area2D" parent="."]
collision_layer = 0
collision_layer = 64
collision_mask = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickingArea"]
@ -90,13 +89,20 @@ shape = SubResource("CircleShape2D_c61vr")
position = Vector2(0, -79)
script = ExtResource("5_y2fh5")
[node name="VisibleOnScreenEnabler2D" type="VisibleOnScreenEnabler2D" parent="."]
position = Vector2(0, 5.5)
scale = Vector2(2.04, 3.05)
[node name="ScoutArea2D" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 76
[node name="CollisionShape2D" type="CollisionShape2D" parent="ScoutArea2D"]
shape = SubResource("CircleShape2D_fowd5")
[node name="TipLabel" type="Label" parent="."]
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -57.0
offset_top = 57.0
offset_right = 61.0
offset_bottom = 82.0
grow_horizontal = 2
horizontal_alignment = 1

View File

@ -1,7 +1,7 @@
[gd_scene load_steps=5 format=3 uid="uid://cn10fimoem04m"]
[gd_scene load_steps=5 format=3 uid="uid://bq5d2w22wnxrf"]
[ext_resource type="Script" path="res://scripts/inventory/Packsack.cs" id="1_slakl"]
[ext_resource type="Texture2D" uid="uid://dvx10dfjctn7t" path="res://sprites/packsack.png" id="2_40jca"]
[ext_resource type="Texture2D" uid="uid://di0sw4rgd26y0" path="res://sprites/PortableBackpacks.png" id="2_l0fbh"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_brthl"]
size = Vector2(21, 29)
@ -14,7 +14,6 @@ collision_layer = 8
collision_mask = 38
script = ExtResource("1_slakl")
NumberSlots = 30
Id = "packsack"
[node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8
@ -30,4 +29,9 @@ shape = SubResource("RectangleShape2D_xqyue")
[node name="Packsack" type="Sprite2D" parent="."]
scale = Vector2(0.5, 0.5)
texture = ExtResource("2_40jca")
texture = ExtResource("2_l0fbh")
[node name="TipLabel" type="Label" parent="."]
offset_top = 31.0
offset_right = 40.0
offset_bottom = 56.0

View File

@ -0,0 +1,36 @@
[gd_scene load_steps=5 format=3 uid="uid://bdxgx5vcof8em"]
[ext_resource type="Script" path="res://scripts/projectile/Projectile.cs" id="1_ib3qh"]
[ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_dxg46"]
[sub_resource type="CircleShape2D" id="CircleShape2D_dgro2"]
[sub_resource type="CircleShape2D" id="CircleShape2D_8117d"]
radius = 11.0
[node name="curseOfTheUndead" type="CharacterBody2D"]
collision_layer = 16
collision_mask = 102
slide_on_ceiling = false
platform_floor_layers = 4294967042
platform_wall_layers = 32
script = ExtResource("1_ib3qh")
Speed = 300.0
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_dgro2")
[node name="CollisionDetectionArea" type="Area2D" parent="."]
collision_layer = 16
collision_mask = 78
[node name="CollisionShape2D" type="CollisionShape2D" parent="CollisionDetectionArea"]
shape = SubResource("CircleShape2D_8117d")
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
bus = &"SoundEffect"
area_mask = 16
[node name="Player" type="Sprite2D" parent="."]
scale = Vector2(0.3, 0.3)
texture = ExtResource("2_dxg46")

View File

@ -1,22 +1,23 @@
[gd_scene load_steps=5 format=3 uid="uid://c01av43yk1q71"]
[gd_scene load_steps=4 format=3 uid="uid://c01av43yk1q71"]
[ext_resource type="Script" path="res://scripts/projectile/Projectile.cs" id="1_ib3qh"]
[ext_resource type="Texture2D" uid="uid://bbcjkyrsx88av" path="res://sprites/projectile/curseOfTheUndead.png" id="1_k8el6"]
[sub_resource type="CircleShape2D" id="CircleShape2D_dgro2"]
[sub_resource type="CircleShape2D" id="CircleShape2D_8117d"]
radius = 11.0
[node name="curseOfTheUndead" type="CharacterBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_ib3qh")
metadata/Speed = 500.0
metadata/Durability = 1.0
metadata/DamageType = 2
metadata/Knockback = Vector2(2, -3)
metadata/Life = 5000
_life = 5000
_durability = 1.0
_maxDamage = 10
_minDamage = 1
_damageType = 2
Speed = 500.0
_enableTracking = true
_targetDiesDestroyProjectile = true
_repelStrength = 2.5
[node name="CurseOfTheUndead" type="Sprite2D" parent="."]
texture = ExtResource("1_k8el6")
@ -24,13 +25,6 @@ texture = ExtResource("1_k8el6")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_dgro2")
[node name="CollisionDetectionArea" type="Area2D" parent="."]
collision_layer = 16
collision_mask = 78
[node name="CollisionShape2D" type="CollisionShape2D" parent="CollisionDetectionArea"]
shape = SubResource("CircleShape2D_8117d")
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
bus = &"SoundEffect"
area_mask = 16

View File

@ -1,10 +1,11 @@
[gd_scene load_steps=8 format=4 uid="uid://b0uurp551pku"]
[gd_scene load_steps=9 format=4 uid="uid://b0uurp551pku"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_a15hy"]
[ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_wamhd"]
[ext_resource type="Texture2D" uid="uid://drw45jlmfo0su" path="res://sprites/light/White_100.png" id="3_0mlbb"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(443, 118)
size = Vector2(505, 186)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_o85u0"]
size = Vector2(20, 48)
@ -16,8 +17,8 @@ size = Vector2(20, 46)
size = Vector2(53, 24)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_rh1gx"]
vertices = PackedVector2Array(501, 150, 9, 151, 11, 107, 43, 107, 170, 41, 169, 11, 217, 11, 219, 42, 42, 44, 470, 107, 499, 106, 469, 43)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 5, 6, 7), PackedInt32Array(0, 3, 8, 4, 7, 9), PackedInt32Array(9, 10, 0), PackedInt32Array(9, 7, 11)])
vertices = PackedVector2Array(499.469, 106.328, 501.523, 150.039, 9.3125, 151.953, 470.156, 107.352, 10.7031, 107, 43.1562, 107, 170.328, 40.6797, 169.344, 11, 216.617, 11, 218.617, 41.9297, 469.148, 43.9219, 42.1484, 44.6875)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(3, 2, 4, 5), PackedInt32Array(6, 7, 8, 9), PackedInt32Array(10, 3, 5, 9), PackedInt32Array(6, 9, 5, 11)])
outlines = Array[PackedVector2Array]([PackedVector2Array(479, 34, 228, 32, 226, 1, 159, 1, 160, 31, 32, 35, 33, 97, 1, 97, -1, 162, 512, 160, 509, 96, 480, 97)])
source_geometry_group_name = &"navigation_polygon_source_group"
@ -27,7 +28,7 @@ source_geometry_group_name = &"navigation_polygon_source_group"
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(256.5, 95)
position = Vector2(255.5, 97)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
@ -53,7 +54,7 @@ shape = SubResource("RectangleShape2D_7tsse")
[node name="Marker2D" type="Marker2D" parent="."]
position = Vector2(260, 87)
script = ExtResource("2_wamhd")
metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_rh1gx")
@ -74,3 +75,9 @@ tile_set = ExtResource("1_a15hy")
use_parent_material = true
tile_map_data = PackedByteArray("AAAAAAAAAQAAAAMAAAAAAAEAAQACAAEAAAAAAAIAAQACAAEAAAABAAAAAQABAAIAAAACAAAAAQABAAIAAAADAAAAAQABAAIAAAAEAAAAAQABAAIAAAAHAAAAAQABAAIAAAAIAAAAAQABAAIAAAAJAAAAAQABAAIAAAAKAAAAAQABAAIAAAALAAAAAQABAAIAAAAMAAAAAQABAAIAAAANAAAAAQABAAIAAAAOAAAAAQABAAMAAAAPAAAAAQACAAMAAAAPAAEAAQACAAQAAAAPAAIAAQACAAQAAAABAAUAAQABAAAAAAACAAUAAQABAAAAAAADAAUAAQABAAAAAAAEAAUAAQABAAAAAAAFAAUAAQABAAAAAAAGAAUAAQABAAAAAAAHAAUAAQABAAAAAAAIAAUAAQABAAAAAAAJAAUAAQABAAAAAAAKAAUAAQABAAAAAAALAAUAAQABAAAAAAAMAAUAAQABAAAAAAANAAUAAQABAAAAAAAOAAUAAQABAAAAAAAAAAUAAQAAAAUAAAAPAAUAAQACAAUAAAAFAAAAAQAEAAQAAAAGAAAAAQAEAAQAAAA=")
tile_set = ExtResource("1_a15hy")
[node name="PointLight2D" type="PointLight2D" parent="."]
visible = false
position = Vector2(256, 96.5)
scale = Vector2(15.875, 5.96875)
texture = ExtResource("3_0mlbb")

View File

@ -1,10 +1,11 @@
[gd_scene load_steps=8 format=4 uid="uid://dslr5tdbp4noq"]
[gd_scene load_steps=9 format=4 uid="uid://dslr5tdbp4noq"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_7q101"]
[ext_resource type="Texture2D" uid="uid://drw45jlmfo0su" path="res://sprites/light/White_100.png" id="3_m3aye"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(441, 122)
size = Vector2(505, 188)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_o85u0"]
size = Vector2(20, 48)
@ -16,8 +17,8 @@ size = Vector2(46, 20)
size = Vector2(20, 54)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_db40i"]
vertices = PackedVector2Array(499, 108, 501, 150, 246, 149, 471, 108, 12, 149, 11, 107, 41, 107, 203, 149, 245, 180, 203, 178, 43, 43, 470, 44)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 5, 6, 7), PackedInt32Array(2, 8, 9, 7), PackedInt32Array(10, 11, 3, 2, 7, 6)])
vertices = PackedVector2Array(499.461, 108, 501.5, 150.906, 246.312, 148.922, 471.156, 108, 245.312, 180.508, 203, 178.469, 203, 149, 11.8438, 149, 11.1562, 107.656, 40.5625, 106.688, 43.5391, 43.0391, 470.148, 44.9531)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(2, 4, 5, 6), PackedInt32Array(7, 8, 9, 6), PackedInt32Array(10, 11, 3, 2, 6, 9)])
outlines = Array[PackedVector2Array]([PackedVector2Array(34, 33, 31, 97, 1, 98, 2, 159, 193, 159, 193, 188, 255, 191, 256, 159, 512, 161, 509, 98, 481, 98, 480, 35)])
source_geometry_group_name = &"navigation_polygon_source_group"
@ -27,7 +28,7 @@ source_geometry_group_name = &"navigation_polygon_source_group"
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(255.5, 97)
position = Vector2(256.5, 95)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
@ -75,3 +76,9 @@ tile_set = ExtResource("1_rn2om")
use_parent_material = true
tile_map_data = PackedByteArray("AAAAAAAAAQAAAAMAAAAAAAEAAQACAAEAAAAAAAIAAQACAAEAAAABAAAAAQABAAIAAAACAAAAAQABAAIAAAADAAAAAQABAAIAAAAEAAAAAQABAAIAAAAFAAAAAQABAAIAAAAGAAAAAQABAAIAAAAHAAAAAQABAAIAAAAIAAAAAQABAAIAAAAJAAAAAQABAAIAAAAKAAAAAQABAAIAAAALAAAAAQABAAIAAAAMAAAAAQABAAIAAAANAAAAAQABAAIAAAAOAAAAAQABAAMAAAAPAAAAAQACAAMAAAAPAAEAAQACAAQAAAAPAAIAAQACAAQAAAABAAUAAQABAAAAAAACAAUAAQABAAAAAAADAAUAAQABAAAAAAAEAAUAAQABAAAAAAAFAAUAAQABAAAAAAAIAAUAAQABAAAAAAAJAAUAAQABAAAAAAAKAAUAAQABAAAAAAALAAUAAQABAAAAAAAMAAUAAQABAAAAAAANAAUAAQABAAAAAAAOAAUAAQABAAAAAAAAAAUAAQAAAAUAAAAPAAUAAQACAAUAAAAGAAUAAQAEAAQAAAAHAAUAAQAEAAQAAAA=")
tile_set = ExtResource("1_rn2om")
[node name="PointLight2D" type="PointLight2D" parent="."]
visible = false
position = Vector2(256.5, 94)
scale = Vector2(15.9688, 5.9375)
texture = ExtResource("3_m3aye")

View File

@ -3,18 +3,18 @@
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Script" path="res://scripts/map/PlayerSpawn.cs" id="2_6p8mv"]
[ext_resource type="Script" path="res://scripts/map/ItemSpawn.cs" id="3_v1tlc"]
[ext_resource type="Texture2D" uid="uid://b2blj0yf4ohx3" path="res://icon.svg" id="4_psvpu"]
[ext_resource type="Texture2D" uid="uid://drw45jlmfo0su" path="res://sprites/light/White_100.png" id="5_4pssd"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(450, 191)
size = Vector2(507, 251)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jxmys"]
size = Vector2(18, 57.75)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_064c7"]
vertices = PackedVector2Array(468, 174, 500, 174, 499, 214, 41, 214, 45, 45, 468, 45)
vertices = PackedVector2Array(467.773, 174, 499.828, 174, 499.156, 214.969, 40.9375, 214.023, 40.0469, 41.0625, 470.758, 43.9297)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(0, 3, 4, 5)])
outlines = Array[PackedVector2Array]([PackedVector2Array(35, 35, 31, 224, 509, 225, 510, 164, 478, 164, 478, 35)])
outlines = Array[PackedVector2Array]([PackedVector2Array(30, 31, 31, 224, 509, 225, 510, 164, 478, 164, 481, 34)])
source_geometry_group_name = &"navigation_polygon_source_group"
[node name="InitialRoom" type="Node2D"]
@ -22,7 +22,7 @@ source_geometry_group_name = &"navigation_polygon_source_group"
[node name="RoomArea" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(253, 130.5)
position = Vector2(254.5, 129.5)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
@ -42,11 +42,7 @@ script = ExtResource("2_6p8mv")
[node name="ItemMarker2D" type="Marker2D" parent="."]
position = Vector2(142, 84)
script = ExtResource("3_v1tlc")
ItemId = "staff_of_the_undead"
[node name="Icon" type="Sprite2D" parent="ItemMarker2D"]
scale = Vector2(0.3, 0.3)
texture = ExtResource("4_psvpu")
ItemId = "staff_necromancy"
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_064c7")
@ -68,3 +64,9 @@ tile_set = ExtResource("1_rn2om")
use_parent_material = true
tile_map_data = PackedByteArray("AAAAAAAAAQAAAAMAAAAAAAEAAQACAAEAAAAAAAIAAQACAAEAAAAAAAMAAQACAAEAAAAAAAQAAQACAAEAAAAAAAcAAQAAAAUAAAAPAAAAAQACAAMAAAAPAAEAAQACAAQAAAAPAAIAAQACAAQAAAAPAAMAAQACAAQAAAAPAAQAAQACAAQAAAAPAAcAAQACAAUAAAAAAAUAAQACAAEAAAAAAAYAAQACAAEAAAABAAAAAQABAAIAAAACAAAAAQABAAIAAAADAAAAAQABAAIAAAAEAAAAAQABAAIAAAAFAAAAAQABAAIAAAAGAAAAAQABAAIAAAAHAAAAAQABAAIAAAAIAAAAAQABAAIAAAAJAAAAAQABAAIAAAAKAAAAAQABAAIAAAALAAAAAQABAAIAAAAMAAAAAQABAAIAAAANAAAAAQABAAIAAAAOAAAAAQABAAMAAAABAAcAAQABAAAAAAACAAcAAQABAAAAAAADAAcAAQABAAAAAAAEAAcAAQABAAAAAAAFAAcAAQABAAAAAAAGAAcAAQABAAAAAAAHAAcAAQABAAAAAAAIAAcAAQABAAAAAAAJAAcAAQABAAAAAAAKAAcAAQABAAAAAAALAAcAAQABAAAAAAAMAAcAAQABAAAAAAANAAcAAQABAAAAAAAOAAcAAQABAAAAAAA=")
tile_set = ExtResource("1_rn2om")
[node name="PointLight2D" type="PointLight2D" parent="."]
visible = false
position = Vector2(259.5, 128.5)
scale = Vector2(16.0312, 7.96875)
texture = ExtResource("5_4pssd")

View File

@ -1,9 +1,10 @@
[gd_scene load_steps=7 format=4 uid="uid://c57cc1tyreybb"]
[gd_scene load_steps=8 format=4 uid="uid://c57cc1tyreybb"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Texture2D" uid="uid://drw45jlmfo0su" path="res://sprites/light/White_100.png" id="2_1ctsj"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(669, 442)
size = Vector2(728, 509)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_o85u0"]
size = Vector2(20, 48)
@ -15,7 +16,7 @@ size = Vector2(22, 46)
size = Vector2(11.875, 51)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_1qloc"]
vertices = PackedVector2Array(696, 425, 720, 425, 722, 467, 12, 470, 10, 426, 46, 424, 691, 84, 45, 48, 721, 44, 723, 80)
vertices = PackedVector2Array(696.148, 425, 719.617, 425, 722.328, 467.047, 12.5391, 470.938, 10.4531, 426.43, 46.0312, 424.461, 690.859, 84.1562, 45.0234, 48.9219, 721.523, 44.0625, 723.5, 80.1875)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3, 4), PackedInt32Array(0, 4, 5), PackedInt32Array(6, 0, 5, 7), PackedInt32Array(6, 7, 8, 9)])
outlines = Array[PackedVector2Array]([PackedVector2Array(35, 39, 36, 415, 0, 417, 3, 481, 733, 477, 729, 415, 706, 415, 701, 93, 734, 89, 731, 34)])
source_geometry_group_name = &"navigation_polygon_source_group"
@ -26,7 +27,7 @@ source_geometry_group_name = &"navigation_polygon_source_group"
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(367.5, 256)
position = Vector2(365, 255.5)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
@ -70,3 +71,9 @@ tile_set = ExtResource("1_rn2om")
use_parent_material = true
tile_map_data = PackedByteArray("AAAAAAAAAQAAAAMAAAAAAAEAAQACAAEAAAAAAAIAAQACAAEAAAAAAAMAAQACAAEAAAAAAAQAAQACAAEAAAAAAAUAAQACAAEAAAAAAAYAAQACAAEAAAABAAAAAQABAAIAAAACAAAAAQABAAIAAAADAAAAAQABAAIAAAAEAAAAAQABAAIAAAAFAAAAAQABAAIAAAAGAAAAAQABAAIAAAAHAAAAAQABAAIAAAAIAAAAAQABAAIAAAAJAAAAAQABAAIAAAAKAAAAAQABAAIAAAALAAAAAQABAAIAAAAMAAAAAQABAAIAAAANAAAAAQABAAIAAAAOAAAAAQABAAIAAAAPAAAAAQABAAIAAAAQAAAAAQABAAIAAAARAAAAAQABAAIAAAASAAAAAQABAAIAAAATAAAAAQABAAIAAAAUAAAAAQABAAIAAAAVAAAAAQABAAIAAAAWAAAAAQACAAMAAAAWAAMAAQAAAAEAAAAWAAQAAQAAAAEAAAAWAAUAAQAAAAEAAAAWAAYAAQAAAAEAAAAWAAcAAQAAAAEAAAAWAAgAAQAAAAEAAAAWAAkAAQAAAAEAAAAWAAoAAQAAAAEAAAAWAAsAAQAAAAEAAAAWAAwAAQAAAAEAAAAAAAcAAQACAAEAAAAAAAgAAQACAAEAAAAAAAkAAQACAAEAAAAAAAoAAQACAAEAAAAAAAsAAQACAAEAAAAAAAwAAQACAAEAAAAVAA8AAQABAAAAAAAUAA8AAQABAAAAAAATAA8AAQABAAAAAAASAA8AAQABAAAAAAARAA8AAQABAAAAAAAQAA8AAQABAAAAAAAPAA8AAQABAAAAAAAOAA8AAQABAAAAAAANAA8AAQABAAAAAAAMAA8AAQABAAAAAAALAA8AAQABAAAAAAAKAA8AAQABAAAAAAAJAA8AAQABAAAAAAAIAA8AAQABAAAAAAAHAA8AAQABAAAAAAAGAA8AAQABAAAAAAAFAA8AAQABAAAAAAAEAA8AAQABAAAAAAADAA8AAQABAAAAAAACAA8AAQABAAAAAAABAA8AAQABAAAAAAAAAA8AAQAAAAUAAAAWAA8AAQACAAUAAAASAAsAAQAEAAQAAAATAAsAAQAEAAQAAAAUAAsAAQAEAAQAAAAVAAsAAQAFAAQAAAABAAsAAQADAAQAAAACAAsAAQAEAAQAAAADAAsAAQAEAAQAAAAEAAsAAQAEAAQAAAAFAAsAAQAEAAQAAAAGAAsAAQAEAAQAAAAHAAsAAQAEAAQAAAAIAAsAAQAEAAQAAAAOAAsAAQAEAAQAAAAPAAsAAQAEAAQAAAAQAAsAAQAEAAQAAAARAAsAAQAEAAQAAAABAAoAAQAEAAMAAAAVAAoAAQAEAAMAAAACAAoAAQAEAAIAAAAUAAoAAQAEAAIAAAANAAQAAQAEAAQAAAANAAwAAQAEAAQAAAAMAA0AAQAEAAQAAAALAA4AAQAEAAQAAAAJAAsAAQAEAAQAAAAGAAoAAQAEAAQAAAAHAAkAAQAEAAQAAAAIAAgAAQAEAAQAAAAJAAcAAQAEAAQAAAAKAAcAAQAEAAQAAAALAAcAAQAEAAQAAAAMAAcAAQAEAAQAAAANAAcAAQAEAAQAAAAOAAcAAQAEAAQAAAAPAAcAAQAEAAQAAAAQAAcAAQAEAAQAAAARAAcAAQAEAAQAAAASAAcAAQAEAAQAAAATAAcAAQAEAAQAAAAUAAcAAQAEAAQAAAAVAAcAAQAEAAQAAAABAAcAAQAEAAQAAAACAAcAAQAEAAQAAAADAAcAAQAEAAQAAAAEAAcAAQAEAAQAAAAFAAcAAQAEAAQAAAALAAYAAQAEAAQAAAAMAAUAAQAEAAQAAAAPAAMAAQAEAAQAAAAQAAMAAQAEAAQAAAARAAMAAQAEAAQAAAASAAMAAQAEAAQAAAATAAMAAQAEAAQAAAAUAAMAAQAEAAQAAAAVAAMAAQAEAAQAAAABAAMAAQAEAAQAAAACAAMAAQAEAAQAAAADAAMAAQAEAAQAAAAEAAMAAQAEAAQAAAAFAAMAAQAEAAQAAAAGAAMAAQAEAAQAAAAHAAMAAQAEAAQAAAAIAAMAAQAEAAQAAAAJAAMAAQAEAAQAAAA=")
tile_set = ExtResource("1_rn2om")
[node name="PointLight2D" type="PointLight2D" parent="."]
visible = false
position = Vector2(367.5, 258)
scale = Vector2(23.0938, 16.25)
texture = ExtResource("2_1ctsj")

View File

@ -1,14 +0,0 @@
[gd_scene format=3 uid="uid://b7se73tsnlpd6"]
[node name="FloatLabel" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Label" type="Label" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0

View File

@ -12,8 +12,3 @@ texture_under = ExtResource("1_sc0v3")
texture_over = ExtResource("2_ay5vh")
texture_progress = ExtResource("2_s0gle")
script = ExtResource("4_84gre")
[node name="Label" type="Label" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=7 format=3 uid="uid://dnnn2xyayiehk"]
[gd_scene load_steps=8 format=3 uid="uid://dnnn2xyayiehk"]
[ext_resource type="Texture2D" uid="uid://e6670ykyq145" path="res://sprites/weapon/staffOfTheUndead.png" id="1_ms3us"]
[ext_resource type="Texture2D" uid="uid://wt50kx6bup51" path="res://sprites/weapon/StaffNecromancy.png" id="1_ms3us"]
[ext_resource type="Script" path="res://scripts/weapon/ProjectileWeapon.cs" id="1_w8hhv"]
[ext_resource type="PackedScene" uid="uid://c01av43yk1q71" path="res://prefab/projectile/curseOfTheUndead.tscn" id="2_34250"]
[ext_resource type="PackedScene" uid="uid://c01av43yk1q71" path="res://prefab/projectile/curseOfTheUndead.tscn" id="2_mwli5"]
[ext_resource type="Texture2D" uid="uid://dg5vwprt66w4j" path="res://sprites/weapon/StaffNecromancy_Icon.png" id="3_31iau"]
[ext_resource type="AudioStream" uid="uid://cak6chjjsu7wo" path="res://sounds/fire.wav" id="4_ffr2k"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_obcq2"]
@ -14,14 +15,18 @@ size = Vector2(49, 5.25)
[node name="StaffOfTheUndead" type="RigidBody2D"]
collision_layer = 8
collision_mask = 34
angular_damp = -1.0
script = ExtResource("1_w8hhv")
ProjectileScenes = [ExtResource("2_34250")]
OffsetAngle = 0.087
ProjectileScenes = [ExtResource("2_mwli5")]
Sequentially = true
FiringIntervalAsMillisecond = 300
Id = "staff_of_the_undead"
_recoilStrength = 5
UniqueIcon = ExtResource("3_31iau")
[node name="DamageArea2D" type="Area2D" parent="."]
collision_layer = 8
collision_mask = 70
collision_mask = 102
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea2D"]
position = Vector2(25.5, 0.5)
@ -41,3 +46,9 @@ position = Vector2(65, 0)
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="Marker2D"]
stream = ExtResource("4_ffr2k")
bus = &"SoundEffect"
[node name="TipLabel" type="Label" parent="."]
offset_left = 6.0
offset_top = 48.0
offset_right = 46.0
offset_bottom = 73.0

View File

@ -76,6 +76,7 @@ ui_down={
pick_up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
]
}
throw={
@ -85,7 +86,8 @@ throw={
}
use_item={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(211, 16),"global_position":Vector2(215, 57),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(206, 22),"global_position":Vector2(215, 68),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null)
]
}
hotbar_1={
@ -135,28 +137,31 @@ hotbar_9={
}
hotbar_next={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":16,"position":Vector2(241, 17),"global_position":Vector2(245, 58),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null)
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":16,"position":Vector2(257, 22),"global_position":Vector2(266, 68),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
]
}
hotbar_previous={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":8,"position":Vector2(169, 17),"global_position":Vector2(173, 58),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null)
]
}
[internationalization]
locale/translations=PackedStringArray("res://locals/DeathInfo.en.translation", "res://locals/DeathInfo.ja.translation", "res://locals/DeathInfo.zh.translation", "res://locals/InputMapping.en.translation", "res://locals/InputMapping.ja.translation", "res://locals/InputMapping.zh.translation", "res://locals/Log.en.translation", "res://locals/Log.ja.translation", "res://locals/Log.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation", "res://locals/Slogan.zh.translation", "res://locals/UI.en.translation", "res://locals/UI.ja.translation", "res://locals/UI.zh.translation", "res://locals/Item.en.translation", "res://locals/Item.ja.translation", "res://locals/Item.zh.translation", "res://locals/Action.en.translation", "res://locals/Action.ja.translation", "res://locals/Action.zh.translation", "res://locals/Misc.en.translation", "res://locals/Misc.ja.translation", "res://locals/Misc.zh.translation")
locale/translations=PackedStringArray("res://locals/DeathInfo.en.translation", "res://locals/DeathInfo.ja.translation", "res://locals/DeathInfo.zh.translation", "res://locals/InputMapping.en.translation", "res://locals/InputMapping.ja.translation", "res://locals/InputMapping.zh.translation", "res://locals/Log.en.translation", "res://locals/Log.ja.translation", "res://locals/Log.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation", "res://locals/Slogan.zh.translation", "res://locals/UI.en.translation", "res://locals/UI.ja.translation", "res://locals/UI.zh.translation", "res://locals/Item.en.translation", "res://locals/Item.ja.translation", "res://locals/Item.zh.translation", "res://locals/Action.en.translation", "res://locals/Action.ja.translation", "res://locals/Action.zh.translation", "res://locals/Misc.en.translation", "res://locals/Misc.ja.translation", "res://locals/Misc.zh.translation", "res://locals/Character.en.translation", "res://locals/Character.ja.translation", "res://locals/Character.zh.translation")
[layer_names]
2d_physics/layer_1="RoomArea"
2d_physics/layer_2="Ground"
2d_physics/layer_2="Floor"
2d_physics/layer_3="Player"
2d_physics/layer_4="PickAbleItem"
2d_physics/layer_5="Projectile"
2d_physics/layer_6="Platform"
2d_physics/layer_7="Mob"
2d_physics/layer_8="Wall"
[physics]

View File

@ -1,10 +1,11 @@
[gd_scene load_steps=7 format=3 uid="uid://bnftvkj2cido7"]
[gd_scene load_steps=8 format=3 uid="uid://bnftvkj2cido7"]
[ext_resource type="Script" path="res://scripts/loader/sceneLoader/GameSceneLoader.cs" id="1_mqdgt"]
[ext_resource type="Texture2D" uid="uid://cs6e0af876ss5" path="res://sprites/ui/HeartEmpty.png" id="2_n1yht"]
[ext_resource type="Script" path="res://scripts/inventory/HotBar.cs" id="2_owrhq"]
[ext_resource type="Script" path="res://scripts/HealthBarUi.cs" id="2_xrm3v"]
[ext_resource type="Script" path="res://scripts/FpsLabel.cs" id="5_dis4v"]
[ext_resource type="PackedScene" uid="uid://c74180dtf7j7a" path="res://scenes/mapContainer.tscn" id="6_ljdj4"]
[ext_resource type="PackedScene" uid="uid://bb188382q7btp" path="res://scenes/gameOverMenu.tscn" id="6_yjmrv"]
[node name="Game" type="Node2D"]
@ -21,6 +22,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/Control"]
layout_mode = 1
@ -34,25 +36,31 @@ offset_right = -20.0
offset_bottom = -20.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
[node name="HealthBarUi" type="HBoxContainer" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
script = ExtResource("2_xrm3v")
[node name="TextureRect3" type="TextureRect" parent="CanvasLayer/Control/VBoxContainer/HealthBarUi"]
layout_mode = 2
mouse_filter = 2
texture = ExtResource("2_n1yht")
[node name="HotBar" type="HBoxContainer" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
script = ExtResource("2_owrhq")
[node name="TextureRect3" type="TextureRect" parent="CanvasLayer/Control/VBoxContainer/HotBar"]
layout_mode = 2
mouse_filter = 2
texture = ExtResource("2_n1yht")
[node name="OperationTip" type="RichTextLabel" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
bbcode_enabled = true
fit_content = true
@ -83,6 +91,9 @@ offset_right = 20.0
offset_bottom = 25.0
grow_horizontal = 2
[node name="MapContainer" parent="CanvasLayer/Control" instance=ExtResource("6_ljdj4")]
layout_mode = 1
[node name="BackpackUIContainer" type="Control" parent="CanvasLayer"]
visible = false
layout_mode = 3
@ -107,3 +118,6 @@ visible = false
[node name="AICharacterContainer" type="Node2D" parent="."]
[node name="PacksackContainer" type="Node2D" parent="."]
[node name="CanvasModulate" type="CanvasModulate" parent="."]
color = Color(0, 0, 0, 1)

View File

@ -9,6 +9,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_vj6du")
[node name="ColorRect" type="ColorRect" parent="."]
@ -18,6 +19,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0.941176, 0.243137, 0.243137, 0.258824)
[node name="CenterContainer" type="CenterContainer" parent="."]
@ -27,12 +29,15 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
layout_mode = 2
mouse_filter = 2
[node name="CenterContainer" type="CenterContainer" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
[node name="GameOverLabel" type="Label" parent="CenterContainer/VBoxContainer/CenterContainer"]
layout_mode = 2
@ -41,10 +46,12 @@ text = "ui_game_over_title"
[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
theme_override_constants/margin_top = 18
[node name="CenterContainer2" type="CenterContainer" parent="CenterContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
mouse_filter = 2
[node name="DeathInfoLabel" type="Label" parent="CenterContainer/VBoxContainer/MarginContainer/CenterContainer2"]
layout_mode = 2
@ -52,6 +59,7 @@ text = "ui_death_info_describe"
[node name="MarginContainer2" type="MarginContainer" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
mouse_filter = 2
theme_override_constants/margin_top = 10
theme_override_constants/margin_bottom = 150

View File

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://ckl23uwxrjat4"]
[ext_resource type="Script" path="res://scripts/loader/uiLoader/LevelGraphEditorLoader.cs" id="1_g1axq"]
[ext_resource type="Script" path="res://scripts/loader/uiLoader/LevelGraphEditorLoader.cs" id="1_qgo7w"]
[node name="LevelGraphEditor" type="Control"]
layout_mode = 3
@ -9,7 +9,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_g1axq")
script = ExtResource("1_qgo7w")
[node name="GraphEdit" type="GraphEdit" parent="."]
layout_mode = 1

43
scenes/mapContainer.tscn Normal file
View File

@ -0,0 +1,43 @@
[gd_scene load_steps=2 format=3 uid="uid://c74180dtf7j7a"]
[ext_resource type="Texture2D" uid="uid://c35bsle7thcnh" path="res://sprites/ui/MiniMapBg.png" id="1_h88bi"]
[node name="MapContainer" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="ColorRect" type="ColorRect" parent="."]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="MiniMap" type="NinePatchRect" parent="."]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -174.0
offset_top = -177.0
offset_right = -24.0
offset_bottom = -27.0
grow_horizontal = 0
grow_vertical = 0
texture = ExtResource("1_h88bi")
patch_margin_left = 7
patch_margin_top = 7
patch_margin_right = 7
patch_margin_bottom = 7
[node name="RoomPreviewContainer" type="Node2D" parent="MiniMap"]

23
scripts/AssetHolder.cs Normal file
View File

@ -0,0 +1,23 @@
using Godot;
namespace ColdMint.scripts;
/// <summary>
/// <para>AssetHolder</para>
/// <para>资产持有者</para>
/// </summary>
public static class AssetHolder
{
public static Texture2D? White25;
public static Texture2D? White100;
/// <summary>
/// <para>Loading the game's static assets</para>
/// <para>加载游戏的静态资产</para>
/// </summary>
public static void LoadStaticAsset()
{
White25 = ResourceLoader.Load<Texture2D>("res://sprites/light/White_25.png");
White100 = ResourceLoader.Load<Texture2D>("res://sprites/light/White_100.png");
}
}

View File

@ -21,6 +21,29 @@ public static class Config
public const string Test = "test";
}
/// <summary>
/// <para>Difficulty</para>
/// <para>游戏难度</para>
/// </summary>
public static class Difficulty
{
/// <summary>
/// <para>Simple mode</para>
/// <para>简单模式</para>
/// </summary>
public const int Easy = 0;
/// <summary>
/// <para>Normal mode</para>
/// <para>正常模式</para>
/// </summary>
public const int Normal = 1;
/// <summary>
/// <para>Hard mode</para>
/// <para>困难模式</para>
/// </summary>
public const int Hard = 2;
}
/// <summary>
/// <para>Camp ID</para>
@ -115,12 +138,6 @@ public static class Config
public const int HotBarSize = 9;
/// <summary>
/// <para>UserID</para>
/// <para>用户ID</para>
/// </summary>
public const string UserId = "DefaultUser";
/// <summary>
/// <para>Whether version isolation is enabled</para>
/// <para>是否启用版本隔离</para>
@ -299,17 +316,27 @@ public static class Config
if (EnableVersionIsolation())
{
return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName,
ProjectSettings.GetSetting("application/config/name").AsString(), UserId,
ProjectSettings.GetSetting("application/config/name").AsString(),
ProjectSettings.GetSetting("application/config/version").AsString());
}
else
{
return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName,
ProjectSettings.GetSetting("application/config/name").AsString(), UserId,
ProjectSettings.GetSetting("application/config/name").AsString(),
DefaultVersionName);
}
}
/// <summary>
/// <para>GetDataBaseDirectory</para>
/// <para>获取数据库文件夹</para>
/// </summary>
/// <returns></returns>
public static string GetDataBaseDirectory()
{
return Path.Join(GetGameDataDirectory(), "Databases");
}
/// <summary>
/// <para>GetModDataDirectory</para>
/// <para>获取模组文件夹</para>
@ -391,6 +418,30 @@ public static class Config
public const int VerticalVelocityOfDamageNumbers = 5;
public static class OffsetAngleMode
{
/// <summary>
/// <para>Random(Default)</para>
/// <para>随机的(默认)</para>
/// </summary>
public const int Random = 0;
/// <summary>
/// <para>AlwaysSame</para>
/// <para>永远不变的偏移角度</para>
/// </summary>
public const int AlwaysSame = 1;
/// <summary>
/// <para>Cross</para>
/// <para>交叉变换</para>
/// </summary>
public const int Cross = 2;
}
/// <summary>
/// <para>Physical collision layer number</para>
/// <para>物理碰撞层 序号</para>
@ -398,12 +449,13 @@ public static class Config
public static class LayerNumber
{
public const int RoomArea = 1;
public const int Ground = 2;
public const int Floor = 2;
public const int Player = 3;
public const int PickAbleItem = 4;
public const int Projectile = 5;
public const int Platform = 6;
public const int Mob = 7;
public const int Wall = 8;
}
/// <summary>

View File

@ -8,11 +8,17 @@ using Godot;
namespace ColdMint.scripts;
/// <summary>
/// <para>The node holder within the game scene</para>
/// <para>游戏场景内的节点持有者</para>
/// <para>Dependencies on the runtime of the game scene</para>
/// <para>游戏场景运行时的依赖</para>
/// </summary>
public static class GameSceneNodeHolder
public static class GameSceneDepend
{
/// <summary>
/// <para>The midpoint of the minimap</para>
/// <para>迷你地图的中点</para>
/// </summary>
public static Vector2 MiniMapMidpointCoordinate;
private static Player? _player;
/// <summary>
@ -35,6 +41,18 @@ public static class GameSceneNodeHolder
}
}
/// <summary>
/// <para>When the mouse enters the scope of a character, it is considered a target</para>
/// <para>鼠标进入到某个角色的范围内时,会将其视作目标</para>
/// </summary>
public static Node2D? TemporaryTargetNode { get; set; }
/// <summary>
/// <para>MiniMapContainerNode</para>
/// <para>迷你地图容器节点</para>
/// </summary>
public static Node2D? MiniMapContainerNode { get; set; }
/// <summary>
/// <para>ProjectileContainer</para>
/// <para>抛射体容器</para>
@ -113,7 +131,6 @@ public static class GameSceneNodeHolder
NodeUtils.ForEachNode<PacksackUi>(BackpackUiContainer, node =>
{
//If the child node is not visible, the traversal continues.
//如果子节点不可见,则继续遍历。
if (!node.Visible)
@ -122,7 +139,6 @@ public static class GameSceneNodeHolder
//直到找到可见的节点隐藏该节点然后返回true结束遍历。
node.Hide();
return true;
});
}
}

View File

@ -9,7 +9,7 @@ namespace ColdMint.scripts.camp;
/// </summary>
public static class CampManager
{
private static readonly Dictionary<string, Camp?> Camps = new Dictionary<string, Camp?>();
private static readonly Dictionary<string, Camp?> Camps = new();
/// <summary>
/// <para>The default camp is returned if no corresponding camp is obtained</para>

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using ColdMint.scripts.bubble;
using ColdMint.scripts.camp;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.stateMachine;
using ColdMint.scripts.utils;
@ -63,9 +62,6 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </remarks>
private RayCast2D? _attackObstacleDetection;
private VisibleOnScreenEnabler2D? _screenEnabler2D;
/// <summary>
/// <para>Navigation agent</para>
/// <para>导航代理</para>
@ -119,22 +115,9 @@ public sealed partial class AiCharacter : CharacterTemplate
{
base._Ready();
_enemyInTheAttackRange = new List<CharacterTemplate>();
_enemyInTheScoutRange = new List<CharacterTemplate>();
_weaponInTheScoutRange = new List<WeaponTemplate>();
_screenEnabler2D = GetNode<VisibleOnScreenEnabler2D>("VisibleOnScreenEnabler2D");
_screenEnabler2D.ScreenEntered += () =>
{
//When the character enters the screen.
//当角色进入屏幕。
ProcessMode = ProcessModeEnum.Disabled;
};
_screenEnabler2D.ScreenExited += () =>
{
//When the character leaves the screen.
//当角色离开屏幕。
ProcessMode = ProcessModeEnum.Inherit;
};
_enemyInTheAttackRange = [];
_enemyInTheScoutRange = [];
_weaponInTheScoutRange = [];
_bubbleMarker = GetNode<BubbleMarker>("BubbleMarker");
if (_bubbleMarker != null)
{
@ -342,7 +325,7 @@ public sealed partial class AiCharacter : CharacterTemplate
if (NavigationAgent2D != null && IsOnFloor())
{
var nextPathPosition = NavigationAgent2D.GetNextPathPosition();
var direction = (nextPathPosition - GlobalPosition).Normalized();
var direction = GlobalPosition.DirectionTo(nextPathPosition);
velocity = direction * Config.CellSize * Speed * ProtectedSpeedScale;
}
}

View File

@ -73,9 +73,16 @@ public partial class CharacterTemplate : CharacterBody2D
//物品被扔出后多长时间恢复与地面和平台的碰撞(单位:秒)
private readonly double _itemCollisionRecoveryTime = 0.045f;
public string? ReadOnlyCharacterName => CharacterName;
public string? ReadOnlyCharacterName => TranslationServerUtils.Translate(CharacterName);
protected string? CharacterName;
[Export] public string? CharacterName;
/// <summary>
/// <para>Can mutate after death</para>
/// <para>是否允许死后变异</para>
/// </summary>
[Export]
public bool CanMutateAfterDeath { get; set; } = true;
protected IItemContainer? ProtectedItemContainer;
@ -156,21 +163,22 @@ public partial class CharacterTemplate : CharacterBody2D
//The initial health of the character after creation
//角色创建后的初始血量
private int _initialHp;
[Export] public int InitialHp;
public int MaxHp;
[Export] public int MaxHp;
/// <summary>
/// <para>The camp ID of the role</para>
/// <para>角色的阵营ID</para>
/// </summary>
public string CampId = null!;
[Export] public string? CampId;
private DamageNumberNodeSpawn? _damageNumber;
[Export] public string LootListId { get; private set; } = "";
private HealthBar? _healthBar;
private Label? _tipLabel;
private DateTime _lastDamageTime;
/// <summary>
@ -244,8 +252,10 @@ public partial class CharacterTemplate : CharacterBody2D
{
continue;
}
weaponTemplates.Add(weaponTemplate);
}
return weaponTemplates.ToArray();
}
@ -253,9 +263,6 @@ public partial class CharacterTemplate : CharacterBody2D
{
base._Ready();
PickingRangeBodiesList = new List<Node>();
CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
if (MaxHp <= 0)
{
@ -264,15 +271,14 @@ public partial class CharacterTemplate : CharacterBody2D
MaxHp = Config.DefaultMaxHp;
}
_initialHp = GetMeta("InitialHp", "0").AsInt32();
if (_initialHp <= 0 || _initialHp > MaxHp)
if (InitialHp <= 0 || InitialHp > MaxHp)
{
//If the initial health is less than or equal to 0 or greater than the maximum health, then set it to the maximum health
//如果初始血量小于等于0或者大于最大血量那么将其设置为最大血量
_initialHp = MaxHp;
InitialHp = MaxHp;
}
CurrentHp = _initialHp;
CurrentHp = InitialHp;
//The health bar of a creature may be null.
//生物的健康条可能为null。
_healthBar = GetNodeOrNull<HealthBar>("HealthBar");
@ -281,12 +287,12 @@ public partial class CharacterTemplate : CharacterBody2D
_healthBar.MaxValue = MaxHp;
}
ItemMarker2D = GetNode<Marker2D>("ItemMarker2D");
_itemMarkerOriginalX = ItemMarker2D.Position.X;
_animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
_pickingArea = GetNode<Area2D>("Area2DPickingArea");
_damageNumber = GetNode<Marker2D>("DamageNumber") as DamageNumberNodeSpawn;
_tipLabel = GetNodeOrNull<Label>("TipLabel");
if (_pickingArea != null)
{
//If true, the zone will detect objects or areas entering and leaving the zone.
@ -298,6 +304,56 @@ public partial class CharacterTemplate : CharacterBody2D
_pickingArea.BodyEntered += EnterThePickingRangeBody;
_pickingArea.BodyExited += ExitThePickingRangeBody;
}
//The character cannot pass through the wall and floor
//角色不能穿过墙壁和地板
SetCollisionMaskValue(Config.LayerNumber.Wall, true);
SetCollisionMaskValue(Config.LayerNumber.Floor, true);
InputPickable = true;
}
public override void _ExitTree()
{
base._ExitTree();
if (GameSceneDepend.TemporaryTargetNode == this)
{
GameSceneDepend.TemporaryTargetNode = null;
}
}
public override void _MouseEnter()
{
if (GameSceneDepend.Player != null)
{
var targetCamp = CampManager.GetCamp(CampId);
var playerCamp = CampManager.GetCamp(GameSceneDepend.Player.CampId);
if (CampManager.CanCauseHarm(targetCamp, playerCamp))
{
GameSceneDepend.TemporaryTargetNode = this;
}
}
if (_tipLabel != null)
{
_tipLabel.Visible = true;
_tipLabel.Text = CharacterName;
//Vertical Centering Tip
//垂直居中提示
var oldPosition = _tipLabel.Position;
oldPosition.X = -_tipLabel.Size.X / 2;
_tipLabel.Position = oldPosition;
}
}
public override void _MouseExit()
{
if (_tipLabel != null)
{
_tipLabel.Visible = false;
}
}
/// <summary>
@ -384,7 +440,9 @@ public partial class CharacterTemplate : CharacterBody2D
pickAbleTemplate.Owner = this;
pickAbleTemplate.Picked = true;
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, false);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Floor, false);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Wall, false);
LogCat.Log("item_pickup_disables_collision_damage", LogCat.LogLabel.ContactInjury);
pickAbleTemplate.EnableContactInjury = false;
pickAbleTemplate.Sleeping = true;
}
@ -453,7 +511,7 @@ public partial class CharacterTemplate : CharacterBody2D
return;
}
if (GameSceneNodeHolder.Player == null)
if (GameSceneDepend.Player == null)
{
//We didn't know who the player was, so we showed it as a hostile color
//我们不知道玩家是谁,所以我们将其显示为敌对颜色
@ -464,7 +522,7 @@ public partial class CharacterTemplate : CharacterBody2D
//If we set up a player node, then compare the injured party ID to the player's party ID
//如果我们设置了玩家节点那么将受伤者的阵营ID与玩家的阵营ID进行比较
var targetCamp = CampManager.GetCamp(CampId);
var playerCamp = CampManager.GetCamp(GameSceneNodeHolder.Player.CampId);
var playerCamp = CampManager.GetCamp(GameSceneDepend.Player.CampId);
if (CampManager.CanCauseHarm(targetCamp, playerCamp))
{
if (targetCamp != null && playerCamp != null)
@ -502,8 +560,8 @@ public partial class CharacterTemplate : CharacterBody2D
///<para>伤害模板</para>
/// </param>
/// <returns>
///<para>Return whether the damage was done successfully</para>
///<para>返回是否成功造成了伤害</para>
///<para>Return whether the character is dead</para>
///<para>返回本次伤害是否导致角色死亡。</para>
/// </returns>
public bool Damage(DamageTemplate damageTemplate)
{
@ -517,12 +575,11 @@ public partial class CharacterTemplate : CharacterBody2D
//角色死亡
OnDie(damageTemplate);
ThrowAllItemOnDie();
return true;
}
UpDataHealthBar();
return true;
return false;
}
/// <summary>
@ -561,6 +618,10 @@ public partial class CharacterTemplate : CharacterBody2D
/// <param name="force"></param>
public void AddForce(Vector2 force)
{
if (_additionalForce != Vector2.Zero)
{
throw new InvalidOperationException("AddForce called more than once");
}
_additionalForce = force;
}
@ -735,7 +796,7 @@ public partial class CharacterTemplate : CharacterBody2D
switch (item)
{
case PickAbleTemplate pickAbleTemplate:
if (GameSceneNodeHolder.WeaponContainer == null)
if (GameSceneDepend.WeaponContainer == null)
{
return;
}
@ -753,7 +814,9 @@ public partial class CharacterTemplate : CharacterBody2D
//We cannot immediately resume the physical collision when the weapon is discharged, which will cause the weapon to collide with the ground and platform earlier, preventing the weapon from flying.
//仍出武器时,我们不能立即恢复物理碰撞,立即恢复会导致武器更早的与地面和平台碰撞,阻止武器的飞行。
pickAbleTemplate.EnableContactInjury = true;
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true);
LogCat.Log("item_thrown_restore_collision_damage", LogCat.LogLabel.ContactInjury);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Floor, true);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Wall, true);
pickAbleTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};

View File

@ -1,4 +1,3 @@
using System;
using System.Text;
using System.Threading.Tasks;
using ColdMint.scripts.damage;
@ -17,8 +16,6 @@ namespace ColdMint.scripts.character;
/// </summary>
public partial class Player : CharacterTemplate
{
private Control? _floatLabel;
//Empty object projectile
//空的物品抛射线
private readonly Vector2[] _emptyVector2Array = [Vector2.Zero];
@ -29,10 +26,6 @@ public partial class Player : CharacterTemplate
//用于检测玩家是否站在平台上的射线
private RayCast2D? _platformDetectionRayCast2D;
private const float PromptTextDistance = 50;
//抛出物品的飞行速度
private float _throwingVelocity = Config.CellSize * 13;
@ -50,26 +43,24 @@ public partial class Player : CharacterTemplate
CharacterName = TranslationServerUtils.Translate("default_player_name");
LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat, ReadOnlyCharacterName,
GlobalPosition);
var floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
//Initializes the float label.
//初始化悬浮标签。
_floatLabel = NodeUtils.InstantiatePackedScene<Control>(floatLabelPackedScene);
if (_floatLabel == null)
{
throw new NullReferenceException(TranslationServer.Translate("float_label_instantiate_failed"));
}
_floatLabel.Hide();
NodeUtils.CallDeferredAddChild(this, _floatLabel);
_parabola = GetNode<Line2D>("Parabola");
_platformDetectionRayCast2D = GetNode<RayCast2D>("PlatformDetectionRayCast");
UpdateOperationTip();
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
var healthBarUi = GameSceneDepend.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.MaxHp = MaxHp;
healthBarUi.CurrentHp = CurrentHp;
}
//Mount the camera.
//挂载相机。
var mainCameraPackedScene = GD.Load<PackedScene>("res://prefab/MainCamera.tscn");
var camera2D = NodeUtils.InstantiatePackedScene<Camera2D>(mainCameraPackedScene);
if (camera2D != null)
{
NodeUtils.CallDeferredAddChild(this, camera2D);
}
}
protected override void WhenBindItemContainer(IItemContainer? itemContainer)
@ -98,7 +89,7 @@ public partial class Player : CharacterTemplate
private void SelectedItemSlotChangeEvent(SelectedItemSlotChangeEvent selectedItemSlotChangeEvent)
{
var item = selectedItemSlotChangeEvent.NewItemSlotNode?.GetItem();
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
GameSceneDepend.HideBackpackUiContainerIfVisible();
if (item is Node2D node2D)
{
CurrentItem = node2D;
@ -109,13 +100,21 @@ public partial class Player : CharacterTemplate
}
}
public override void _MouseEnter()
{
}
public override void _MouseExit()
{
}
/// <summary>
/// <para>Update operation prompt</para>
/// <para>更新操作提示</para>
/// </summary>
private void UpdateOperationTip()
{
var operationTipLabel = GameSceneNodeHolder.OperationTipLabel;
var operationTipLabel = GameSceneDepend.OperationTipLabel;
if (operationTipLabel == null)
{
return;
@ -243,8 +242,6 @@ public partial class Player : CharacterTemplate
{
PickingRangeBodiesList?.Remove(pickAbleItem);
}
RecycleFloatLabel();
}
}
@ -306,7 +303,7 @@ public partial class Player : CharacterTemplate
}
ThrowItem(ItemContainer.GetSelectIndex(), 1, GetThrowVelocity());
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
GameSceneDepend.HideBackpackUiContainerIfVisible();
CurrentItem = null;
}
}
@ -388,13 +385,15 @@ public partial class Player : CharacterTemplate
public override void Revive(int newHp)
{
base.Revive(newHp);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
var healthBarUi = GameSceneDepend.HealthBarUi;
if (healthBarUi != null)
{
//The purpose of setting Hp to the current Hp is to cause the life bar to refresh.
//将Hp设置为当前Hp的目的是使生命条刷新。
healthBarUi.CurrentHp = CurrentHp;
}
ProcessMode = ProcessModeEnum.Inherit;
Show();
}
/// <summary>
@ -430,48 +429,11 @@ public partial class Player : CharacterTemplate
return;
}
if (node is not Node2D node2D)
if (node is not Node2D)
{
return;
}
if (_floatLabel != null)
{
if (node is not PickAbleTemplate pickAbleTemplate)
{
return;
}
if (pickAbleTemplate.Picked)
{
//If the pickables are picked up, the label is not displayed.
//如果可拾捡物被捡起了,那么不显示标签。
LogCat.LogWarning("pickable_picked_up");
return;
}
NodeUtils.CallDeferredReparent(node, _floatLabel);
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.GlobalPosition = node2D.GlobalPosition;
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
var stringBuilder = new StringBuilder();
if (pickAbleTemplate.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
}
stringBuilder.Append(TranslationServerUtils.Translate(pickAbleTemplate.Name));
label.Text = stringBuilder.ToString();
_floatLabel.Show();
}
UpdateOperationTip();
}
@ -483,29 +445,13 @@ public partial class Player : CharacterTemplate
return;
}
RecycleFloatLabel();
UpdateOperationTip();
}
/// <summary>
/// <para>Recycle Float Label</para>
/// <para>回收悬浮标签</para>
/// </summary>
private void RecycleFloatLabel()
{
if (_floatLabel == null)
{
return;
}
_floatLabel.Hide();
NodeUtils.CallDeferredReparent(this, _floatLabel);
}
protected override void OnHit(DamageTemplate damageTemplate)
{
base.OnHit(damageTemplate);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
var healthBarUi = GameSceneDepend.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.CurrentHp = CurrentHp;

View File

@ -27,7 +27,7 @@ public partial class DamageNumber : CharacterBody2D
private bool _enableGravity;
public void SetVelocity(Vector2 velocity)
public new void SetVelocity(Vector2 velocity)
{
Velocity = velocity;
_enableGravity = true;

View File

@ -0,0 +1,48 @@
using System;
using System.IO;
namespace ColdMint.scripts.database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// <para>Game database manager</para>
/// <para>游戏数据库管理器</para>
/// </summary>
public class DataBaseManager
{
private static ServiceProvider? _serviceProvider;
/// <summary>
/// <para>Initialize database</para>
/// <para>初始化数据库</para>
/// </summary>
public static void InitDataBases(string databasePath)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDbContext<GameDbContext>(options =>
options.UseSqlite($"Data Source={Path.Join(databasePath, Config.SolutionName + ".db")}"));
_serviceProvider = serviceCollection.BuildServiceProvider();
var dataPackDbContext = GetRequiredService<GameDbContext>();
dataPackDbContext.Database.EnsureCreated();
}
/// <summary>
/// <para>Get database service</para>
/// <para>获取数据库服务</para>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetRequiredService<T>() where T : notnull
{
if (_serviceProvider == null)
{
throw new NullReferenceException("service provider is null");
}
var scope = _serviceProvider.CreateScope();
return scope.ServiceProvider.GetRequiredService<T>();
}
}

View File

@ -0,0 +1,42 @@
using ColdMint.scripts.database.gameDbTables;
using Microsoft.EntityFrameworkCore;
namespace ColdMint.scripts.database;
/// <summary>
/// <para>Game database</para>
/// <para>游戏数据库</para>
/// </summary>
public class GameDbContext(DbContextOptions<GameDbContext> options) : DbContext(options)
{
// ReSharper disable UnusedAutoPropertyAccessor.Global
public DbSet<ErrorRecord> ErrorRecords { get; set; }
// ReSharper restore UnusedAutoPropertyAccessor.Global
/// <summary>
/// <para>Async add error record</para>
/// <para>异步添加错误信息</para>
/// </summary>
/// <param name="errorRecord"></param>
public async void AddOrUpdateErrorRecordAsync(ErrorRecord errorRecord)
{
if (errorRecord.Message == null)
{
return;
}
var oldErrorRecord = await ErrorRecords.FindAsync(errorRecord.Message);
if (oldErrorRecord == null)
{
ErrorRecords.Add(errorRecord);
}
else
{
oldErrorRecord.Count++;
oldErrorRecord.LastDateTime = errorRecord.LastDateTime;
ErrorRecords.Update(oldErrorRecord);
}
await SaveChangesAsync();
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace ColdMint.scripts.database.gameDbTables;
/// <summary>
/// <para>Error record</para>
/// <para>错误记录</para>
/// </summary>
public class ErrorRecord
{
/// <summary>
/// <para>Message</para>
/// <para>错误消息</para>
/// </summary>
[Key]
[MaxLength(255)]
// ReSharper disable UnusedAutoPropertyAccessor.Global
public string? Message { get; set; }
// ReSharper restore UnusedAutoPropertyAccessor.Global
/// <summary>
/// <para>Count</para>
/// <para>出现次数</para>
/// </summary>
public int Count { get; set; } = 1;
/// <summary>
/// <para>DateTime</para>
/// <para>时间</para>
/// </summary>
public DateTime LastDateTime { get; set; } = DateTime.Now;
}

View File

@ -64,11 +64,30 @@ public static class LogCat
/// <para>日志收集器</para>
/// </summary>
public const string LogCollector = "LogCollector";
/// <summary>
/// <para>Mod Loader</para>
/// <para>模组加载器</para>
/// </summary>
public const string ModLoader = "ModLoader";
/// <summary>
/// <para>PickAble Template</para>
/// <para>可拾取物模板</para>
/// </summary>
public const string PickAbleTemplate = "PickAbleTemplate";
/// <summary>
/// <para>ContactInjury</para>
/// <para>物品碰撞伤害</para>
/// </summary>
public const string ContactInjury = "ContactInjury";
/// <summary>
/// <para>Room</para>
/// <para>房间</para>
/// </summary>
public const string Room = "Room";
}
@ -237,6 +256,8 @@ public static class LogCat
return;
}
//If you need to upload logs, you can upload logs.
//如果需要上传日志,并且能够上传日志。
if (LogCollector.CanUploadLog && upload)
{
var logData = new LogData

View File

@ -6,9 +6,9 @@ public interface IItem
{
/// <summary>
/// <para>ID of current item</para>
/// <para>当前项目的ID</para>
/// <para>当前物品的ID</para>
/// </summary>
string Id { get; }
string Id { get; set; }
/// <summary>
/// <para>Icon of current item</para>

View File

@ -1,5 +1,6 @@
using System;
using ColdMint.scripts.debug;
using ColdMint.scripts.pickable;
using ColdMint.scripts.utils;
using Godot;
@ -165,6 +166,11 @@ public partial class ItemSlotNode : MarginContainer
}
var duplicate = node2D.Duplicate();
if (node2D is PickAbleTemplate pickAbleTemplate)
{
pickAbleTemplate.CopyAttributes(duplicate);
}
if (duplicate is not Node2D newNode2D)
{
return null;

View File

@ -5,9 +5,10 @@ namespace ColdMint.scripts.inventory;
public readonly struct ItemType(
string id,
Func<Node?,IItem?> createItemFunc,
Texture2D? icon,
int maxStackQuantity)
int maxStackQuantity,
Func<Node?, IItem?> createItemFunc
)
{
/// <summary>
/// <para>Item id of this type</para>

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
@ -49,7 +48,7 @@ public static class ItemTypeManager
/// <summary>
/// <para>Create multiple new item instances for the given item Id</para>
/// <para>Create multiple new item instances for the given item ID</para>
/// <para>创建多个给定物品Id的新物品实例</para>
/// </summary>
/// <param name="id"></param>
@ -85,23 +84,6 @@ public static class ItemTypeManager
return items.ToArray();
}
/// <summary>
/// <para>Get the translated default name of the item type for the given id</para>
/// <para>获取指定物品id翻译后的物品名</para>
/// </summary>
/// <returns>
/// Translated default name of the item id if it exists. Else, return the id itself
/// </returns>
public static string DefaultNameOf(string id) => TranslationServerUtils.Translate($"item_{id}") ?? id;
/// <summary>
/// <para>Get the translated default description of the item type for the given id</para>
/// <para>获取指定物品id翻译后的描述</para>
/// </summary>
/// <returns>
/// Translated default description of the item id if it exists. Else, return null
/// </returns>
public static string? DefaultDescriptionOf(string id) => TranslationServerUtils.Translate($"item_{id}_desc");
/// <summary>
/// <para>Get the default icon of the item type for the given id</para>

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.serialization;
using ColdMint.scripts.utils;
@ -30,7 +29,7 @@ public static class ItemTypeRegister
LogCat.Log("start_item_register_from_file");
//初始化文件目录
//initialize file dir
var itemRegsDirPath = "res://data/itemRegs";
const string itemRegsDirPath = "res://data/itemRegs";
var itemRegsDir = DirAccess.Open(itemRegsDirPath);
var error = DirAccess.GetOpenError();
if (error is not Error.Ok)
@ -103,24 +102,10 @@ public static class ItemTypeRegister
//加载场景和图标
var scene = ResourceLoader.Load<PackedScene>(typeInfo.ScenePath);
var icon = ResourceLoader.Load<Texture2D>(typeInfo.IconPath);
//Create init delegate
//创建初始化委托
Action<Node?>? setArgs = null;
if (typeInfo.CustomArgs != null && typeInfo.CustomArgs.Count > 0)
{
foreach (var arg in typeInfo.CustomArgs)
{
setArgs +=
node => node?.SetDeferred(arg.Name, arg.ParseValue());
}
}
//构造项目类型,寄存器
//construct item type, register
var itemType = new ItemType(typeInfo.Id,
defaultParentNode =>
icon, typeInfo.MaxStackValue, defaultParentNode =>
{
var newItem = NodeUtils.InstantiatePackedScene<IItem>(scene);
if (newItem is not Node node) return newItem;
@ -130,11 +115,10 @@ public static class ItemTypeRegister
return null;
}
setArgs?.Invoke(node);
newItem.Id = typeInfo.Id;
NodeUtils.CallDeferredAddChild(NodeUtils.FindContainerNode(node, defaultParentNode), node);
return newItem;
},
icon, typeInfo.MaxStackValue);
});
var succeed = ItemTypeManager.Register(itemType);
LogCat.LogWithFormat("register_item", label: LogCat.LogLabel.Default, LogCat.UploadFormat, itemType.Id,
succeed);
@ -146,44 +130,5 @@ public static class ItemTypeRegister
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.Bool => bool.Parse(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", LogCat.LogLabel.Default, LogCat.UploadFormat, "Vector2",
s);
return Vector2.Zero;
}
return new Vector2(float.Parse(ss[0]), float.Parse(ss[1]));
}
}
private enum CustomArgType
{
String,
Int,
Float,
Vector2,
Texture,
Bool,
}
int MaxStackValue);
}

View File

@ -46,7 +46,7 @@ public partial class Packsack : PickAbleTemplate
}
}
GameSceneNodeHolder.BackpackUiContainer?.Show();
GameSceneDepend.BackpackUiContainer?.Show();
_packsackUi?.Show();
}

View File

@ -19,42 +19,50 @@ public partial class GameSceneLoader : SceneLoaderTemplate
public override Task InitializeData()
{
RenderingServer.SetDefaultClearColor(Color.FromHsv(0, 0, 0));
//Loading the blood bar scene
//加载血条场景
var healthBarUi = GetNode<HealthBarUi>("CanvasLayer/Control/VBoxContainer/HealthBarUi");
GameSceneNodeHolder.HealthBarUi = healthBarUi;
GameSceneDepend.HealthBarUi = healthBarUi;
//Load HotBar
//加载HotBar
var hotBar = GetNode<HotBar>("CanvasLayer/Control/VBoxContainer/HotBar");
GameSceneNodeHolder.HotBar = hotBar;
GameSceneDepend.HotBar = hotBar;
//Backpack Ui container
//背包Ui容器
var backpackUiContainer = GetNode<Control>("CanvasLayer/BackpackUIContainer");
GameSceneNodeHolder.BackpackUiContainer = backpackUiContainer;
GameSceneDepend.BackpackUiContainer = backpackUiContainer;
//Load operation prompt
//加载操作提示
var operationTip = GetNode<RichTextLabel>("CanvasLayer/Control/VBoxContainer/OperationTip");
GameSceneNodeHolder.OperationTipLabel = operationTip;
GameSceneDepend.OperationTipLabel = operationTip;
//Loaded weapon container
//加载武器容器
var weaponContainer = GetNode<Node2D>("WeaponContainer");
GameSceneNodeHolder.WeaponContainer = weaponContainer;
GameSceneDepend.WeaponContainer = weaponContainer;
//Load projectile container
//加载抛射体容器
var projectileContainer = GetNode<Node2D>("ProjectileContainer");
GameSceneNodeHolder.ProjectileContainer = projectileContainer;
GameSceneDepend.ProjectileContainer = projectileContainer;
//Load Packsack container
//加载背包容器
var packsackContainer = GetNode<Node2D>("PacksackContainer");
GameSceneNodeHolder.PacksackContainer = packsackContainer;
GameSceneDepend.PacksackContainer = packsackContainer;
//Load AICharacter container
//加载AICharacter容器
var aiCharacterContainer = GetNode<Node2D>("AICharacterContainer");
GameSceneNodeHolder.AiCharacterContainer = aiCharacterContainer;
GameSceneDepend.AiCharacterContainer = aiCharacterContainer;
//Load player container
//加载玩家容器
var playerContainer = GetNode<Node2D>("PlayerContainer");
GameSceneNodeHolder.PlayerContainer = playerContainer;
GameSceneDepend.PlayerContainer = playerContainer;
//Load the room container node
//加载房间容器节点
var miniMapContainerNode = GetNode<Node2D>("CanvasLayer/Control/MapContainer/MiniMap/RoomPreviewContainer");
GameSceneDepend.MiniMapContainerNode = miniMapContainerNode;
//计算迷你地图的中点
var mapContainer = GetNode<NinePatchRect>("CanvasLayer/Control/MapContainer/MiniMap");
GameSceneDepend.MiniMapMidpointCoordinate = mapContainer.Size / 2;
return Task.CompletedTask;
}
@ -95,6 +103,7 @@ public partial class GameSceneLoader : SceneLoaderTemplate
var seedInfo = TranslationServerUtils.TranslateWithFormat("ui_seed_info", MapGenerator.Seed);
_seedLabel.Text = seedInfo ?? $"Seed: {MapGenerator.Seed}";
}
await MapGenerator.GenerateMap();
}
}

View File

@ -291,14 +291,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
if (_nodeBinding.TagLineEdit != null)
{
if (_hasStartNode)
{
_nodeBinding.TagLineEdit.Text = string.Empty;
}
else
{
_nodeBinding.TagLineEdit.Text = Config.RoomDataTag.StartingRoom;
}
_nodeBinding.TagLineEdit.Text = _hasStartNode ? string.Empty : Config.RoomDataTag.StartingRoom;
}
if (_nodeBinding.HBoxContainer != null)
@ -316,6 +309,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
{
return;
}
GetTree().ChangeSceneToPacked(_mainMenu);
};
}
@ -534,11 +528,6 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
{
foreach (var dictionary in connectionList)
{
if (dictionary == null)
{
continue;
}
var keys = dictionary.Keys;
if (keys.Count == 0)
{

View File

@ -107,7 +107,7 @@ public partial class PacksackUi : UiLoaderTemplate
{
_exitButton.Pressed += () =>
{
GameSceneNodeHolder.BackpackUiContainer?.Hide();
GameSceneDepend.BackpackUiContainer?.Hide();
Hide();
};
}

View File

@ -1,9 +1,9 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using ColdMint.scripts.camp;
using ColdMint.scripts.contribute;
using ColdMint.scripts.database;
using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
@ -82,6 +82,7 @@ public partial class SplashScreenLoader : UiLoaderTemplate
//Disable all logs in the release version.
//在发行版禁用所有日志。
LogCat.MinLogLevel = Config.IsDebug() ? LogCat.InfoLogLevel : LogCat.DisableAllLogLevel;
AssetHolder.LoadStaticAsset();
ContributorDataManager.RegisterAllContributorData();
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
@ -97,6 +98,13 @@ public partial class SplashScreenLoader : UiLoaderTemplate
Directory.CreateDirectory(dataPath);
}
var databasePath = Config.GetDataBaseDirectory();
if (!Directory.Exists(databasePath))
{
Directory.CreateDirectory(databasePath);
}
DataBaseManager.InitDataBases(databasePath);
//Registered camp
//注册阵营
var defaultCamp = new Camp(Config.CampId.Default)
@ -104,7 +112,10 @@ public partial class SplashScreenLoader : UiLoaderTemplate
FriendInjury = true
};
CampManager.SetDefaultCamp(defaultCamp);
var mazoku = new Camp(Config.CampId.Mazoku);
var mazoku = new Camp(Config.CampId.Mazoku)
{
FriendInjury = true
};
CampManager.AddCamp(mazoku);
var aborigines = new Camp(Config.CampId.Aborigines);
CampManager.AddCamp(aborigines);

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.utils;
namespace ColdMint.scripts.loot;
@ -40,9 +39,15 @@ public readonly struct LootEntry(string itemId,int minQuantity=1,int maxQuantity
/// <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)
/// <param name="Chance">
///<para>Chance</para>
///<para>概率</para>
/// </param>
/// <param name="Entries">
///<para>Entries</para>
///<para>条目列表</para>
/// </param>
public readonly record struct LootGroup(float Chance, IEnumerable<LootEntry> Entries)
{
private int WeightSum { get; } = Entries.Sum(entry => entry.Weight);
@ -61,6 +66,6 @@ public readonly record struct LootGroup(double Chance, IEnumerable<LootEntry> En
var quantity = random.Next(entry.MinQuantity, entry.MaxQuantity + 1);
return new(entry.ItemId, quantity);
return new LootDatum(entry.ItemId, quantity);
}
}

View File

@ -10,19 +10,18 @@ public static class LootRegister
/// </summary>
public static void StaticRegister()
{
//Register the test using the loot table
//注册测试使用的战利品表
if (Config.IsDebug())
{
IList<LootGroup> lootGroups = [];
lootGroups.Add(new LootGroup(0.8,
List<LootGroup> lootGroups =
[
new LootEntry("degraded_staff_of_the_undead", weight: 2), new LootEntry("staff_of_the_undead")
]));
lootGroups.Add(new LootGroup(1,
new LootGroup(0.8f,
[
new LootEntry("packsack", minQuantity: 2, maxQuantity: 4)
]));
new LootEntry("staff_necromancy"),
]),
new LootGroup(1, [new LootEntry("portable_backpacks")])
];
var testLootList = new LootList(Config.LootListId.Test, lootGroups);
LootListManager.RegisterLootList(testLootList);
}

View File

@ -12,14 +12,15 @@ namespace ColdMint.scripts.map;
public partial class AiCharacterSpawn : Marker2D
{
private PackedScene? _packedScene;
[Export]
public string? ResPath;
public override void _Ready()
{
base._Ready();
var resPath = GetMeta("ResPath", Name).AsString();
if (!string.IsNullOrEmpty(resPath))
if (!string.IsNullOrEmpty(ResPath))
{
_packedScene = GD.Load<PackedScene>(resPath);
_packedScene = GD.Load<PackedScene>(ResPath);
}
EventManager.AiCharacterGenerateEvent += OnAiCharacterGenerateEvent;
@ -32,7 +33,7 @@ public partial class AiCharacterSpawn : Marker2D
/// <param name="aiCharacterGenerateEvent"></param>
public void OnAiCharacterGenerateEvent(AiCharacterGenerateEvent aiCharacterGenerateEvent)
{
if (GameSceneNodeHolder.AiCharacterContainer == null)
if (GameSceneDepend.AiCharacterContainer == null)
{
return;
}
@ -48,7 +49,7 @@ public partial class AiCharacterSpawn : Marker2D
return;
}
NodeUtils.CallDeferredAddChild(GameSceneNodeHolder.AiCharacterContainer, aiCharacter);
NodeUtils.CallDeferredAddChild(GameSceneDepend.AiCharacterContainer, aiCharacter);
aiCharacter.GlobalPosition = GlobalPosition;
}

View File

@ -7,6 +7,7 @@ using ColdMint.scripts.map.events;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.preview;
using ColdMint.scripts.map.room;
using ColdMint.scripts.serialization;
using ColdMint.scripts.utils;
@ -148,9 +149,14 @@ public static class MapGenerator
return;
}
if (GameSceneNodeHolder.AiCharacterContainer != null)
if (GameSceneDepend.MiniMapContainerNode != null)
{
NodeUtils.DeleteAllChild(GameSceneNodeHolder.AiCharacterContainer);
NodeUtils.DeleteAllChild(GameSceneDepend.MiniMapContainerNode);
}
if (GameSceneDepend.AiCharacterContainer != null)
{
NodeUtils.DeleteAllChild(GameSceneDepend.AiCharacterContainer);
}
NodeUtils.DeleteAllChild(_mapRoot);
@ -335,6 +341,26 @@ public static class MapGenerator
return false;
}
//Create a room preview image.
//创建房间预览图。
var image = RoomPreview.CreateImage(roomPlacementData.NewRoom.GetTileMapLayer(Config.TileMapLayerName.Ground));
if (GameSceneDepend.MiniMapContainerNode == null || roomPlacementData.Position == null ||
image == null)
{
return false;
}
var sprite = new Sprite2D();
sprite.Scale = new Vector2(5, 5);
sprite.Texture = image;
//TODOCalculate the coordinates of the room preview view.
//TODO计算房间预览图的坐标。
sprite.Position = GameSceneDepend.MiniMapMidpointCoordinate +
roomPlacementData.Position.Value / Config.CellSize;
NodeUtils.CallDeferredAddChild(GameSceneDepend.MiniMapContainerNode, sprite);
//Rooms are added to the dictionary only after the preview is created.
//创建预览图后才将房间添加到字典。
dictionary.Add(roomNodeDataId, roomPlacementData.NewRoom);
LogCat.LogWithFormat("room_placement_information", LogCat.LogLabel.Default, LogCat.UploadFormat, roomNodeDataId,
roomPlacementData.Position.ToString());

View File

@ -25,11 +25,10 @@ public partial class PlayerSpawn : Marker2D
private void GameReplayEvent(GameReplayEvent gameReplayEvent)
{
if (GameSceneNodeHolder.Player != null)
if (GameSceneDepend.Player != null)
{
GameSceneNodeHolder.Player.ProcessMode = ProcessModeEnum.Inherit;
GameSceneNodeHolder.Player.GlobalPosition = GlobalPosition;
GameSceneNodeHolder.Player.Revive(GameSceneNodeHolder.Player.MaxHp);
GameSceneDepend.Player.Revive(GameSceneDepend.Player.MaxHp);
GameSceneDepend.Player.GlobalPosition = GlobalPosition;
return;
}
@ -42,7 +41,7 @@ public partial class PlayerSpawn : Marker2D
/// </summary>
private void SpawnPlayer()
{
if (GameSceneNodeHolder.PlayerContainer == null)
if (GameSceneDepend.PlayerContainer == null)
{
return;
}
@ -60,10 +59,10 @@ public partial class PlayerSpawn : Marker2D
return;
}
//The player's parent node must be GameSceneNodeHolder PlayerContainer.
//玩家的父节点必须是GameSceneNodeHolder.PlayerContainer。
NodeUtils.CallDeferredAddChild(GameSceneNodeHolder.PlayerContainer, playerNode);
var itemContainer = GameSceneNodeHolder.HotBar?.GetItemContainer();
//The player's parent node must be GameSceneDepend PlayerContainer.
//玩家的父节点必须是GameSceneDepend.PlayerContainer。
NodeUtils.CallDeferredAddChild(GameSceneDepend.PlayerContainer, playerNode);
var itemContainer = GameSceneDepend.HotBar?.GetItemContainer();
if (itemContainer == null)
{
//Throws an exception when the item container is empty.
@ -72,7 +71,7 @@ public partial class PlayerSpawn : Marker2D
}
playerNode.ItemContainer = itemContainer;
GameSceneNodeHolder.Player = playerNode;
GameSceneDepend.Player = playerNode;
playerNode.GlobalPosition = GlobalPosition;
}
@ -80,7 +79,7 @@ public partial class PlayerSpawn : Marker2D
{
//After the map is generated, create the player instance.
//当地图生成完成后,创建玩家实例。
if (GameSceneNodeHolder.Player != null)
if (GameSceneDepend.Player != null)
{
//An existing player instance will not be created.
//已经存在玩家实例,不再创建。

View File

@ -0,0 +1,70 @@
namespace ColdMint.scripts.map.preview;
using Godot;
public static class RoomPreview
{
/// <summary>
/// <para>Create Image</para>
/// <para>创建图像</para>
/// </summary>
/// <param name="groundTileMapLayer"></param>
/// <returns></returns>
public static ImageTexture? CreateImage(TileMapLayer? groundTileMapLayer)
{
if (groundTileMapLayer == null)
{
return null;
}
//Calculate the size of the image.
//计算图像的尺寸。
//The width is maxX minX and the height is maxY minY.
//宽度为maxX-minX高度为maxY-minY。
var maxY = int.MinValue;
var maxX = int.MinValue;
var minY = int.MaxValue;
var minX = int.MaxValue;
var cellsArray = groundTileMapLayer.GetUsedCells();
foreach (var vector2I in cellsArray)
{
if (vector2I.Y > maxY)
{
maxY = vector2I.Y;
}
if (vector2I.Y < minY)
{
minY = vector2I.Y;
}
if (vector2I.X > maxX)
{
maxX = vector2I.X;
}
if (vector2I.X < minX)
{
minX = vector2I.X;
}
}
var height = maxY - minY;
var width = maxX - minX;
var offsetVector2 = Vector2I.Zero - new Vector2I(minX, minY);
//Create an image.
//创建image。
var image = Image.CreateEmpty(width + 1, height + 1, false, Image.Format.Rgba8);
//image.Fill(Colors.Green);
//Fill in pixels
//填充像素点
foreach (var vector2I in cellsArray)
{
image.SetPixel(vector2I.X + offsetVector2.X, vector2I.Y + offsetVector2.Y, Colors.Blue);
}
//Create texture.
//创建texture
return ImageTexture.CreateFromImage(image);
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.utils;
@ -23,7 +24,10 @@ public class Room
private List<TileMapLayer>? _tileMapLayers;
private Area2D? _area2D;
private PointLight2D? _pointLight2D;
private CollisionShape2D? _collisionShape2D;
private bool _hasPlayer;
private readonly List<CharacterTemplate> _characterTemplateList = [];
public string? EnterRoomEventHandlerId { get; set; }
@ -46,6 +50,34 @@ public class Room
return _tileMapLayers?.FirstOrDefault(tileMapLayer => tileMapLayer.Name == layerName);
}
/// <summary>
/// <para>ShowAllCharacterTemplate</para>
/// <para>显示所有角色模板</para>
/// </summary>
private void ShowAllCharacterTemplate()
{
LogCat.LogWithFormat("show_all_node", LogCat.LogLabel.Room, LogCat.UploadFormat,
_characterTemplateList.Count);
foreach (var characterTemplate in _characterTemplateList)
{
characterTemplate.Show();
}
}
/// <summary>
/// <para>HideAllCharacterTemplate</para>
/// <para>隐藏所有角色模板</para>
/// </summary>
private void HideAllCharacterTemplate()
{
LogCat.LogWithFormat("hide_all_node", LogCat.LogLabel.Room, LogCat.UploadFormat,
_characterTemplateList.Count);
foreach (var characterTemplate in _characterTemplateList)
{
characterTemplate.Hide();
}
}
/// <summary>
/// <para>When a node enters the room</para>
/// <para>当有节点进入房间时</para>
@ -59,6 +91,33 @@ public class Room
_rootNode.Name);
}
if (node is Player player)
{
_characterTemplateList.Add(player);
_hasPlayer = true;
//The player enters the room, opening up their view.
//玩家进入了房间,开放视野。
if (_pointLight2D != null)
{
_pointLight2D.Show();
_pointLight2D.Texture = AssetHolder.White100;
}
ShowAllCharacterTemplate();
}else if (node is CharacterTemplate characterTemplate)
{
if (_hasPlayer)
{
characterTemplate.Show();
}
else
{
characterTemplate.Hide();
}
_characterTemplateList.Add(characterTemplate);
}
if (string.IsNullOrEmpty(EnterRoomEventHandlerId))
{
return;
@ -68,6 +127,7 @@ public class Room
enterRoomEventHandler?.OnEnterRoom(node, this);
}
/// <summary>
/// <para>When a node exits the room</para>
/// <para>当有节点退出房间时</para>
@ -81,6 +141,26 @@ public class Room
_rootNode.Name);
}
if (node is Player player)
{
//The player leaves the room, hiding the view.
//玩家离开了房间,隐藏视野。
_characterTemplateList.Remove(player);
_hasPlayer = false;
if (_pointLight2D != null)
{
_pointLight2D.Show();
_pointLight2D.Texture = AssetHolder.White25;
}
HideAllCharacterTemplate();
}
else if (node is CharacterTemplate characterTemplate && characterTemplate.Visible)
{
_characterTemplateList.Remove(characterTemplate);
}
if (string.IsNullOrEmpty(ExitRoomEventHandlerId))
{
return;
@ -148,11 +228,17 @@ public class Room
//Sets the collision layer that can be detected in the current room area.
//设置当前房间区域可检测到的碰撞层。
_area2D.SetCollisionMaskValue(Config.LayerNumber.Player, true);
_area2D.SetCollisionMaskValue(Config.LayerNumber.Mob, true);
_area2D.BodyExited += OnExitRoom;
_area2D.BodyEntered += OnEnterRoom;
_collisionShape2D = _area2D.GetChild<CollisionShape2D>(0);
_roomSlots = GetRoomSlots(GetTileMapLayer(Config.TileMapLayerName.Ground), _area2D,
node2D.GetNode<Node2D>("RoomSlotList"));
_pointLight2D = node2D.GetNodeOrNull<PointLight2D>("PointLight2D");
if (_pointLight2D != null)
{
_pointLight2D.BlendMode = Light2D.BlendModeEnum.Mix;
}
}
public Node2D? RootNode => _rootNode;

View File

@ -2,7 +2,6 @@
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;

View File

@ -4,6 +4,7 @@ using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.pickable;
@ -14,12 +15,21 @@ namespace ColdMint.scripts.pickable;
/// </summary>
public partial class PickAbleTemplate : RigidBody2D, IItem
{
[Export] public virtual string Id { get; set; } = "ID";
//Do not export this field because the ID is specified within yaml.
//不要导出此字段因为ID是在yaml内指定的。
public virtual string Id { get; set; } = "ID";
[Export] protected Texture2D? UniqueIcon { get; set; }
public Texture2D Icon => UniqueIcon ?? ItemTypeManager.DefaultIconOf(Id);
[Export] protected string? UniqueName { get; set; }
public new string Name => UniqueName ?? ItemTypeManager.DefaultNameOf(Id);
[Export] protected string? UniqueDescription { get; set; }
public new string Name
{
get
{
var key = $"item_{Id}";
return TranslationServerUtils.Translate(key) ?? key;
}
}
public virtual bool CanPutInPack => true;
/// <summary>
@ -37,7 +47,15 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
[Export] private int _minContactInjury = 1;
[Export] private int _maxContactInjury = 2;
public string? Description => UniqueDescription ?? ItemTypeManager.DefaultDescriptionOf(Id);
public string Description
{
get
{
var key = $"item_{Id}_desc";
return TranslationServerUtils.Translate(key) ?? key;
}
}
public int Quantity { get; set; } = 1;
/// <summary>
@ -60,6 +78,8 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
public int MaxQuantity { get; set; }
private Label? _tipLabel;
public virtual void Use(Node2D? owner, Vector2 targetGlobalPosition)
{
}
@ -69,6 +89,8 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
_damageArea2D = GetNode<Area2D>("DamageArea2D");
_damageArea2D.BodyEntered += OnBodyEnter;
_damageArea2D.BodyExited += OnBodyExited;
_tipLabel = GetNodeOrNull<Label>("TipLabel");
InputPickable = true;
}
private void OnBodyExited(Node node)
@ -88,6 +110,7 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
//No longer in contact with any shingles can cause injury
//不再与任何瓦片接触后,可以造成伤害
EnableContactInjury = true;
LogCat.Log("after_no_longer_in_contact_with_any_tiles", LogCat.LogLabel.ContactInjury);
SetCollisionMaskValue(Config.LayerNumber.Player, false);
}
}
@ -109,6 +132,7 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
{
_tileMapNumber++;
EnableContactInjury = false;
LogCat.Log("contact_with_tiles_disables_damage", LogCat.LogLabel.ContactInjury);
//Items can be pushed by the player when they are on the ground
//当物品在地面上时,可被玩家推动
SetCollisionMaskValue(Config.LayerNumber.Player, true);
@ -117,20 +141,20 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
{
if (!EnableContactInjury)
{
LogCat.LogWarning("contact_damage_disabled_during_collision");
LogCat.LogWarning("contact_damage_disabled_during_collision", LogCat.LogLabel.ContactInjury);
return;
}
if (Owner == null)
{
LogCat.LogWarning("item_has_no_owner");
LogCat.LogWarning("item_has_no_owner", LogCat.LogLabel.PickAbleTemplate);
return;
}
if (Owner is not CharacterTemplate ownerCharacterTemplate)
{
LogCat.LogWarning("owner_of_the_item_is_not_character");
LogCat.LogWarning("owner_of_the_item_is_not_character", LogCat.LogLabel.PickAbleTemplate);
return;
}
@ -140,7 +164,7 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
CampManager.GetCamp(characterTemplate.CampId));
if (!canCauseHarm)
{
LogCat.Log("no_damage_between_camps");
LogCat.Log("no_damage_between_camps", LogCat.LogLabel.PickAbleTemplate);
return;
}
@ -163,6 +187,35 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
}
public override void _MouseEnter()
{
if (Picked)
{
return;
}
if (_tipLabel == null)
{
return;
}
_tipLabel.Visible = true;
_tipLabel.Text = Name;
//Vertical Centering Tip
//垂直居中提示
var oldPosition = _tipLabel.Position;
oldPosition.X = -_tipLabel.Size.X / 2;
_tipLabel.Rotation = -Rotation;
_tipLabel.Position = oldPosition;
}
public override void _MouseExit()
{
if (_tipLabel == null)
{
return;
}
_tipLabel.Visible = false;
}
/// <summary>
/// <para>Flip item</para>
/// <para>翻转物品</para>
@ -172,6 +225,20 @@ public partial class PickAbleTemplate : RigidBody2D, IItem
{
}
/// <summary>
/// <para>Please copy node properties within this function</para>
/// <para>请在此函数内复制节点属性</para>
/// </summary>
/// <param name="node"></param>
public void CopyAttributes(Node node)
{
if (node is not PickAbleTemplate pickAbleTemplate)
{
return;
}
pickAbleTemplate.Id = Id;
}
public virtual void Destroy()
{
QueueFree();

View File

@ -1,3 +1,363 @@
using System;
using System.Collections.Generic;
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.pickable;
using ColdMint.scripts.projectile.decorator;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.projectile;
public partial class Projectile : ProjectileTemplate;
/// <summary>
/// <para>Projectile</para>
/// <para>抛射体</para>
/// </summary>
public partial class Projectile : CharacterBody2D
{
/// <summary>
/// <para>life(ms)</para>
/// <para>子弹的存在时间(毫秒)</para>
/// </summary>
[Export] private long _life;
//The durability of the projectile
//抛射体的耐久度
//When the projectile hits the object, the durability will be reduced, and when the durability is less than or equal to 0, the projectile will be destroyed
//当抛射体撞击到物体时会减少耐久度当耐久度小于等于0时销毁抛射体
[Export] private double _durability;
[Export] private int _maxDamage;
[Export] private int _minDamage;
[Export] private int _damageType;
/// <summary>
/// <para>After this time destroy the projectile</para>
/// <para>超过此时刻销毁抛射体</para>
/// </summary>
private DateTime? _destructionTime;
[Export] public float Speed;
/// <summary>
/// <para>Whether it bounces back after hitting an enemy or a wall</para>
/// <para>是否撞到敌人或墙壁后反弹</para>
/// </summary>
[Export] private bool _enableBounce;
/// <summary>
/// <para>Can it penetrate the wall</para>
/// <para>是否可以穿透墙壁</para>
/// </summary>
[Export] private bool _ignoreWall;
/// <summary>
/// <para>Enable the tracking of the enemy</para>
/// <para>启用追踪敌人的功能</para>
/// </summary>
[Export] private bool _enableTracking;
/// <summary>
/// <para>The target dies and destroys the projectile at the same time</para>
/// <para>在目标死亡后销毁抛射体</para>
/// </summary>
[Export] private bool _targetDiesDestroyProjectile;
/// <summary>
/// <para>The target</para>
/// <para>设置目标</para>
/// </summary>
public Node2D? TargetNode { get; set; }
private List<IProjectileDecorator>? _projectileDecorators;
/// <summary>
/// <para>Rays used to detect walls</para>
/// <para>用于检测墙壁的射线</para>
/// </summary>
private RayCast2D? _wallRayCast;
/// <summary>
/// <para>Repel strength</para>
/// <para>击退强度</para>
/// </summary>
/// <remarks>
///<para>Must be greater than 0, the unit is in format.</para>
///<para>必须大于0,单位为格式。</para>
/// </remarks>
[Export] private float _repelStrength;
/// <summary>
/// <para>The master of the weapon</para>
/// <para>武器的主人</para>
/// </summary>
public new Node2D? Owner { get; set; }
public override void _Ready()
{
if (!_ignoreWall)
{
_wallRayCast = new RayCast2D();
_wallRayCast.SetCollisionMaskValue(Config.LayerNumber.Wall, true);
_wallRayCast.SetCollisionMaskValue(Config.LayerNumber.Floor, true);
NodeUtils.CallDeferredAddChild(this, _wallRayCast);
}
//If the existence time is less than or equal to 0, then it is set to exist for 10 seconds, and projectiles that exist indefinitely are prohibited
//如果存在时间小于等于0那么设置为存在10秒禁止无限期存在的抛射体
if (_life <= 0)
{
_life = 10000;
}
_destructionTime = DateTime.Now.AddMilliseconds(_life);
SetCollisionMaskValue(Config.LayerNumber.Wall, !_ignoreWall);
SetCollisionMaskValue(Config.LayerNumber.Floor, !_ignoreWall);
SetCollisionMaskValue(Config.LayerNumber.Player, true);
SetCollisionMaskValue(Config.LayerNumber.Mob, true);
SetCollisionMaskValue(Config.LayerNumber.PickAbleItem, true);
//Platform collision layer is not allowed to collide
//平台碰撞层不可碰撞
SetCollisionMaskValue(Config.LayerNumber.Platform, false);
if (TargetNode != null)
{
TargetNode.TreeExiting += () =>
{
//Clear the trace when the target is destroyed.
//在目标被销毁的时候清空跟踪。
TargetNode = null;
if (_targetDiesDestroyProjectile)
{
OnTimeOut();
}
};
}
}
/// <summary>
/// <para>Add decorator</para>
/// <para>添加装饰器</para>
/// </summary>
/// <param name="decorator">
///<para>decorator</para>
///<para>装饰器</para>
/// </param>
public void AddProjectileDecorator(IProjectileDecorator decorator)
{
_projectileDecorators ??= [];
_projectileDecorators.Add(decorator);
}
/// <summary>
/// <para>Remove decorator</para>
/// <para>移除装饰器</para>
/// </summary>
/// <param name="decorator">
///<para>decorator</para>
///<para>装饰器</para>
/// </param>
/// <returns></returns>
public bool RemoveProjectileDecorator(IProjectileDecorator decorator)
{
return _projectileDecorators?.Remove(decorator) ?? false;
}
/// <summary>
/// <para>Detect whether harm is allowed</para>
/// <para>检测是否允许造成伤害</para>
/// </summary>
/// <param name="owner"></param>
/// <param name="target"></param>
/// <returns></returns>
private bool CanCauseHarm(Node2D? owner, Node2D target)
{
//We must know who the owner of the bullet is in order to determine whether it should cause damage or not
//我们必须知道子弹的主人是谁,才能判断是否应该造成伤害
if (owner == null)
{
return false;
}
if (owner is not CharacterTemplate ownerCharacterTemplate)
{
return false;
}
if (target is TileMapLayer)
{
//When we hit the tile, we return true to prevent the bullet from penetrating the tile.
//撞击到瓦片时我们返回true是为了防止子弹穿透瓦片。
return true;
}
if (target is PickAbleTemplate pickAbleTemplate)
{
//The picked-up item cannot resist the bullet.
//被拾起的物品无法抵挡子弹。
return !pickAbleTemplate.Picked;
}
if (target is not CharacterTemplate characterTemplate)
{
return false;
}
//First get the owner's camp and compare it with the target camp
//先获取主人的阵营与目标阵营进行比较
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
return canCauseHarm;
}
/// <summary>
/// <para>Executive injury treatment</para>
/// <para>执行伤害处理</para>
/// </summary>
/// <param name="owner"></param>
/// <param name="target"></param>
private void DoDamage(Node2D? owner, Node2D target)
{
if (target is CharacterTemplate characterTemplate)
{
//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;
var dead = characterTemplate.Damage(damage);
if (dead)
{
//If the character is dead, then call OnKillCharacter
//如果角色死亡了那么调用OnKillCharacter
InvokeDecorators(decorator => { decorator.OnKillCharacter(owner, characterTemplate); });
}
if (_repelStrength > 0)
{
//If we set the attack force, then apply the force to the object
//如果我们设置了攻退力,那么将力应用到对象上
var normalized = Velocity.Normalized();
characterTemplate.AddForce(new Vector2(normalized.X * _repelStrength * Config.CellSize,
normalized.Y * _repelStrength * Config.CellSize));
}
}
else if (target is PickAbleTemplate pickAbleTemplate)
{
if (_repelStrength > 0)
{
//If we set the attack force, then apply the force to the object
//如果我们设置了攻退力,那么将力应用到对象上
var normalized = Velocity.Normalized();
pickAbleTemplate.ApplyImpulse(new Vector2(normalized.X * _repelStrength * Config.CellSize,
normalized.Y * _repelStrength * Config.CellSize));
}
}
}
/// <summary>
/// <para>Call the method of the decorator</para>
/// <para>调用装饰器的方法</para>
/// </summary>
/// <param name="projectileDecoratorAction"></param>
private void InvokeDecorators(Action<IProjectileDecorator> projectileDecoratorAction)
{
if (_projectileDecorators == null)
{
return;
}
foreach (var decorator in _projectileDecorators)
{
projectileDecoratorAction(decorator);
}
}
/// <summary>
/// <para>When beyond the time of existence</para>
/// <para>当超过存在时间</para>
/// </summary>
private void OnTimeOut()
{
QueueFree();
}
public override void _Process(double delta)
{
//If the existence time is exceeded, the projectile is destroyed
//如果超过了存在时间,那么销毁抛射体
if (DateTime.Now >= _destructionTime)
{
OnTimeOut();
}
}
public override void _PhysicsProcess(double delta)
{
var collisionInfo = MoveAndCollide(Velocity * (float)delta);
if (collisionInfo == null)
{
//No collision.
//没有撞到任何东西。
if (_enableTracking && TargetNode != null)
{
//Track the target
//追踪目标
//Gets a vector of the projectile pointing at the enemy's position.
//得到抛射体指向敌人位置的向量。
var desiredVelocity = TargetNode.GlobalPosition - GlobalPosition;
if (!_ignoreWall && _wallRayCast != null)
{
_wallRayCast!.TargetPosition = desiredVelocity;
if (_wallRayCast.IsColliding())
{
return;
}
}
var actualDesiredVelocity = desiredVelocity.Normalized() * Speed;
//The weight is smaller, the circle is larger.
//weight越小子弹绕的圈越大。
Velocity = Velocity.Lerp(actualDesiredVelocity, 0.1f);
}
}
else
{
//Bump into other objects.
//撞到其他对象。
if (_enableBounce)
{
Velocity = Velocity.Bounce(collisionInfo.GetNormal());
}
//Here we test whether harm is allowed, notice that for TileMap, we directly allow harm.
//这里我们检测是否允许造成伤害注意对于TileMap我们直接允许造成伤害。
var godotObject = collisionInfo.GetCollider();
var node = (Node2D)godotObject;
var canCauseHarm = CanCauseHarm(Owner, node);
if (!canCauseHarm)
{
return;
}
DoDamage(Owner, node);
//Please specify in the Mask who the bullet will collide with
//请在Mask内配置子弹会和谁碰撞
//When a bullet hits an object, its durability decreases
//子弹撞击到物体时,耐久度减少
_durability--;
if (_durability <= 0)
{
//When the durability is less than or equal to 0, destroy the bullet
//当耐久度小于等于0时销毁子弹
QueueFree();
}
}
}
}

View File

@ -1,260 +0,0 @@
using System;
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.inventory;
using ColdMint.scripts.pickable;
using Godot;
namespace ColdMint.scripts.projectile;
/// <summary>
/// <para>Projectile template</para>
/// <para>抛射体模板</para>
/// </summary>
public partial class ProjectileTemplate : CharacterBody2D
{
private long _life;
//The durability of the projectile
//抛射体的耐久度
//When the projectile hits the object, the durability will be reduced, and when the durability is less than or equal to 0, the projectile will be destroyed
//当抛射体撞击到物体时会减少耐久度当耐久度小于等于0时销毁抛射体
private double _durability;
private int _maxDamage;
private int _minDamage;
private int _damageType;
/// <summary>
/// <para>After this time destroy the projectile</para>
/// <para>超过此时刻销毁抛射体</para>
/// </summary>
private DateTime? _destructionTime;
/// <summary>
/// <para>The impact area of the bullet</para>
/// <para>子弹的碰撞区域</para>
/// </summary>
private Area2D? _area2D;
/// <summary>
/// <para>knockback</para>
/// <para>击退</para>
/// </summary>
/// <remarks>
///<para>How much force does it have when hitting the character? Unit: Number of cellsThe X direction of the force is inferred automatically.</para>
///<para>当击中角色时带有多大的力单位格数力的X方向是自动推断的。</para>
/// </remarks>
private Vector2 _knockbackForce;
public float Speed => GetMeta("Speed", "15").AsSingle();
/// <summary>
/// <para>The master of the weapon</para>
/// <para>武器的主人</para>
/// </summary>
public new Node2D? Owner { get; set; }
public override void _Ready()
{
//The bullet's impact detection area
//子弹的碰撞检测区域
_area2D = GetNode<Area2D>("CollisionDetectionArea");
_area2D.Monitoring = true;
_area2D.BodyEntered += OnBodyEnter;
_area2D.BodyExited += OnBodyExited;
_durability = GetMeta("Durability", "1").AsDouble();
_maxDamage = GetMeta("MaxDamage", "7").AsInt32();
_minDamage = GetMeta("MinDamage", "5").AsInt32();
_damageType = GetMeta("DamageType", Config.DamageType.Physical).AsInt32();
_knockbackForce = GetMeta("Knockback", Vector2.Zero).AsVector2();
//life(ms)
//子弹的存在时间(毫秒)
_life = GetMeta("Life", "10000").AsInt64();
//If the existence time is less than or equal to 0, then it is set to exist for 10 seconds, and projectiles that exist indefinitely are prohibited
//如果存在时间小于等于0那么设置为存在10秒禁止无限期存在的抛射体
if (_life <= 0)
{
_life = 10000;
}
_destructionTime = DateTime.Now.AddMilliseconds(_life);
}
/// <summary>
/// <para>Detect whether harm is allowed</para>
/// <para>检测是否允许造成伤害</para>
/// </summary>
/// <param name="owner"></param>
/// <param name="target"></param>
/// <returns></returns>
private bool CanCauseHarm(Node2D? owner, Node2D target)
{
//We must know who the owner of the bullet is in order to determine whether it should cause damage or not
//我们必须知道子弹的主人是谁,才能判断是否应该造成伤害
if (owner == null)
{
return false;
}
if (owner is not CharacterTemplate ownerCharacterTemplate)
{
return false;
}
if (target is TileMapLayer)
{
//When we hit the tile, we return true to prevent the bullet from penetrating the tile.
//撞击到瓦片时我们返回true是为了防止子弹穿透瓦片。
return true;
}
//Match any item now
//现在使它识别任何物品
if (target is IItem)
{
//Bullets are allowed to strike objects.
//允许子弹撞击物品。
return true;
}
if (target is not CharacterTemplate characterTemplate)
{
return false;
}
//First get the owner's camp and compare it with the target camp
//先获取主人的阵营与目标阵营进行比较
var canCauseHarm = CampManager.CanCauseHarm(CampManager.GetCamp(ownerCharacterTemplate.CampId),
CampManager.GetCamp(characterTemplate.CampId));
return canCauseHarm;
}
/// <summary>
/// <para>Executive injury treatment</para>
/// <para>执行伤害处理</para>
/// </summary>
/// <param name="owner"></param>
/// <param name="target"></param>
private void DoDamage(Node2D? owner, Node2D target)
{
if (target is CharacterTemplate characterTemplate)
{
//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)
{
//Beat back to port
//向左击退
forceX = -forceX;
}
force.X = forceX * Config.CellSize;
force.Y = _knockbackForce.Y * Config.CellSize;
characterTemplate.AddForce(force);
}
}
else if (target is PickAbleTemplate pickAbleTemplate)
{
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;
pickAbleTemplate.ApplyImpulse(force);
}
}
}
/// <summary>
/// <para>When the bullet is in contact with the node</para>
/// <para>当子弹与节点接触时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void OnBodyEnter(Node2D node)
{
//Here we test whether harm is allowed, notice that for TileMap, we directly allow harm.
//这里我们检测是否允许造成伤害注意对于TileMap我们直接允许造成伤害。
var canCauseHarm = CanCauseHarm(Owner, node);
if (!canCauseHarm)
{
return;
}
DoDamage(Owner, node);
//Please specify in the Mask who the bullet will collide with
//请在Mask内配置子弹会和谁碰撞
//When a bullet hits an object, its durability decreases
//子弹撞击到物体时,耐久度减少
_durability--;
if (_durability <= 0)
{
//When the durability is less than or equal to 0, destroy the bullet
//当耐久度小于等于0时销毁子弹
QueueFree();
}
}
/// <summary>
/// <para>When the bullet leaves the node</para>
/// <para>当子弹离开节点时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void OnBodyExited(Node2D node)
{
}
/// <summary>
/// <para>When beyond the time of existence</para>
/// <para>当超过存在时间</para>
/// </summary>
private void OnTimeOut()
{
QueueFree();
}
public override void _Process(double delta)
{
//If the existence time is exceeded, the projectile is destroyed
//如果超过了存在时间,那么销毁抛射体
if (DateTime.Now >= _destructionTime)
{
OnTimeOut();
}
}
public override void _PhysicsProcess(double delta)
{
MoveAndSlide();
}
}

View File

@ -0,0 +1,29 @@
using ColdMint.scripts.character;
using Godot;
namespace ColdMint.scripts.projectile.decorator;
/// <summary>
/// <para>Projectile decorator</para>
/// <para>抛射体装饰</para>
/// </summary>
/// <remarks>
///<para>Decorator mode is a structural mode, which can add special features to the projectile.</para>
///<para>装饰模式是一种结构性模式,可以将一种抛射体的特殊功能添加到抛射体上。</para>
/// </remarks>
public interface IProjectileDecorator
{
/// <summary>
/// <para>When the character is killed by this projectile</para>
/// <para>当角色被此抛射体击杀时</para>
/// </summary>
/// <param name="owner">
/// <para>owner</para>
/// <para>主人</para>
/// </param>
/// <param name="target">
///<para>target</para>
///<para>目标</para>
/// </param>
void OnKillCharacter(Node2D? owner, CharacterTemplate target);
}

View File

@ -0,0 +1,56 @@
using ColdMint.scripts.character;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.projectile.decorator;
/// <summary>
/// <para>NodeSpawnOnKillCharacterDecorator</para>
/// <para>在击杀角色后生成节点</para>
/// </summary>
public class NodeSpawnOnKillCharacterDecorator : IProjectileDecorator
{
/// <summary>
/// <para>PackedScenePath</para>
/// <para>要实例化的场景路径</para>
/// </summary>
public string? PackedScenePath { get; set; }
/// <summary>
/// <para>DefaultParentNode</para>
/// <para>默认的父节点</para>
/// </summary>
public Node? DefaultParentNode { get; set; }
/// <summary>
/// <para>Chance</para>
/// <para>生成概率</para>
/// </summary>
public float Chance { get; set; } = 1f;
public void OnKillCharacter(Node2D? owner, CharacterTemplate target)
{
if (RandomUtils.Instance.NextSingle() > Chance)
{
//Not in probability, straight back.
//没有在概率内,直接返回。
return;
}
if (PackedScenePath == null || DefaultParentNode == null || !target.CanMutateAfterDeath)
{
return;
}
var packedScene = GD.Load<PackedScene>(PackedScenePath);
var node2D = NodeUtils.InstantiatePackedScene<Node2D>(packedScene);
if (node2D == null)
{
return;
}
var container = NodeUtils.FindContainerNode(node2D, DefaultParentNode);
node2D.GlobalPosition = target.GlobalPosition;
NodeUtils.CallDeferredAddChild(container, node2D);
}
}

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ColdMint.scripts.utils;
/// <summary>
/// <para>Light layer utils</para>
/// <para>光照层工具</para>
/// </summary>
public static class LightMaskUtils
{
private static readonly int[] PowInts =
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288];
/// <summary>
/// <para>Pass in a number and return the largest subscript that matches it.</para>
/// <para>传入一个数字,返回与其匹配的最大下标</para>
/// </summary>
/// <param name="number"></param>
/// <param name="startIndex"></param>
/// <returns></returns>
private static int GetMaxPow(int number, int startIndex)
{
for (var i = startIndex - 1; i >= 0; i--)
{
var pow = PowInts[i];
if (number >= pow)
{
return i;
}
}
return -1;
}
/// <summary>
/// <para>ParseMaskValue</para>
/// <para>解析Mask的值</para>
/// </summary>
/// <param name="maskValue">
///<para>maskValue</para>
///<para>值</para>
/// </param>
/// <param name="afterParsingTheValue">
///<para>This callback returns true when the value is parsed, stopping the parsing immediately.</para>
///<para>当解析到值时此回调返回true立即停止解析。</para>
/// </param>
/// <returns>
///<para>The position of the element corresponding to its value, For example, passing in 10, returning [1,3]</para>
///<para>与其值对应的元素位置例如传入10,返回[1,3]</para>
/// </returns>
public static int[] ParseMaskValue(int maskValue, Func<int, bool>? afterParsingTheValue = null)
{
var result = new List<int>();
var startIndex = PowInts.Length;
var indexInPowIntArray = GetMaxPow(maskValue, startIndex);
while (indexInPowIntArray > -1)
{
result.Insert(0, indexInPowIntArray);
if (afterParsingTheValue != null)
{
if (afterParsingTheValue.Invoke(indexInPowIntArray))
{
//If it needs to be stopped, then the result is returned directly.
//如果需要停止,那么直接返回结果。
return result.ToArray();
}
}
maskValue -= PowInts[indexInPowIntArray];
startIndex = indexInPowIntArray;
indexInPowIntArray = GetMaxPow(maskValue, startIndex);
}
return result.ToArray();
}
/// <summary>
/// <para>Is there a location within the mask value?</para>
/// <para>mask值内是否包含某个位置</para>
/// </summary>
/// <param name="maskValue"></param>
/// <param name="index"></param>
/// <returns></returns>
public static bool ContainsMaskValue(int maskValue, int index)
{
var result = false;
ParseMaskValue(maskValue, i =>
{
result = i == index;
return result;
});
return result;
}
/// <summary>
/// <para>Add a location to MaskValue</para>
/// <para>为MaskValue添加某个位置</para>
/// </summary>
/// <param name="maskValue"></param>
/// <param name="index"></param>
/// <returns></returns>
public static int AddIndexToMaskValue(int maskValue, int index)
{
if (ContainsMaskValue(maskValue,index))
{
return maskValue;
}
return maskValue + PowInts[index];
}
/// <summary>
/// <para>Converting an array to its corresponding value</para>
/// <para>将数组转化为与其对应的值</para>
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static int ArrayToMaskValue(int[] array)
{
return array.Sum(index => PowInts[index]);
}
}

View File

@ -1,4 +1,5 @@
using System;
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.projectile;
using Godot;
@ -224,24 +225,29 @@ public static class NodeUtils
/// <returns></returns>
public static Node FindContainerNode(Node childNode, Node defaultParentNode)
{
if (GameSceneNodeHolder.ProjectileContainer != null && childNode is Projectile)
if (GameSceneDepend.AiCharacterContainer!= null && childNode is AiCharacter)
{
return GameSceneNodeHolder.ProjectileContainer;
return GameSceneDepend.AiCharacterContainer;
}
if (GameSceneNodeHolder.WeaponContainer != null && childNode is WeaponTemplate)
if (GameSceneDepend.ProjectileContainer != null && childNode is Projectile)
{
return GameSceneNodeHolder.WeaponContainer;
return GameSceneDepend.ProjectileContainer;
}
if (GameSceneNodeHolder.PacksackContainer != null && childNode is Packsack)
if (GameSceneDepend.WeaponContainer != null && childNode is WeaponTemplate)
{
return GameSceneNodeHolder.PacksackContainer;
return GameSceneDepend.WeaponContainer;
}
if (GameSceneNodeHolder.BackpackUiContainer != null && childNode is PacksackUi)
if (GameSceneDepend.PacksackContainer != null && childNode is Packsack)
{
return GameSceneNodeHolder.BackpackUiContainer;
return GameSceneDepend.PacksackContainer;
}
if (GameSceneDepend.BackpackUiContainer != null && childNode is PacksackUi)
{
return GameSceneDepend.BackpackUiContainer;
}
return defaultParentNode;

View File

@ -29,6 +29,6 @@ public static class TranslationServerUtils
/// <returns></returns>
public static string? Translate(string? key)
{
return TranslationServer.Translate(key);
return key == null ? null : TranslationServer.Translate(key);
}
}

View File

@ -1,5 +1,6 @@
using ColdMint.scripts.debug;
using ColdMint.scripts.projectile;
using ColdMint.scripts.projectile.decorator;
using ColdMint.scripts.utils;
using Godot;
@ -21,14 +22,105 @@ public partial class ProjectileWeapon : WeaponTemplate
/// </summary>
private Marker2D? _marker2D;
/// <summary>
/// <para>Scattering radians</para>
/// <para>散射弧度</para>
/// </summary>
[Export] protected float OffsetAngle;
/// <summary>
/// <para>Offset angle mode</para>
/// <para>偏移角度模式</para>
/// </summary>
[Export] protected int OffsetAngleMode = Config.OffsetAngleMode.Random;
/// <summary>
/// <para>Whether the last offset angle is positive</para>
/// <para>上次的偏移角度是否为正向的</para>
/// </summary>
private bool _positiveOffsetAngle = true;
/// <summary>
/// <para>The number of projectiles fired at once</para>
/// <para>一次可以发射多少个子弹</para>
/// </summary>
[Export] protected float NumberOfProjectiles = 1;
[Export] protected PackedScene[] ProjectileScenes { get; set; } = [];
/// <summary>
/// <para>Whether to launch in the order of the projectile list</para>
/// <para>是否按照抛射体列表的循序发射</para>
/// </summary>
[Export]
protected bool Sequentially { get; set; }
private int _projectileIndex;
public override void _Ready()
{
base._Ready();
_marker2D = GetNode<Marker2D>("Marker2D");
}
/// <summary>
/// <para>GetNextProjectileScene</para>
/// <para>获取下一个抛射体</para>
/// </summary>
/// <returns></returns>
private PackedScene GetNextProjectileScene()
{
if (Sequentially)
{
_projectileIndex = (_projectileIndex + 1) % ProjectileScenes.Length;
return ProjectileScenes[_projectileIndex];
}
else
{
return ProjectileScenes[RandomUtils.Instance.Next(ProjectileScenes.Length)];
}
}
/// <summary>
/// <para>GetRandomAngle</para>
/// <para>获取随机的偏移弧度</para>
/// </summary>
/// <returns></returns>
private float GetRandomAngle()
{
if (OffsetAngle == 0)
{
//If the offset angle is 0, then return 0
//弧度为0,不用偏移。
return 0;
}
if (OffsetAngleMode == Config.OffsetAngleMode.Cross)
{
float result;
if (_positiveOffsetAngle)
{
result = -OffsetAngle / 2;
}
else
{
result = OffsetAngle / 2;
}
_positiveOffsetAngle = !_positiveOffsetAngle;
return result;
}
if (OffsetAngleMode == Config.OffsetAngleMode.AlwaysSame)
{
return OffsetAngle;
}
var min = -OffsetAngle / 2;
return min + RandomUtils.Instance.NextSingle() * OffsetAngle;
}
protected override void DoFire(Node2D? owner, Vector2 enemyGlobalPosition)
{
@ -44,11 +136,12 @@ public partial class ProjectileWeapon : WeaponTemplate
return;
}
if (GameSceneNodeHolder.ProjectileContainer == null)
if (GameSceneDepend.ProjectileContainer == null)
{
LogCat.LogError("projectile_container_is_null");
return;
}
//Empty list check
//空列表检查
if (ProjectileScenes is [])
@ -59,13 +152,28 @@ public partial class ProjectileWeapon : WeaponTemplate
//Get the first projectile
//获取第一个抛射体
var projectileScene = ProjectileScenes[0];
// var projectileScene = _projectileCache[_projectiles[0]];
var projectile = NodeUtils.InstantiatePackedScene<ProjectileTemplate>(projectileScene);
var projectileScene = GetNextProjectileScene();
for (int i = 0; i < NumberOfProjectiles; i++)
{
var projectile = NodeUtils.InstantiatePackedScene<Projectile>(projectileScene);
if (projectile == null) return;
NodeUtils.CallDeferredAddChild(GameSceneNodeHolder.ProjectileContainer, projectile);
if (Config.IsDebug())
{
var nodeSpawnOnKillCharacterDecorator = new NodeSpawnOnKillCharacterDecorator
{
DefaultParentNode = this,
PackedScenePath = "res://prefab/entitys/BlackenedAboriginalWarrior.tscn"
};
projectile.AddProjectileDecorator(nodeSpawnOnKillCharacterDecorator);
}
NodeUtils.CallDeferredAddChild(GameSceneDepend.ProjectileContainer, projectile);
projectile.Owner = owner;
projectile.Velocity = (enemyGlobalPosition - _marker2D.GlobalPosition).Normalized() * projectile.Speed;
projectile.TargetNode = GameSceneDepend.TemporaryTargetNode;
projectile.Velocity =
(_marker2D.GlobalPosition.DirectionTo(enemyGlobalPosition) * projectile.Speed)
.Rotated(GetRandomAngle());
projectile.Position = _marker2D.GlobalPosition;
}
}
}

View File

@ -38,8 +38,11 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
/// <para>开火间隔</para>
/// </summary>
private TimeSpan _firingInterval;
private long _firingIntervalAsMillisecond = 100;
[Export] protected long FiringIntervalAsMillisecond
[Export]
protected long FiringIntervalAsMillisecond
{
get => _firingIntervalAsMillisecond;
set
@ -58,7 +61,7 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
///<para>When the weapon is fired, how much recoil is applied to the user, in units: the number of cells, and the X direction of the force is automatically inferred.</para>
///<para>武器开火要对使用者施加多大的后坐力单位格数力的X方向是自动推断的。</para>
/// </remarks>
[Export] private Vector2 _recoil;
[Export] private long _recoilStrength;
/// <summary>
/// <para>Discharge of the weapon</para>
@ -88,19 +91,9 @@ public abstract partial class WeaponTemplate : PickAbleTemplate
{
//We check the recoil of the weapon before each firing.
//我们在每次开火之前,检查武器的后坐力。
if (_recoil != Vector2.Zero)
if (_recoilStrength != 0)
{
var force = new Vector2();
var forceX = Math.Abs(_recoil.X);
if (Math.Abs(RotationDegrees) < 90)
{
//The weapon goes to the right, and we apply a recoil to the left
//武器朝向右边我们向左施加后坐力
forceX = -forceX;
}
force.X = forceX * Config.CellSize;
force.Y = _recoil.Y * Config.CellSize;
var force = -characterTemplate.GlobalPosition.DirectionTo(enemyGlobalPosition) * _recoilStrength * Config.CellSize;
characterTemplate.AddForce(force);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://e6670ykyq145"
path="res://.godot/imported/staffOfTheUndead.png-9bf2297abccc48c9610807d69d632153.ctex"
uid="uid://di0sw4rgd26y0"
path="res://.godot/imported/PortableBackpacks.png-a6ce21006145ee0b58e687b1801240a6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/weapon/staffOfTheUndead.png"
dest_files=["res://.godot/imported/staffOfTheUndead.png-9bf2297abccc48c9610807d69d632153.ctex"]
source_file="res://sprites/PortableBackpacks.png"
dest_files=["res://.godot/imported/PortableBackpacks.png-a6ce21006145ee0b58e687b1801240a6.ctex"]
[params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c1yxhtiqrfvvp"
path="res://.godot/imported/BlackenedAboriginalWarrior.png-483f0928238bc97273aa2fd838825f33.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/character/BlackenedAboriginalWarrior.png"
dest_files=["res://.godot/imported/BlackenedAboriginalWarrior.png-483f0928238bc97273aa2fd838825f33.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
sprites/light/White_100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drw45jlmfo0su"
path="res://.godot/imported/White_100.png-d319ba9988ade64ae599ad90841c52c7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/light/White_100.png"
dest_files=["res://.godot/imported/White_100.png-d319ba9988ade64ae599ad90841c52c7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
sprites/light/White_25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

View File

@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dvx10dfjctn7t"
path="res://.godot/imported/packsack.png-ba078a68a3754f332c41ffcad073a395.ctex"
uid="uid://diqf0as7rn18v"
path="res://.godot/imported/White_25.png-584d94cc8c53e39250e249a7f385cbb4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/packsack.png"
dest_files=["res://.godot/imported/packsack.png-ba078a68a3754f332c41ffcad073a395.ctex"]
source_file="res://sprites/light/White_25.png"
dest_files=["res://.godot/imported/White_25.png-584d94cc8c53e39250e249a7f385cbb4.ctex"]
[params]

BIN
sprites/ui/MiniMapBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c35bsle7thcnh"
path="res://.godot/imported/MiniMapBg.png-d798deffd210ae6fbca7a397a2bc688e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/ui/MiniMapBg.png"
dest_files=["res://.godot/imported/MiniMapBg.png-d798deffd210ae6fbca7a397a2bc688e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://wt50kx6bup51"
path="res://.godot/imported/StaffNecromancy.png-82a6bb745d120d5cf5445c4e669e3c6d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/weapon/StaffNecromancy.png"
dest_files=["res://.godot/imported/StaffNecromancy.png-82a6bb745d120d5cf5445c4e669e3c6d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dg5vwprt66w4j"
path="res://.godot/imported/staffOfTheUndead_icon.png-4ca5c1341e2dac55b2ef4a0fc05b6e6c.ctex"
path="res://.godot/imported/StaffNecromancy_Icon.png-b792107cbd73ce862a81c86021ad2001.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/weapon/staffOfTheUndead_icon.png"
dest_files=["res://.godot/imported/staffOfTheUndead_icon.png-4ca5c1341e2dac55b2ef4a0fc05b6e6c.ctex"]
source_file="res://sprites/weapon/StaffNecromancy_Icon.png"
dest_files=["res://.godot/imported/StaffNecromancy_Icon.png-b792107cbd73ce862a81c86021ad2001.ctex"]
[params]

View File

@ -6,14 +6,14 @@
texture = ExtResource("1_4c2am")
texture_region_size = Vector2i(32, 32)
0:0/0 = 0
0:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
0:0/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
1:0/0 = 0
1:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
1:1/0 = 0
2:1/0 = 0
2:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
2:1/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
2:2/0 = 0
2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
2:2/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
3:2/0 = 0
3:4/0 = 0
3:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, -6.75, 8.25, -6, 7, -2.25, 1, -2, -16, 16)
@ -27,17 +27,18 @@ texture_region_size = Vector2i(32, 32)
1:3/0 = 0
1:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
1:2/0 = 0
1:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
1:2/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
0:2/0 = 0
0:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
0:2/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
0:1/0 = 0
0:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
0:1/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
2:3/0 = 0
2:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
3:1/0 = 0
3:0/0 = 0
2:0/0 = 0
2:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
2:0/0/physics_layer_2/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
4:0/0 = 0
4:1/0 = 0
5:0/0 = 0
@ -86,4 +87,6 @@ physics_layer_0/collision_layer = 2
physics_layer_0/collision_mask = 0
physics_layer_1/collision_layer = 32
physics_layer_1/collision_mask = 0
physics_layer_2/collision_layer = 128
physics_layer_2/collision_mask = 0
sources/1 = SubResource("TileSetAtlasSource_abxy2")