Initial commit

初始提交
This commit is contained in:
Cold-Mint 2024-04-28 21:55:19 +08:00
commit 35aa4ce85b
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
164 changed files with 7562 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Godot 4+ specific ignores
.godot/
export_presets.cfg
.idea/

14
ColdMint.Traveler.csproj Normal file
View File

@ -0,0 +1,14 @@
<Project Sdk="Godot.NET.Sdk/4.2.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>ColdMint</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.29" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
</ItemGroup>
</Project>

19
ColdMint.Traveler.sln Normal file
View File

@ -0,0 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColdMint.Traveler", "ColdMint.Traveler.csproj", "{73860AFB-EAF0-42BE-B373-073DC6417BDA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportRelease|Any CPU = ExportRelease|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{73860AFB-EAF0-42BE-B373-073DC6417BDA}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
EndGlobalSection
EndGlobal

Binary file not shown.

View File

@ -0,0 +1,33 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://cbb0bh8j0jbn0"
path="res://.godot/imported/ark-pixel-12px-proportional-zh_cn.ttf-478ad476d12cd787ee651b8790ca25e2.fontdata"
[deps]
source_file="res://fonts/ark-pixel-12px-proportional-zh_cn.ttf"
dest_files=["res://.godot/imported/ark-pixel-12px-proportional-zh_cn.ttf-478ad476d12cd787ee651b8790ca25e2.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

1
icon.svg Normal file
View File

@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>

After

Width:  |  Height:  |  Size: 950 B

37
icon.svg.import Normal file
View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2blj0yf4ohx3"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

7
locals/Error.csv Normal file
View File

@ -0,0 +1,7 @@
id,zh,en
Object reference not set to an instance of an object.,尝试在空对象上调用虚拟方法。,Object reference not set to an instance of an object.
missing_parameters,缺少参数。,Missing parameters.
room_root_node_must_be_node2d,房间根节点必须是 Node2D。,Room root node must be Node2D.
width_or_height_of_room_slot_must_be_1,房间槽的宽度或高度必须为1。,The width or height of the room slot must be 1.
connected_room_timeout,连接房间超时。,Connecting the room timed out.
projectiles_is_empty,未设置抛射体。,The projectile is not set.
1 id zh en
2 Object reference not set to an instance of an object. 尝试在空对象上调用虚拟方法。 Object reference not set to an instance of an object.
3 missing_parameters 缺少参数。 Missing parameters.
4 room_root_node_must_be_node2d 房间根节点必须是 Node2D。 Room root node must be Node2D.
5 width_or_height_of_room_slot_must_be_1 房间槽的宽度或高度必须为1。 The width or height of the room slot must be 1.
6 connected_room_timeout 连接房间超时。 Connecting the room timed out.
7 projectiles_is_empty 未设置抛射体。 The projectile is not set.

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

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

BIN
locals/Error.en.translation Normal file

Binary file not shown.

BIN
locals/Error.zh.translation Normal file

Binary file not shown.

2
locals/InputMapping.csv Normal file
View File

@ -0,0 +1,2 @@
id,zh,en
Left Mouse Button,鼠标左键,Left Mouse Button
1 id zh en
2 Left Mouse Button 鼠标左键 Left Mouse Button

View File

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

Binary file not shown.

Binary file not shown.

8
locals/Log.csv Normal file
View File

@ -0,0 +1,8 @@
id,zh,en
data_packet_missing_id,位于{0}的数据包缺少Id无法加载。,"Packet at {0}, missing Id, unable to load."
index_is_up_to_date,位于{0}的数据包索引已是最新。,"The packet index at {0} is up to date."
build_an_index,为{0}构建索引。,"Build an index for {0}."
add_file_index,添加文件索引{0}。,"Add file index {0}."
index_updated,{0}索引已更新。,"{0} Index has been updated."
no_manifest_file,位于{0}的数据包,没有清单文件,无法加载。,"Packet located in {0}, no manifest file, cannot be loaded."
duplicate_at_path_id,位于{0}路径{1}的Id({2})已被占用,无法加载。,"Id({2}) in {0} path {1} is occupied and cannot be loaded."
1 id zh en
2 data_packet_missing_id 位于{0}的数据包,缺少Id,无法加载。 Packet at {0}, missing Id, unable to load.
3 index_is_up_to_date 位于{0}的数据包索引已是最新。 The packet index at {0} is up to date.
4 build_an_index 为{0}构建索引。 Build an index for {0}.
5 add_file_index 添加文件索引{0}。 Add file index {0}.
6 index_updated {0}索引已更新。 {0} Index has been updated.
7 no_manifest_file 位于{0}的数据包,没有清单文件,无法加载。 Packet located in {0}, no manifest file, cannot be loaded.
8 duplicate_at_path_id 位于{0}路径{1}的Id({2})已被占用,无法加载。 Id({2}) in {0} path {1} is occupied and cannot be loaded.

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

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

BIN
locals/Log.en.translation Normal file

Binary file not shown.

BIN
locals/Log.zh.translation Normal file

Binary file not shown.

14
locals/UI.csv Normal file
View File

@ -0,0 +1,14 @@
id,zh,en
product_name,异界旅人,A traveler from another world
start_game,开始游戏,Start game
pick_up,拾捡,Pick up
move_left,向左移动,Move left
move_right,向右移动,Move right
jump,跳跃,Jump
throw,抛出,Throw a
must_be_thrown,必须先抛出{0}才能拾捡{1},{0} must be thrown before {1} can be picked up.
use_item,使用,Use
jump_down,跳下平台,Jump off platform
de,的,'s
default_player_name,白纸,blankPaper
unknown,未知,Unknown
1 id zh en
2 product_name 异界旅人 A traveler from another world
3 start_game 开始游戏 Start game
4 pick_up 拾捡 Pick up
5 move_left 向左移动 Move left
6 move_right 向右移动 Move right
7 jump 跳跃 Jump
8 throw 抛出 Throw a
9 must_be_thrown 必须先抛出{0}才能拾捡{1} {0} must be thrown before {1} can be picked up.
10 use_item 使用 Use
11 jump_down 跳下平台 Jump off platform
12 de 's
13 default_player_name 白纸 blankPaper
14 unknown 未知 Unknown

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

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

BIN
locals/UI.en.translation Normal file

Binary file not shown.

BIN
locals/UI.zh.translation Normal file

Binary file not shown.

2
locals/Weapon.csv Normal file
View File

@ -0,0 +1,2 @@
id,zh,en
staff_of_the_undead,死灵法杖,StaffOfTheUndead
1 id zh en
2 staff_of_the_undead 死灵法杖 StaffOfTheUndead

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

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

Binary file not shown.

Binary file not shown.

13
locals/slogan.csv Normal file
View File

@ -0,0 +1,13 @@
id,zh,en
slogan_1,如果是你,你会选择金钱还是荣誉?,"If it were you, would you choose money or honor?"
slogan_2,游戏属于每一个人。,The game belongs to everyone.
slogan_3,如果你想要得到爱,你就播种爱。,"If you want love, you sow love."
slogan_4,作为自然法则,死亡是每一个人人生的最终归宿。,"As a law of nature, everyone will end up dead."
slogan_5,光阴似箭。,tempus fugit.
slogan_6,快乐?伤心?痛苦?,Happy? Sad? Pain?
slogan_7,在图像和音乐中酝酿感情。,Brewing emotions in images and music.
slogan_8,罪与罚。,crime and punishment.
slogan_9,高桥李依!,Rie Takahashi!
slogan_10,0001 0011 0001 0101 0100 1111 0100,0001 0011 0001 0101 0100 1111 0100
slogan_11,恋愛偏差値上昇中!-P丸様。,恋愛偏差値上昇中!-P丸様。
slogan_12,kaWaYi!,kaWaYi!
1 id zh en
2 slogan_1 如果是你,你会选择金钱还是荣誉? If it were you, would you choose money or honor?
3 slogan_2 游戏属于每一个人。 The game belongs to everyone.
4 slogan_3 如果你想要得到爱,你就播种爱。 If you want love, you sow love.
5 slogan_4 作为自然法则,死亡是每一个人人生的最终归宿。 As a law of nature, everyone will end up dead.
6 slogan_5 光阴似箭。 tempus fugit.
7 slogan_6 快乐?伤心?痛苦? Happy? Sad? Pain?
8 slogan_7 在图像和音乐中酝酿感情。 Brewing emotions in images and music.
9 slogan_8 罪与罚。 crime and punishment.
10 slogan_9 高桥李依! Rie Takahashi!
11 slogan_10 0001 0011 0001 0101 0100 1111 0100 0001 0011 0001 0101 0100 1111 0100
12 slogan_11 恋愛偏差値上昇中!-P丸様。 恋愛偏差値上昇中!-P丸様。
13 slogan_12 kaWaYi! kaWaYi!

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

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

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,60 @@
[gd_scene load_steps=7 format=3 uid="uid://cj65pso40syj5"]
[ext_resource type="Script" path="res://scripts/character/Player.cs" id="1_1dlls"]
[ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_eha68"]
[ext_resource type="Script" path="res://scripts/damage/DamageNumberNodeSpawn.cs" id="3_lrmsw"]
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bb8wt"]
radius = 21.0
height = 58.0
[sub_resource type="CircleShape2D" id="CircleShape2D_vmqbt"]
radius = 43.566
[sub_resource type="SpriteFrames" id="SpriteFrames_qumby"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2_eha68")
}],
"loop": true,
"name": &"default",
"speed": 5.0
}]
[node name="Player" type="CharacterBody2D"]
collision_layer = 4
collision_mask = 34
script = ExtResource("1_1dlls")
metadata/CampId = "Default"
metadata/MaxHp = 12
[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="."]
[node name="Area2DPickingArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickingArea"]
shape = SubResource("CircleShape2D_vmqbt")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
sprite_frames = SubResource("SpriteFrames_qumby")
[node name="Parabola" type="Line2D" parent="."]
width = 5.0
default_color = Color(0.858824, 0.65098, 0.682353, 1)
[node name="PlatformDetectionRayCast" type="RayCast2D" parent="."]
collision_mask = 32
[node name="ItemMarker2D" type="Marker2D" parent="."]
position = Vector2(15, 20)
[node name="DamageNumber" type="Marker2D" parent="."]
position = Vector2(0, -47)
script = ExtResource("3_lrmsw")

View File

@ -0,0 +1,82 @@
[gd_scene load_steps=10 format=3 uid="uid://cj65pso40syj5"]
[ext_resource type="Script" path="res://scripts/character/AICharacter.cs" id="1_hym0i"]
[ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_eha68"]
[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/behaviorTree/BehaviorNode.cs" id="5_h6w2s"]
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bb8wt"]
radius = 20.0
height = 52.0
[sub_resource type="CircleShape2D" id="CircleShape2D_vmqbt"]
radius = 34.5398
[sub_resource type="SpriteFrames" id="SpriteFrames_qumby"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2_eha68")
}],
"loop": true,
"name": &"default",
"speed": 5.0
}]
[sub_resource type="CircleShape2D" id="CircleShape2D_c61vr"]
radius = 129.027
[node name="DelivererOfDarkMagic" type="CharacterBody2D"]
collision_layer = 64
collision_mask = 38
script = ExtResource("1_hym0i")
metadata/CampId = "Mazoku"
metadata/MaxHp = 99999
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_mask = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickingArea"]
shape = SubResource("CircleShape2D_vmqbt")
[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="Behavior" type="Node2D" parent="."]
script = ExtResource("5_h6w2s")
[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")

View File

@ -0,0 +1,32 @@
[gd_scene load_steps=5 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/Life = 10.0
metadata/Durability = 1.0
metadata/DamageType = 2
metadata/Knockback = Vector2(2, -3)
[node name="CurseOfTheUndead" type="Sprite2D" parent="."]
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")

View File

@ -0,0 +1,46 @@
[gd_scene load_steps=5 format=3 uid="uid://b0uurp551pku"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_a15hy"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(508.75, 191)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_o85u0"]
size = Vector2(20, 48)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_30r3c"]
size = Vector2(20, 46)
[node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."]
tile_set = ExtResource("1_a15hy")
format = 2
layer_0/name = "BackgroundWall"
layer_0/navigation_enabled = false
layer_0/tile_data = PackedInt32Array(65550, 393217, 5, 131086, 393217, 5, 196622, 393217, 5, 262158, 393217, 5, 262157, 393217, 5, 196621, 393217, 5, 131085, 393217, 5, 65549, 393217, 5, 65548, 393217, 5, 65547, 393217, 5, 65546, 393217, 5, 65545, 393217, 5, 65544, 393217, 5, 65543, 393217, 5, 65542, 393217, 5, 65541, 393217, 5, 65540, 393217, 5, 65539, 393217, 5, 65538, 393217, 5, 65537, 393217, 5, 131073, 393217, 5, 131074, 393217, 5, 131075, 393217, 5, 131076, 393217, 5, 131077, 393217, 5, 131078, 393217, 5, 131079, 393217, 5, 131080, 393217, 5, 131081, 393217, 5, 131082, 393217, 5, 131083, 393217, 5, 131084, 393217, 5, 196620, 393217, 5, 196619, 393217, 5, 196618, 393217, 5, 196617, 393217, 5, 196616, 262145, 5, 196615, 393217, 5, 196614, 393217, 5, 196613, 262145, 5, 196612, 393217, 5, 196611, 393217, 5, 196610, 393217, 5, 196609, 393217, 5, 262145, 393217, 5, 262146, 393217, 5, 262147, 393217, 5, 262148, 393217, 5, 262149, 393217, 5, 262150, 393217, 5, 262151, 393217, 5, 262152, 393217, 5, 262153, 393217, 5, 262154, 393217, 5, 262155, 393217, 5, 262156, 393217, 5, 196608, 393217, 5, 262144, 393217, 5, 262159, 393217, 5, 196623, 393217, 5)
layer_1/name = "BackgroundDecoration"
layer_1/tile_data = PackedInt32Array()
layer_2/name = "Ground"
layer_2/tile_data = PackedInt32Array(0, 1, 3, 65536, 131073, 1, 131072, 131073, 1, 1, 65537, 2, 2, 65537, 2, 3, 65537, 2, 4, 65537, 2, 5, 65537, 2, 6, 65537, 2, 7, 65537, 2, 8, 65537, 2, 9, 65537, 2, 10, 65537, 2, 11, 65537, 2, 12, 65537, 2, 13, 65537, 2, 14, 65537, 3, 15, 131073, 3, 65551, 131073, 4, 131087, 131073, 4, 327681, 65537, 0, 327682, 65537, 0, 327683, 65537, 0, 327684, 65537, 0, 327685, 65537, 0, 327686, 65537, 0, 327687, 65537, 0, 327688, 65537, 0, 327689, 65537, 0, 327690, 65537, 0, 327691, 65537, 0, 327692, 65537, 0, 327693, 65537, 0, 327694, 65537, 0, 327680, 1, 5, 327695, 131073, 5)
[node name="RoomArea" type="Area2D" parent="."]
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(256.625, 98.5)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
[node name="Area2D" type="Area2D" parent="RoomSlotList"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D"]
position = Vector2(17, 129)
shape = SubResource("RectangleShape2D_o85u0")
[node name="Area2D2" type="Area2D" parent="RoomSlotList"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D2"]
position = Vector2(498, 128)
shape = SubResource("RectangleShape2D_30r3c")

View File

@ -0,0 +1,55 @@
[gd_scene load_steps=6 format=3 uid="uid://dslr5tdbp4noq"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(508.75, 191)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_o85u0"]
size = Vector2(20, 48)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_30r3c"]
size = Vector2(20, 46)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_x4kt2"]
size = Vector2(46, 20)
[node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."]
tile_set = ExtResource("1_rn2om")
format = 2
layer_0/name = "BackgroundWall"
layer_0/navigation_enabled = false
layer_0/tile_data = PackedInt32Array(65550, 393217, 5, 131086, 393217, 5, 196622, 393217, 5, 262158, 393217, 5, 262157, 393217, 5, 196621, 393217, 5, 131085, 393217, 5, 65549, 393217, 5, 65548, 393217, 5, 65547, 393217, 5, 65546, 393217, 5, 65545, 393217, 5, 65544, 393217, 5, 65543, 393217, 5, 65542, 393217, 5, 65541, 393217, 5, 65540, 393217, 5, 65539, 393217, 5, 65538, 393217, 5, 65537, 393217, 5, 131073, 393217, 5, 131074, 393217, 5, 131075, 393217, 5, 131076, 393217, 5, 131077, 393217, 5, 131078, 393217, 5, 131079, 393217, 5, 131080, 393217, 5, 131081, 393217, 5, 131082, 393217, 5, 131083, 393217, 5, 131084, 393217, 5, 196620, 393217, 5, 196619, 393217, 5, 196618, 393217, 5, 196617, 393217, 5, 196616, 262145, 5, 196615, 393217, 5, 196614, 393217, 5, 196613, 262145, 5, 196612, 393217, 5, 196611, 393217, 5, 196610, 393217, 5, 196609, 393217, 5, 262145, 393217, 5, 262146, 393217, 5, 262147, 393217, 5, 262148, 393217, 5, 262149, 393217, 5, 262150, 393217, 5, 262151, 393217, 5, 262152, 393217, 5, 262153, 393217, 5, 262154, 393217, 5, 262155, 393217, 5, 262156, 393217, 5, 196608, 393217, 5, 262144, 393217, 5, 262159, 393217, 5, 196623, 393217, 5, 327686, 393217, 5, 327687, 393217, 5)
layer_1/name = "BackgroundDecoration"
layer_1/tile_data = PackedInt32Array()
layer_2/name = "Ground"
layer_2/tile_data = PackedInt32Array(0, 1, 3, 65536, 131073, 1, 131072, 131073, 1, 1, 65537, 2, 2, 65537, 2, 3, 65537, 2, 4, 65537, 2, 5, 65537, 2, 6, 65537, 2, 7, 65537, 2, 8, 65537, 2, 9, 65537, 2, 10, 65537, 2, 11, 65537, 2, 12, 65537, 2, 13, 65537, 2, 14, 65537, 3, 15, 131073, 3, 65551, 131073, 4, 131087, 131073, 4, 327681, 65537, 0, 327682, 65537, 0, 327683, 65537, 0, 327684, 65537, 0, 327685, 65537, 0, 327688, 65537, 0, 327689, 65537, 0, 327690, 65537, 0, 327691, 65537, 0, 327692, 65537, 0, 327693, 65537, 0, 327694, 65537, 0, 327680, 1, 5, 327695, 131073, 5, 327686, 262145, 4, 327687, 262145, 4)
[node name="RoomArea" type="Area2D" parent="."]
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(256.625, 98.5)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
[node name="Area2D" type="Area2D" parent="RoomSlotList"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D"]
position = Vector2(17, 129)
shape = SubResource("RectangleShape2D_o85u0")
[node name="Area2D2" type="Area2D" parent="RoomSlotList"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D2"]
position = Vector2(498, 128)
shape = SubResource("RectangleShape2D_30r3c")
[node name="Area2D3" type="Area2D" parent="RoomSlotList"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D3"]
position = Vector2(224, 178)
shape = SubResource("RectangleShape2D_x4kt2")

View File

@ -0,0 +1,38 @@
[gd_scene load_steps=4 format=3 uid="uid://du5ldsp613fei"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(511.5, 254)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jxmys"]
size = Vector2(18, 57.75)
[node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."]
tile_set = ExtResource("1_rn2om")
format = 2
layer_0/name = "BackgroundWall"
layer_0/navigation_enabled = false
layer_0/tile_data = PackedInt32Array(393230, 393217, 5, 393229, 393217, 5, 393228, 393217, 5, 393227, 393217, 5, 393226, 393217, 5, 393225, 393217, 5, 393224, 393217, 5, 393223, 393217, 5, 393222, 393217, 5, 65550, 393217, 5, 131086, 393217, 5, 196622, 393217, 5, 262158, 393217, 5, 327694, 393217, 5, 327693, 393217, 5, 262157, 393217, 5, 196621, 393217, 5, 131085, 393217, 5, 65549, 393217, 5, 65548, 393217, 5, 65547, 393217, 5, 65546, 393217, 5, 65545, 393217, 5, 65544, 393217, 5, 65543, 393217, 5, 65542, 393217, 5, 65541, 393217, 5, 65540, 393217, 5, 65539, 393217, 5, 65538, 393217, 5, 65537, 393217, 5, 131073, 393217, 5, 131074, 393217, 5, 131075, 393217, 5, 131076, 393217, 5, 131077, 393217, 5, 131078, 393217, 5, 131079, 393217, 5, 131080, 393217, 5, 131081, 393217, 5, 131082, 393217, 5, 131083, 393217, 5, 131084, 393217, 5, 196620, 393217, 5, 196619, 393217, 5, 196618, 393217, 5, 196617, 393217, 5, 196616, 262145, 5, 196615, 393217, 5, 196614, 393217, 5, 196613, 262145, 5, 196612, 393217, 5, 196611, 393217, 5, 196610, 393217, 5, 196609, 393217, 5, 262145, 393217, 5, 262146, 393217, 5, 262147, 393217, 5, 262148, 393217, 5, 262149, 393217, 5, 262150, 393217, 5, 262151, 393217, 5, 262152, 393217, 5, 262153, 393217, 5, 262154, 393217, 5, 262155, 393217, 5, 262156, 393217, 5, 327692, 393217, 5, 327691, 393217, 5, 327690, 393217, 5, 327689, 393217, 5, 327688, 393217, 5, 327687, 393217, 5, 327686, 393217, 5, 327685, 393217, 5, 327684, 393217, 5, 327683, 393217, 5, 327682, 393217, 5, 327681, 393217, 5, 393217, 393217, 5, 393218, 393217, 5, 393219, 393217, 5, 393220, 393217, 5, 393221, 393217, 5, 393231, 393217, 5, 327695, 393217, 5)
layer_1/name = "BackgroundDecoration"
layer_1/tile_data = PackedInt32Array(393218, 458753, 5, 65550, 458753, 4)
layer_2/name = "Ground"
layer_2/tile_data = PackedInt32Array(0, 1, 3, 65536, 131073, 1, 131072, 131073, 1, 196608, 131073, 1, 262144, 131073, 1, 458752, 1, 5, 1, 65537, 2, 458753, 65537, 0, 2, 65537, 2, 458754, 65537, 0, 3, 65537, 2, 458755, 65537, 0, 4, 65537, 2, 458756, 65537, 0, 5, 65537, 2, 458757, 65537, 0, 6, 65537, 2, 458758, 65537, 0, 7, 65537, 2, 458759, 65537, 0, 8, 65537, 2, 9, 65537, 2, 458761, 65537, 0, 10, 65537, 2, 458762, 65537, 0, 11, 65537, 2, 458763, 65537, 0, 12, 65537, 2, 458764, 65537, 0, 13, 65537, 2, 458765, 65537, 0, 14, 65537, 3, 458766, 65537, 0, 15, 131073, 3, 65551, 131073, 4, 131087, 131073, 4, 196623, 131073, 4, 262159, 131073, 4, 458767, 131073, 5, 458760, 65537, 0, 327680, 131073, 1, 393216, 131073, 1)
[node name="RoomArea" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomArea"]
position = Vector2(257, 129)
shape = SubResource("RectangleShape2D_kiih8")
[node name="RoomSlotList" type="Node2D" parent="."]
[node name="Slot1" type="Area2D" parent="RoomSlotList"]
position = Vector2(491, 193)
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Slot1"]
position = Vector2(5, 0)
shape = SubResource("RectangleShape2D_jxmys")
debug_color = Color(0.854902, 0.14902, 0.823529, 0.419608)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://bci3swhs1crfb"]
[ext_resource type="Script" path="res://scripts/damage/DamageNumber.cs" id="1_fui0m"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_vq24w"]
[node name="DamageNumber" type="CharacterBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_fui0m")
[node name="Label" type="Label" parent="."]
offset_right = 40.0
offset_bottom = 23.0
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_vq24w")
[node name="VisibleOnScreenNotifier2D" type="VisibleOnScreenNotifier2D" parent="."]

15
prefab/ui/FloatLabel.tscn Normal file
View File

@ -0,0 +1,15 @@
[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="."]
z_index = 1
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0

19
prefab/ui/HealthBar.tscn Normal file
View File

@ -0,0 +1,19 @@
[gd_scene load_steps=5 format=3 uid="uid://sqqfrmikmk5v"]
[ext_resource type="Texture2D" uid="uid://bptnlrbqoc5qw" path="res://sprites/progressBar/Under.png" id="1_sc0v3"]
[ext_resource type="Texture2D" uid="uid://i6st7ohpjkwu" path="res://sprites/progressBar/Over.png" id="2_ay5vh"]
[ext_resource type="Texture2D" uid="uid://cfky5jupwflnv" path="res://sprites/progressBar/Progress.png" id="2_s0gle"]
[ext_resource type="Script" path="res://scripts/health/HealthBar.cs" id="4_84gre"]
[node name="HealthBar" type="TextureProgressBar"]
offset_right = 96.0
offset_bottom = 12.0
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

46
prefab/ui/ItemSlot.tscn Normal file
View File

@ -0,0 +1,46 @@
[gd_scene load_steps=3 format=3 uid="uid://d2i4udh0hho41"]
[ext_resource type="Script" path="res://scripts/inventory/ItemSlotNode.cs" id="1_fbwot"]
[ext_resource type="Texture2D" uid="uid://dmthsdg1wx318" path="res://sprites/ui/ItemBarEmpty.png" id="1_y2wyt"]
[node name="ItemSlot" type="MarginContainer"]
offset_right = 38.0
offset_bottom = 38.0
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
script = ExtResource("1_fbwot")
[node name="BackgroundTexture" type="TextureRect" parent="."]
layout_mode = 2
texture = ExtResource("1_y2wyt")
[node name="CenterContainer" type="CenterContainer" parent="BackgroundTexture"]
layout_mode = 2
offset_right = 160.0
offset_bottom = 160.0
grow_horizontal = 2
grow_vertical = 2
scale = Vector2(0.2, 0.2)
[node name="IconTextureRect" type="TextureRect" parent="BackgroundTexture/CenterContainer"]
layout_mode = 2
[node name="Control" type="Control" parent="."]
layout_mode = 2
[node name="QuantityLabel" type="Label" parent="Control"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -1.0
offset_top = -14.0
grow_horizontal = 0
grow_vertical = 0
size_flags_horizontal = 8
size_flags_vertical = 8
theme_override_font_sizes/font_size = 10

View File

@ -0,0 +1,45 @@
[gd_scene load_steps=5 format=3 uid="uid://dnnn2xyayiehk"]
[ext_resource type="Texture2D" uid="uid://e6670ykyq145" path="res://sprites/weapon/staffOfTheUndead.png" id="1_ms3us"]
[ext_resource type="Script" path="res://scripts/weapon/ProjectileWeapon.cs" id="1_w8hhv"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_obcq2"]
size = Vector2(49, 5)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_14m1g"]
size = Vector2(49, 5.25)
[node name="StaffOfTheUndead" type="RigidBody2D"]
collision_layer = 8
collision_mask = 34
script = ExtResource("1_w8hhv")
metadata/Projectiles = PackedStringArray("res://prefab/projectile/curseOfTheUndead.tscn")
metadata/Name = "staff_of_the_undead"
metadata/FiringIntervalArray = PackedInt64Array(5000, 500, 250)
[node name="Area2D" type="Area2D" parent="."]
collision_layer = 8
collision_mask = 68
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
position = Vector2(25.5, 0.5)
shape = SubResource("RectangleShape2D_obcq2")
[node name="StaffOfTheUndead2" type="Sprite2D" parent="."]
position = Vector2(30, 0)
texture = ExtResource("1_ms3us")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(25.5, 0.375)
shape = SubResource("RectangleShape2D_14m1g")
[node name="Marker2D" type="Marker2D" parent="."]
position = Vector2(65, 0)
[node name="RayCast2D" type="RayCast2D" parent="."]
position = Vector2(26, -8)
target_position = Vector2(0, 20)
collision_mask = 34
[node name="RayCast2D2" type="RayCast2D" parent="."]
target_position = Vector2(0, -29)

112
project.godot Normal file
View File

@ -0,0 +1,112 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Traveler"
config/version="0.0.1"
run/main_scene="res://scenes/mainMenu.tscn"
config/features=PackedStringArray("4.2", "C#", "Mobile")
boot_splash/bg_color=Color(0.141176, 0.141176, 0.141176, 1)
config/icon="res://icon.svg"
[dotnet]
project/assembly_name="ColdMint.Traveler"
[file_customization]
folder_colors={
"res://locals/": "red",
"res://prefab/": "orange",
"res://scenes/": "yellow",
"res://scripts/": "green",
"res://sprites/": "teal",
"res://tileSets/": "blue"
}
[gui]
theme/custom_font="res://fonts/ark-pixel-12px-proportional-zh_cn.ttf"
[input]
ui_accept={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
]
}
ui_left={
"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":65,"physical_keycode":0,"key_label":0,"unicode":97,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
]
}
ui_right={
"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":68,"physical_keycode":0,"key_label":0,"unicode":100,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
]
}
ui_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":87,"physical_keycode":0,"key_label":0,"unicode":119,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
]
}
ui_down={
"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":83,"physical_keycode":0,"key_label":0,"unicode":115,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
]
}
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,"echo":false,"script":null)
]
}
throw={
"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":81,"physical_keycode":0,"key_label":0,"unicode":113,"echo":false,"script":null)
]
}
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)
]
}
[internationalization]
locale/translations=PackedStringArray("res://locals/UI.en.translation", "res://locals/UI.zh.translation", "res://locals/Error.zh.translation", "res://locals/Error.en.translation", "res://locals/slogan.en.translation", "res://locals/slogan.zh.translation", "res://locals/Log.en.translation", "res://locals/Log.zh.translation", "res://locals/Weapon.en.translation", "res://locals/Weapon.zh.translation", "res://locals/InputMapping.en.translation", "res://locals/InputMapping.zh.translation")
[layer_names]
2d_physics/layer_1="RoomArea"
2d_physics/layer_2="Ground"
2d_physics/layer_3="Player"
2d_physics/layer_4="Weapon"
2d_physics/layer_5="Projectile"
2d_physics/layer_6="Platform"
2d_physics/layer_7="Mob"
[physics]
2d/default_gravity=480.0
[rendering]
renderer/rendering_method="mobile"

58
scenes/game.tscn Normal file
View File

@ -0,0 +1,58 @@
[gd_scene load_steps=5 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"]
[node name="Game" type="Node2D"]
script = ExtResource("1_mqdgt")
[node name="MapRoot" type="Node2D" parent="."]
[node name="CanvasLayer" type="CanvasLayer" parent="."]
[node name="Control" type="Control" parent="CanvasLayer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/Control"]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -95.0
offset_right = 178.0
offset_bottom = -10.0
grow_vertical = 0
[node name="HealthBarUi" type="HBoxContainer" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
script = ExtResource("2_xrm3v")
[node name="TextureRect3" type="TextureRect" parent="CanvasLayer/Control/VBoxContainer/HealthBarUi"]
layout_mode = 2
texture = ExtResource("2_n1yht")
[node name="HotBar" type="HBoxContainer" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
script = ExtResource("2_owrhq")
[node name="TextureRect3" type="TextureRect" parent="CanvasLayer/Control/VBoxContainer/HotBar"]
layout_mode = 2
texture = ExtResource("2_n1yht")
[node name="OperationTip" type="Label" parent="CanvasLayer/Control/VBoxContainer"]
layout_mode = 2
text = "32323"
[node name="ProjectileContainer" type="Node2D" parent="."]
[node name="DamageNumberContainer" type="Node2D" parent="."]
[node name="WeaponContainer" type="Node2D" parent="."]

91
scenes/mainMenu.tscn Normal file
View File

@ -0,0 +1,91 @@
[gd_scene load_steps=2 format=3 uid="uid://c3o422sb2opuh"]
[ext_resource type="Script" path="res://scripts/loader/uiLoader/MainMenuLoader.cs" id="1_ljkri"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 3.0
offset_bottom = 2.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_ljkri")
[node name="StartGameButton" type="Button" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -47.5
offset_top = 170.0
offset_right = 47.5
offset_bottom = 201.0
grow_horizontal = 2
text = "start_game"
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = -103.0
offset_right = 87.0
offset_bottom = -20.0
grow_vertical = 0
[node name="Bilibili" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Bilibili"
[node name="GithubLabel2" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Github"
[node name="CopyrightLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "copyright"
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_top = 30.0
offset_bottom = 98.0
grow_horizontal = 2
[node name="Label" type="Label" parent="CenterContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 45
text = "product_name"
[node name="CenterContainer2" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_top = 102.0
offset_bottom = 127.0
grow_horizontal = 2
[node name="SloganLabel" type="Label" parent="CenterContainer2"]
layout_mode = 2
text = "slogan"
[node name="VBoxContainer2" type="VBoxContainer" 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 = -68.0
offset_top = -60.0
offset_right = -67.0
offset_bottom = -35.0
grow_horizontal = 0
grow_vertical = 0
[node name="VersionLabel" type="Label" parent="VBoxContainer2"]
layout_mode = 2

286
scripts/Config.cs Normal file
View File

@ -0,0 +1,286 @@
using System;
using System.IO;
using ColdMint.scripts.dataPack;
using Godot;
using Environment = System.Environment;
namespace ColdMint.scripts;
public static class Config
{
public class BehaviorTreeId
{
/// <summary>
/// <para>巡逻</para>
/// <para>Patrol</para>
/// </summary>
public const string Patrol = "Patrol";
}
/// <summary>
/// <para>BehaviorTreeResult</para>
/// <para>行为树的结果</para>
/// </summary>
public class BehaviorTreeResult
{
/// <summary>
/// <para>Running</para>
/// <para>运行中</para>
/// </summary>
public const int Running = 0;
/// <summary>
/// <para>Success</para>
/// <para>成功</para>
/// </summary>
public const int Success = 1;
/// <summary>
/// <para>Failure</para>
/// <para>失败</para>
/// </summary>
public const int Failure = 2;
}
/// <summary>
/// <para>Camp ID</para>
/// <para>阵营ID</para>
/// </summary>
public static class CampId
{
/// <summary>
/// <para>Default camp</para>
/// <para>表示默认阵营</para>
/// </summary>
public const string Default = "Default";
/// <summary>
/// <para>Demon camp</para>
/// <para>魔族阵营</para>
/// </summary>
public const string Mazoku = "Mazoku";
/// <summary>
/// <para>Aborigines</para>
/// <para>原住民</para>
/// </summary>
public const string Aborigines = "Aborigines";
}
/// <summary>
/// <para>How much blood does a heart represent</para>
/// <para>一颗心代表多少血量</para>
/// </summary>
public const int HeartRepresentsHealthValue = 4;
/// <summary>
/// <para>The maximum number of stacked items in a single inventory</para>
/// <para>单个物品栏最大堆叠的物品数量</para>
/// </summary>
public const int MaxStackQuantity = 99;
/// <summary>
/// <para>Company/Creator name</para>
/// <para>公司/创作者名字</para>
/// </summary>
public const string CompanyName = "ColdMint";
/// <summary>
/// <para>An empty namespace</para>
/// <para>空的命名空间</para>
/// </summary>
public const string EmptyNamespace = "Empty";
/// <summary>
/// <para>The default namespace of the packet</para>
/// <para>数据包的默认命名空间</para>
/// </summary>
public const string DefaultNamespace = "traveler";
/// <summary>
/// <para>UserID</para>
/// <para>用户ID</para>
/// </summary>
public const string UserId = "DefaultUser";
/// <summary>
/// <para>Whether version isolation is enabled</para>
/// <para>是否启用版本隔离</para>
/// </summary>
public const bool EnableVersionIsolation = true;
/// <summary>
/// <para>Default version name</para>
/// <para>默认的版本名称</para>
/// </summary>
/// <remarks>
///<para>Used when version isolation is disabled</para>
///<para>在禁用版本隔离时用的</para>
/// </remarks>
public const string DefaultVersionName = "Default";
public const string DataPackDirectoryName = "DataPacks";
public const string DataBaseDirectoryName = "DataBases";
/// <summary>
/// <para>The starting path of the item data</para>
/// <para>物品数据的起始路径</para>
/// </summary>
public const string ItemStartPathName = "items";
public const string SpriteStartPathName = "sprites";
/// <summary>
/// <para>The format of the source file inside the packet</para>
/// <para>数据包内的源文件格式</para>
/// </summary>
public const string DataPackSourceFileFomat = ".json";
/// <summary>
/// <para>The path symbol inside the compressed package</para>
/// <para>压缩包内部的路径符号</para>
/// </summary>
public const char ZipPathCharacter = '/';
/// <summary>
/// <para>Gets the packet directory</para>
/// <para>获取数据包目录</para>
/// </summary>
/// <returns></returns>
public static string GetDataPackDirectory()
{
return Path.Join(GetGameDataDirectory(), DataPackDirectoryName);
}
/// <summary>
/// <para>Get database directory</para>
/// <para>获取数据库目录</para>
/// </summary>
/// <returns></returns>
public static string GetDataBaseDirectory()
{
return Path.Join(GetGameDataDirectory(), DataBaseDirectoryName);
}
/// <summary>
/// <para>GetGameDataDirectory</para>
/// <para>获取游戏数据目录</para>
/// </summary>
/// <returns></returns>
public static string GetGameDataDirectory()
{
if (EnableVersionIsolation)
{
return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName,
ProjectSettings.GetSetting("application/config/name").AsString(), UserId,
ProjectSettings.GetSetting("application/config/version").AsString());
}
else
{
return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName,
ProjectSettings.GetSetting("application/config/name").AsString(), UserId,
DefaultVersionName);
}
}
/// <summary>
/// <para>The initial year of creating this game</para>
/// <para>创建此游戏的初始年份</para>
/// </summary>
public const int CreationYear = 2024;
/// <summary>
/// <para>Tile map, dimensions of individual tiles</para>
/// <para>瓦片地图,单个瓦片的尺寸</para>
/// </summary>
public const int CellSize = 32;
/// <summary>
/// <para>The maximum health of the default creature</para>
/// <para>默认生物的最大血量</para>
/// </summary>
public const int DefaultMaxHp = 100;
/// <summary>
/// <para>When a creature takes damage, how long to hide the bloodline again</para>
/// <para>生物受到伤害时,要在多长时间后再次隐藏血条</para>
/// </summary>
public static TimeSpan HealthBarDisplaysTime = TimeSpan.FromSeconds(2);
/// <summary>
/// <para>Text size of critical hit damage</para>
/// <para>暴击伤害的文本大小</para>
/// </summary>
public const int CritDamageTextSize = 33;
/// <summary>
/// <para>Crit damage multiplier</para>
/// <para>暴击伤害乘数</para>
/// </summary>
/// <remarks>
///<para>How much damage to increase after a critical strike</para>
///<para>造成暴击后要将伤害提升到原有的多少倍</para>
/// </remarks>
public const float CriticalHitMultiplier = 2f;
/// <summary>
/// <para>Text size of normal damage</para>
/// <para>普通伤害的文本大小</para>
/// </summary>
public const int NormalDamageTextSize = 22;
/// <summary>
/// <para>Horizontal speed of damage numbers</para>
/// <para>伤害数字的水平速度</para>
/// </summary>
public const int HorizontalSpeedOfDamageNumbers = 3;
/// <summary>
/// <para>The file name of the packet's manifest</para>
/// <para>数据包的清单文件名</para>
/// </summary>
public const string DataPackManifestName = "DataPackManifest.json";
/// <summary>
/// <para>VerticalVelocityOfDamageNumbers</para>
/// <para>伤害数字的垂直速度</para>
/// </summary>
public const int VerticalVelocityOfDamageNumbers = 5;
/// <summary>
/// <para>Physical collision layer number</para>
/// <para>物理碰撞层 序号</para>
/// </summary>
public class LayerNumber
{
public const int RoomArea = 1;
public const int Ground = 2;
public const int Player = 3;
public const int Weapon = 4;
public const int Projectile = 5;
public const int Platform = 6;
public const int Mob = 7;
}
/// <summary>
/// <para>Specify the type of damage used in the game</para>
/// <para>指定游戏内使用的伤害类型</para>
/// </summary>
public class DamageType
{
/// <summary>
/// <para>physical injury</para>
/// <para>物理伤害</para>
/// </summary>
public const int Physical = 1;
/// <summary>
/// <para>Magic damage</para>
/// <para>魔法伤害</para>
/// </summary>
public const int Magic = 2;
}
}

View File

@ -0,0 +1,29 @@
using ColdMint.scripts.inventory;
using Godot;
namespace ColdMint.scripts;
/// <summary>
/// <para>The node holder within the game scene</para>
/// <para>游戏场景内的节点持有者</para>
/// </summary>
public class GameSceneNodeHolder
{
/// <summary>
/// <para>Player instances within the game scene</para>
/// <para>游戏场景内的玩家实例</para>
/// </summary>
public static Player Player { get; set; }
/// <summary>
/// <para>WeaponContainer</para>
/// <para>武器容器</para>
/// </summary>
public static Node2D WeaponContainer { get; set; }
public static HotBar HotBar { get; set; }
public static HealthBarUi HealthBarUi { get; set; }
public static Label OperationTipLabel { get; set; }
}

138
scripts/HealthBarUi.cs Normal file
View File

@ -0,0 +1,138 @@
using Godot;
namespace ColdMint.scripts;
public partial class HealthBarUi : HBoxContainer
{
private int _maxHp;
private int _currentHp;
public int CurrentHp
{
get => _currentHp;
set
{
if (value == _currentHp)
{
return;
}
var heartCount = GetChildCount();
//有几颗心是满的
var fullHeartCount = value / Config.HeartRepresentsHealthValue;
for (int i = 0; i < fullHeartCount; i++)
{
//把Ui刷满
var textureRect = GetChild<TextureRect>(i);
textureRect.Texture = _heartFull;
}
//有多少空心
var emptyHeartCount = heartCount - fullHeartCount;
//最后那颗剩余多少血
var leftOverTextureRect = GetChild<TextureRect>(fullHeartCount);
var leftOver = value % Config.HeartRepresentsHealthValue;
if (leftOver > 0)
{
//占总数的百分比
var percent = leftOver / (float)Config.HeartRepresentsHealthValue;
leftOverTextureRect.Texture = GetTexture2DByPercent(percent);
emptyHeartCount--;
}
for (int i = heartCount - emptyHeartCount; i < heartCount; i++)
{
var textureRect = GetChild<TextureRect>(i);
textureRect.Texture = _heartEmpty;
}
_currentHp = value;
}
}
public int MaxHp
{
get => _maxHp;
set
{
if (value == _maxHp)
{
return;
}
var heartCount = value / Config.HeartRepresentsHealthValue;
for (var i = 0; i < heartCount; i++)
{
var heart = CreateTextureRect();
heart.Texture = _heartFull;
AddChild(heart);
}
//最后那颗剩余多少血
var leftOver = value % Config.HeartRepresentsHealthValue;
if (leftOver > 0)
{
var lastHeart = CreateTextureRect();
//占总数的百分比
var percent = leftOver / (float)Config.HeartRepresentsHealthValue;
lastHeart.Texture = GetTexture2DByPercent(percent);
AddChild(lastHeart);
}
_maxHp = value;
}
}
private TextureRect CreateTextureRect()
{
var textureRect = new TextureRect();
return textureRect;
}
/// <summary>
/// <para>Get the texture based on percentage</para>
/// <para>根据百分比获取纹理</para>
/// </summary>
/// <param name="percent"></param>
/// <returns></returns>
private Texture2D GetTexture2DByPercent(float percent)
{
if (percent == 0)
{
return _heartEmpty;
}
if (percent <= 0.25f)
{
return _heartQuarter;
}
else if (percent <= 0.5f)
{
return _heartHalf;
}
else if (percent <= 0.75f)
{
return _heartThreeFourths;
}
else
{
return _heartFull;
}
}
private Texture2D _heartFull;
private Texture2D _heartEmpty;
private Texture2D _heartHalf;
private Texture2D _heartQuarter;
private Texture2D _heartThreeFourths;
public override void _Ready()
{
base._Ready();
_heartEmpty = GD.Load<Texture2D>("res://sprites/ui/HeartEmpty.png");
_heartQuarter = GD.Load<Texture2D>("res://sprites/ui/HeartQuarter.png");
_heartHalf = GD.Load<Texture2D>("res://sprites/ui/HeartHalf.png");
_heartThreeFourths = GD.Load<Texture2D>("res://sprites/ui/HeartThreeFourths.png");
_heartFull = GD.Load<Texture2D>("res://sprites/ui/HeartFull.png");
}
}

26
scripts/SloganProvider.cs Normal file
View File

@ -0,0 +1,26 @@
using Godot;
namespace ColdMint.scripts;
/// <summary>
/// <para>SloganProvider</para>
/// <para>标语提供器</para>
/// </summary>
public static class SloganProvider
{
/// <summary>
/// <para>Define how many banners you want to display</para>
/// <para>定义共有多少条标语需要展示</para>
/// </summary>
private const int total = 12;
/// <summary>
/// <para>Swipe the machine to get a slogan</para>
/// <para>刷机获取一个标语</para>
/// </summary>
/// <returns></returns>
public static string GetSlogan()
{
return TranslationServer.Translate("slogan_" + GD.RandRange(1, total));
}
}

View File

@ -0,0 +1,35 @@
using Godot;
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>BehaviorNode</para>
/// <para>行为节点</para>
/// </summary>
public partial class BehaviorNode : Node2D
{
public IBehaviorTreeNode Root { get; set; }
public override void _PhysicsProcess(double delta)
{
InvokeBehaviorTreeNode(true, delta);
}
// public override void _Process(double delta)
// {
// InvokeBehaviorTreeNode(false, delta);
// }
/// <summary>
/// <para>InvokeBehaviorTreeNode</para>
/// <para>调用行为树节点</para>
/// </summary>
private void InvokeBehaviorTreeNode(bool isPhysicsProcess, double delta)
{
if (Root == null)
{
return;
}
Root.Execute(isPhysicsProcess, delta);
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>Behavior tree node template</para>
/// <para>行为树节点模板</para>
/// </summary>
public abstract class BehaviorTreeNodeTemplate : IBehaviorTreeNode
{
private List<IBehaviorTreeNode> _children = new List<IBehaviorTreeNode>();
public void AddChild(IBehaviorTreeNode child)
{
_children.Add(child);
child.Parent = this;
}
public void RemoveChild(IBehaviorTreeNode child)
{
_children.Remove(child);
child.Parent = null;
}
/// <summary>
/// <para>Gets the child node of the specified type</para>
/// <para>获取指定类型的子节点</para>
/// </summary>
/// <param name="defaultT"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetChild<T>(T defaultT)
{
if (_children.Count == 0)
{
return defaultT;
}
foreach (var behaviorTreeNode in _children)
{
if (behaviorTreeNode is T t)
{
return t;
}
}
return defaultT;
}
public abstract int Execute(bool isPhysicsProcess, double delta);
public IBehaviorTreeNode Parent { get; set; }
public IBehaviorTreeNode[] Children => _children.ToArray();
}

View File

@ -0,0 +1,23 @@
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>BehaviorTreeTemplate</para>
/// <para>行为树模板</para>
/// </summary>
public abstract class BehaviorTreeTemplate : IBehaviorTree
{
private IBehaviorTreeNode _root;
private string _id;
public IBehaviorTreeNode Root => _root;
public string ID => _id;
public void Init()
{
_root = CreateRoot();
_id = CreateID();
}
protected abstract IBehaviorTreeNode CreateRoot();
protected abstract string CreateID();
}

View File

@ -0,0 +1,8 @@
namespace ColdMint.scripts.behaviorTree;
public interface IBehaviorTree
{
string ID { get; }
IBehaviorTreeNode Root { get; }
}

View File

@ -0,0 +1,32 @@
using Godot;
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>Behavior tree node</para>
/// <para>行为树节点</para>
/// </summary>
public interface IBehaviorTreeNode
{
/// <summary>
/// <para>execution node</para>
/// <para>执行节点</para>
/// </summary>
/// <paramref name="isPhysicsProcess">
///<para>Whether to call within a physical process</para>
///<para>是否在物理流程内调用</para>
/// </paramref>
int Execute(bool isPhysicsProcess, double delta);
/// <summary>
/// <para>The parent of this node</para>
/// <para>此节点的父节点</para>
/// </summary>
IBehaviorTreeNode Parent { get; set; }
/// <summary>
/// <para>child node</para>
/// <para>子节点</para>
/// </summary>
IBehaviorTreeNode[] Children { get; }
}

View File

@ -0,0 +1,79 @@
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
using Godot;
namespace ColdMint.scripts.behaviorTree.ai;
public class AIAttackNode : BehaviorTreeNodeTemplate
{
public AICharacter Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
var nodesInTheAttackRange = Character.NodesInTheAttackRange;
if (nodesInTheAttackRange.Length == 0)
{
//No nodes are in range of the attack
//没有节点在攻击范围内
return Config.BehaviorTreeResult.Failure;
}
//Save the nearest enemy
//保存最近的敌人
CharacterTemplate closestEnemy = null;
var closestDistance = float.MaxValue;
var selfCamp = CampManager.GetCamp(Character.CampId);
foreach (var node in nodesInTheAttackRange)
{
if (node is CharacterTemplate characterTemplate)
{
if (node == Character)
{
continue;
}
var characterCamp = CampManager.GetCamp(characterTemplate.CampId);
var canCause = CampManager.CanCauseHarm(selfCamp, characterCamp);
if (!canCause)
{
continue;
}
if (selfCamp.ID == characterCamp.ID)
{
//如果是同一阵营,不攻击
continue;
}
var distance = characterTemplate.GlobalPosition - Character.GlobalPosition;
var distanceLength = distance.Length();
if (distanceLength < closestDistance)
{
closestDistance = distanceLength;
closestEnemy = characterTemplate;
}
}
}
if (closestEnemy != null)
{
//There are the closest enemies
//有距离最近的敌人
var distance = closestEnemy.GlobalPosition - Character.GlobalPosition;
Character.AttackObstacleDetection.TargetPosition = distance;
if (Character.AttackObstacleDetection.GetCollider() == null)
{
Character.StopMoving();
Character.AimTheCurrentItemAtAPoint(closestEnemy.GlobalPosition);
Character.UseItem(closestEnemy.GlobalPosition);
}
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -0,0 +1,71 @@
using ColdMint.scripts.behaviorTree.framework;
using ColdMint.scripts.character;
using ColdMint.scripts.weapon;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>AI巡逻节点</para>
/// </summary>
public class AIPatrolNode : SelectorNode
{
public AICharacter Character { get; set; }
public override IBehaviorTreeNode SelectNode(bool isPhysicsProcess, double delta, IBehaviorTreeNode[] children)
{
if (Character.NodesInTheAttackRange.Length > 1)
{
if (Character.CurrentItem == null)
{
//No weapon
//没有武器
if (Character.NodesInTheAttackRange.Length > 0)
{
var weaponTemplates = Character.GetCanPickedWeapon();
if (weaponTemplates.Length > 0)
{
var aiPickNode = GetChild<AIPickNode>(null);
if (aiPickNode != null)
{
return aiPickNode;
}
}
}
//No weapon, and no weapon to pick up, then try to escape
//没有武器,且没有武器可捡,那么尝试逃跑
var aiRotorNode = GetChild<AIRotorNode>(null);
if (aiRotorNode != null)
{
return aiRotorNode;
}
return children[0];
}
//There are enemies around
//周围有敌人
if (Character.AttackObstacleDetection.GetCollider() == null)
{
var aiAttackNode = GetChild<AIAttackNode>(null);
if (aiAttackNode != null)
{
return aiAttackNode;
}
}
}
if (Character.WallDetection.GetCollider() != null)
{
//Encounter a wall
//遇到墙壁
var aiRotorNode = GetChild<AIRotorNode>(null);
if (aiRotorNode != null)
{
return aiRotorNode;
}
}
return children[0];
}
}

View File

@ -0,0 +1,70 @@
using ColdMint.scripts.character;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>Deal with AI picking up items</para>
/// <para>处理AI拾起物品的行为</para>
/// </summary>
public class AIPickNode : BehaviorTreeNodeTemplate
{
public AICharacter Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
if (Character.CurrentItem != null)
{
//If the character already has the item, we don't pick it up
//如果角色已经持有物品了,我们就不再拾取
return Config.BehaviorTreeResult.Success;
}
//Find the nearest item
//查找距离最近的物品
var childCount = Character.PickingRangeBodies.Length;
if (childCount == 0)
{
//We can't pick things up without them
//没有物品,我们不能捡起
return Config.BehaviorTreeResult.Failure;
}
//The closest weapon
//距离最近的武器
WeaponTemplate closestWeapon = null;
var closestDistance = float.MaxValue;
foreach (var weaponTemplate in Character.GetCanPickedWeapon())
{
//If it's a weapon
//如果是武器
var distance = weaponTemplate.GlobalPosition - Character.GlobalPosition;
var distanceLength = distance.Length();
if (distanceLength < closestDistance)
{
closestDistance = distanceLength;
closestWeapon = weaponTemplate;
}
}
//绘制一条线从AI到武器
// Draw a line from AI to weapon
if (closestWeapon != null)
{
//If we find the nearest weapon
//如果找到了最近的武器
Character.PickItem(closestWeapon);
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -0,0 +1,33 @@
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>一个节点用于实现角色的移动</para>
/// <para>A node is used to implement the movement of the character</para>
/// </summary>
public class AIWalkNode : BehaviorTreeNodeTemplate
{
public AICharacter Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
if (Character.FacingLeft)
{
//If the character is facing left, move left
//如果角色面向左边,那么向左移动
Character.MoveLeft();
}
else
{
Character.MoveRight();
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -0,0 +1,31 @@
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>The node that controls the rotor when the AI is facing the wall</para>
/// <para>当AI面向墙壁时控制转头的节点</para>
/// </summary>
public class AIRotorNode : BehaviorTreeNodeTemplate
{
public AICharacter Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
var notFacingTheWall = Character.WallDetection.GetCollider() == null;
if (notFacingTheWall)
{
return Config.BehaviorTreeResult.Failure;
}
else
{
Character.Rotor();
return Config.BehaviorTreeResult.Success;
}
}
}

View File

@ -0,0 +1,37 @@
using ColdMint.scripts.behaviorTree.ai;
using ColdMint.scripts.behaviorTree.framework;
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.behavior;
/// <summary>
/// <para>Represents a behavior tree for patrol</para>
/// <para>表示巡逻的行为树</para>
/// </summary>
public class PatrolBehaviorTree : BehaviorTreeTemplate
{
public AICharacter Character { get; set; }
protected override IBehaviorTreeNode CreateRoot()
{
var patrolNode = new AIPatrolNode();
var aiWalkNode = new AIWalkNode();
var aiRotorNode = new AIRotorNode();
var aIPickNode = new AIPickNode();
var aiAttackNode = new AIAttackNode();
aiWalkNode.Character = Character;
patrolNode.Character = Character;
aiRotorNode.Character = Character;
aIPickNode.Character = Character;
aiAttackNode.Character = Character;
patrolNode.AddChild(aiWalkNode);
patrolNode.AddChild(aiRotorNode);
patrolNode.AddChild(aIPickNode);
patrolNode.AddChild(aiAttackNode);
return patrolNode;
}
protected override string CreateID()
{
return Config.BehaviorTreeId.Patrol;
}
}

View File

@ -0,0 +1,22 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>并行节点</para>
/// <para>ParallelNode</para>
/// </summary>
/// <remarks>
///<para>Run all of its child nodes</para>
///<para>将其所有子节点都运行一遍</para>
/// </remarks>
public class ParallelNode : BehaviorTreeNodeTemplate
{
public override int Execute(bool isPhysicsProcess, double delta)
{
foreach (var child in Children)
{
child.Execute(isPhysicsProcess, delta);
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -0,0 +1,32 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>Selector node</para>
/// <para>选择器节点</para>
/// </summary>
/// <remarks>
///<para>Select an execution of the child node and pass the execution result to the parent node</para>
///<para>选择其子节点的某一个执行,并将执行结果传递给父节点</para>
/// </remarks>
public abstract class SelectorNode : BehaviorTreeNodeTemplate
{
public override int Execute(bool isPhysicsProcess, double delta)
{
var behaviorTreeNode = SelectNode(isPhysicsProcess, delta, Children);
if (behaviorTreeNode == null)
{
return Config.BehaviorTreeResult.Failure;
}
return behaviorTreeNode.Execute(isPhysicsProcess, delta);
}
/// <summary>
/// <para>Select an abstract method for the node</para>
/// <para>选择节点的抽象方法</para>
/// </summary>
/// <returns></returns>
public abstract IBehaviorTreeNode SelectNode(bool isPhysicsProcess, double delta, IBehaviorTreeNode[] children);
}

View File

@ -0,0 +1,71 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>SequenceNode</para>
/// <para>序列器节点</para>
/// </summary>
/// <remarks>
///<para>Execute all its child nodes in turn, that is, after the current one returns to the "finished" state, run the second child node, until all nodes return "finished", then this node returns "finished".</para>
///<para>将其所有子节点依次执行,也就是说当前一个返回“完成”状态后,再运行第二个子节点,直到所有节点都返回“完成”后,那么此节点返回"完成"</para>
/// </remarks>
public class SequenceNode : BehaviorTreeNodeTemplate
{
/// <summary>
/// <para>Check whether all child nodes are executed in sequence</para>
/// <para>所有子节点是否按顺序执行完毕</para>
/// </summary>
bool _complete = true;
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Children.Length == 0)
{
return Config.BehaviorTreeResult.Failure;
}
if (_complete)
{
//If the last execution is over, we start executing a new sequence
//如果上次执行完毕了,我们开始执行新的序列
_complete = false;
}
else
{
//If it hasn't finished, we return to Running
//如果还没有执行完毕我们返回Running
return Config.BehaviorTreeResult.Running;
}
var result = true;
foreach (var behaviorTreeNode in Children)
{
var singleResult = behaviorTreeNode.Execute(isPhysicsProcess, delta);
while (singleResult == Config.BehaviorTreeResult.Running)
{
//Wait for the child node to complete execution
//等得子节点执行完毕
}
//Single child node is executed
//单个子节点执行完毕
if (singleResult == Config.BehaviorTreeResult.Failure)
{
//If a child node fails, the entire sequence fails
//如果有一个子节点失败,整个序列失败
result = false;
}
}
//All child nodes are executed
//全部子节点执行完毕
_complete = true;
if (result)
{
return Config.BehaviorTreeResult.Success;
}
else
{
return Config.BehaviorTreeResult.Failure;
}
}
}

47
scripts/camp/Camp.cs Normal file
View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace ColdMint.scripts.camp;
/// <summary>
/// <para>camp</para>
/// <para>阵营</para>
/// </summary>
public class Camp
{
private string _id;
private List<string> _friendlyCampIdList;
public Camp(string id)
{
_id = id;
_friendlyCampIdList = new List<string>();
}
/// <summary>
/// <para>Get camp ID</para>
/// <para>获取阵营ID</para>
/// </summary>
public string ID => _id;
/// <summary>
/// <para>Get camp name</para>
/// <para>获取阵营名</para>
/// </summary>
public string Name { get; }
/// <summary>
/// <para>Friend Injury</para>
/// <para>友伤</para>
/// </summary>
/// <remarks>
///<para>Whether to damage targets on the same side</para>
///<para>是否可伤害同一阵营的目标</para>
/// </remarks>
public bool FriendInjury { get; set; }
/// <summary>
/// <para>Gets the camp ID that is friendly to this camp</para>
/// <para>获取与此阵营友好的阵营ID</para>
/// </summary>
public string[] FriendlyCampIdArray => _friendlyCampIdList.ToArray();
}

113
scripts/camp/CampManager.cs Normal file
View File

@ -0,0 +1,113 @@
using System.Collections.Generic;
namespace ColdMint.scripts.camp;
/// <summary>
/// <para>Camp manager</para>
/// <para>阵营管理器</para>
/// </summary>
public class CampManager
{
private static readonly Dictionary<string, Camp> Camps = new Dictionary<string, Camp>();
/// <summary>
/// <para>The default camp is returned if no corresponding camp is obtained</para>
/// <para>当获取不到对应的阵营时,返回的默认阵营</para>
/// </summary>
private static Camp _defaultCamp;
/// <summary>
/// <para>SetDefaultCamp</para>
/// <para>设置默认阵营</para>
/// </summary>
/// <param name="camp">
///<para>Camp, whose ID must be the default camp ID.</para>
///<para>阵营要求其ID必须为默认阵营ID。</para>
/// </param>
/// <returns>
///<para>Return whether the setting is successful</para>
///<para>返回是否设置成功</para>
/// </returns>
public static bool SetDefaultCamp(Camp camp)
{
if (camp.ID == Config.CampId.Default)
{
_defaultCamp = camp;
AddCamp(camp);
return true;
}
return false;
}
/// <summary>
/// <para>Whether camp A can damage camp B</para>
/// <para>阵营A是否可伤害阵营B</para>
/// </summary>
/// <param name="attacker"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool CanCauseHarm(Camp attacker, Camp target)
{
if (attacker.ID == target.ID)
{
//In the same camp, return whether friendly fire is allowed
//在同一阵营内,返回是否允许友伤
return attacker.FriendInjury;
}
//A camp ID that the attacker considers friendly
//攻击者认为友好的阵营ID
var friendlyCampIdArray = attacker.FriendlyCampIdArray;
var targetId = target.ID;
if (friendlyCampIdArray.Length > 0)
{
foreach (var friendlyCampId in friendlyCampIdArray)
{
if (friendlyCampId == targetId)
{
//The attacker thinks the target is friendly, and we can't hurt a friendly target
//攻击者认为目标友好,我们不能伤害友好的目标
return false;
}
}
}
return true;
}
/// <summary>
/// <para>Add camp</para>
/// <para>添加阵营</para>
/// </summary>
/// <param name="camp"></param>
/// <returns></returns>
public static bool AddCamp(Camp camp)
{
return Camps.TryAdd(camp.ID, camp);
}
/// <summary>
/// <para>Get camp based on ID</para>
/// <para>根据ID获取阵营</para>
/// </summary>
/// <remarks>
///<para>Cannot get back to default camp</para>
///<para>获取不到返回默认阵营</para>
/// </remarks>
/// <param name="id">
///<para>Camp ID</para>
///<para>阵营ID</para>
/// </param>
/// <returns></returns>
public static Camp GetCamp(string id)
{
if (id == null)
{
return _defaultCamp;
}
return Camps.GetValueOrDefault(id, _defaultCamp);
}
}

View File

@ -0,0 +1,141 @@
using System.Collections.Generic;
using ColdMint.scripts.behaviorTree;
using ColdMint.scripts.behaviorTree.ai;
using ColdMint.scripts.behaviorTree.behavior;
using ColdMint.scripts.behaviorTree.framework;
using ColdMint.scripts.damage;
using Godot;
namespace ColdMint.scripts.character;
/// <summary>
/// <para>The role played by computers</para>
/// <para>由电脑扮演的角色</para>
/// </summary>
public partial class AICharacter : CharacterTemplate
{
/// <summary>
/// <para>How fast the character moves</para>
/// <para>角色的移动速度</para>
/// </summary>
private float MovementSpeed = 300.0f;
private BehaviorNode _behaviorNode;
//Used to detect rays on walls
//用于检测墙壁的射线
private RayCast2D _wallDetection;
public RayCast2D WallDetection => _wallDetection;
private Vector2 _wallDetectionOrigin;
private Area2D _attackArea;
/// <summary>
/// <para>Nodes in the attack range</para>
/// <para>在攻击范围内的节点</para>
/// </summary>
private List<Node> _nodesInTheAttackRange;
/// <summary>
/// <para>All nodes in the attack range</para>
/// <para>在攻击范围内的全部节点</para>
/// </summary>
public Node[] NodesInTheAttackRange => _nodesInTheAttackRange.ToArray();
/// <summary>
/// <para>Obstacle detection ray during attack</para>
/// <para>攻击时的障碍物检测射线</para>
/// </summary>
/// <remarks>
///<para></para>
///<para>检测与目标点直接是否间隔墙壁</para>
/// </remarks>
private RayCast2D _attackObstacleDetection;
public RayCast2D AttackObstacleDetection => _attackObstacleDetection;
public override void _Ready()
{
base._Ready();
_nodesInTheAttackRange = new List<Node>();
_behaviorNode = GetNode<BehaviorNode>("Behavior");
_wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D");
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
if (_attackArea != null)
{
//如果为true该区域将检测进出该区域的物体或区域。
_attackArea.Monitoring = true;
//Other regions cannot detect our pick region
//其他区域不能检测到我们的拾取区域
_attackArea.Monitorable = false;
_attackArea.BodyEntered += EnterTheAttackArea;
_attackArea.BodyExited += ExitTheAttackArea;
}
_wallDetectionOrigin = _wallDetection.TargetPosition;
// var patrolBehaviorTree = new PatrolBehaviorTree();
// patrolBehaviorTree.Character = this;
// patrolBehaviorTree.Init();
// _behaviorNode.Root = patrolBehaviorTree.Root;
}
protected virtual void EnterTheAttackArea(Node node)
{
_nodesInTheAttackRange.Add(node);
}
protected virtual void ExitTheAttackArea(Node node)
{
_nodesInTheAttackRange.Remove(node);
}
/// <summary>
/// <para>Move left</para>
/// <para>向左移动</para>
/// </summary>
public void MoveLeft()
{
var oldVelocity = Velocity;
oldVelocity.X = -MovementSpeed;
Velocity = oldVelocity;
}
/// <summary>
/// <para>Move right</para>
/// <para>向右移动</para>
/// </summary>
public void MoveRight()
{
var oldVelocity = Velocity;
oldVelocity.X = MovementSpeed;
Velocity = oldVelocity;
}
/// <summary>
/// <para>Rotor</para>
/// <para>转头</para>
/// </summary>
public void Rotor()
{
FacingLeft = !FacingLeft;
Flip();
//Change the direction of the wall detection
//改变墙壁检测的方向
var newDirection = _wallDetectionOrigin;
newDirection.X = FacingLeft ? -_wallDetectionOrigin.X : _wallDetectionOrigin.X;
_wallDetection.TargetPosition = newDirection;
}
/// <summary>
/// <para>stop moving</para>
/// <para>停止移动</para>
/// </summary>
public void StopMoving()
{
Velocity = Vector2.Zero;
}
}

View File

@ -0,0 +1,401 @@
using System;
using System.Collections.Generic;
using ColdMint.scripts.camp;
using ColdMint.scripts.damage;
using ColdMint.scripts.debug;
using ColdMint.scripts.health;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.character;
/// <summary>
/// <para>CharacterTemplate</para>
/// <para>角色模板</para>
/// </summary>
/// <remarks>
///<para>Behavior shared by all characters</para>
///<para>所有角色共有的行为</para>
/// </remarks>
public partial class CharacterTemplate : CharacterBody2D
{
// Get the gravity from the project settings to be synced with RigidBody nodes.
public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public const float Speed = 300.0f;
public const float JumpVelocity = -240;
protected string _characterName;
public string CharacterName => _characterName;
//当前持有的物品
public Node2D CurrentItem;
//定义一个拾起范围
private Area2D PickingArea;
protected AnimatedSprite2D AnimatedSprite2D;
//一个标志,定义物品的位置
protected Marker2D ItemMarker2D;
//物品标记的原始X坐标
private float ItemMarkerOriginalX;
public float ReadOnlyItemMarkerOriginalX => ItemMarkerOriginalX;
//面向左边吗
public bool FacingLeft = false;
//The force added by the AddForce method
//由AddForce方法追加的力
private Vector2 additionalForce = Vector2.Zero;
protected int CurrentHp;
//The initial health of the character after creation
//角色创建后的初始血量
private int InitialHp;
protected int MaxHp;
/// <summary>
/// <para>The camp ID of the role</para>
/// <para>角色的阵营ID</para>
/// </summary>
public string CampId;
private DamageNumberNodeSpawn DamageNumber;
private HealthBar _healthBar;
private DateTime _lastDamageTime;
/// <summary>
/// <para>Pick up all items within range</para>
/// <para>拾捡范围内的所有物品</para>
/// </summary>
private List<Node> _pickingRangeBodies;
public Node[] PickingRangeBodies => _pickingRangeBodies.ToArray();
/// <summary>
/// <para>Get all weapons within range of the pick up</para>
/// <para>获取所有在拾捡范围内的武器</para>
/// </summary>
/// <returns></returns>
public WeaponTemplate[] GetCanPickedWeapon()
{
var weaponTemplates = new List<WeaponTemplate>();
foreach (var pickingRangeBody in PickingRangeBodies)
{
if (pickingRangeBody is not WeaponTemplate weaponTemplate) continue;
if (weaponTemplate.Owner != null)
{
continue;
}
weaponTemplates.Add(weaponTemplate);
}
return weaponTemplates.ToArray();
}
public override void _Ready()
{
base._Ready();
_pickingRangeBodies = new List<Node>();
_characterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
if (MaxHp <= 0)
{
//若最大血量为0或小于0则将最大血量设置为10
MaxHp = Config.DefaultMaxHp;
}
InitialHp = GetMeta("InitialHp", "0").AsInt32();
if (InitialHp <= 0)
{
//若初始血量小于等于0则将初始血量设置为最大血量
InitialHp = MaxHp;
}
CurrentHp = InitialHp;
//生物的健康条可能为null。
_healthBar = GetNodeOrNull<HealthBar>("HealthBar");
if (_healthBar != null)
{
_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;
if (PickingArea != null)
{
//如果为true该区域将检测进出该区域的物体或区域。
PickingArea.Monitoring = true;
//Other regions cannot detect our pick region
//其他区域不能检测到我们的拾取区域
PickingArea.Monitorable = false;
PickingArea.BodyEntered += EnterThePickingRangeBody;
PickingArea.BodyExited += ExitThePickingRangeBody;
}
}
/// <summary>
/// <para>Pick up the specified items</para>
/// <para>将指定物品拾起来</para>
/// </summary>
/// <param name="pickAbleItem"></param>
/// <returns>
///<para>Whether successfully picked up</para>
///<para>是否成功拾起</para>
/// </returns>
public bool PickItem(Node2D pickAbleItem)
{
if (pickAbleItem == null)
{
return false;
}
if (CurrentItem == null)
{
if (pickAbleItem is WeaponTemplate weaponTemplate)
{
if (weaponTemplate.Owner != null && weaponTemplate.Owner != this)
{
return false;
}
weaponTemplate.Owner = this;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, false);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
weaponTemplate.EnableContactInjury = false;
weaponTemplate.Sleeping = true;
}
pickAbleItem.Reparent(ItemMarker2D);
pickAbleItem.Position = Vector2.Zero;
CurrentItem = pickAbleItem;
return true;
}
return false;
}
/// <summary>
/// <para>Use what you have in your hand</para>
/// <para>使用手中的物品</para>
/// </summary>
public bool UseItem(Vector2 position)
{
if (CurrentItem == null)
{
return false;
}
if (CurrentItem is WeaponTemplate weaponTemplate)
{
weaponTemplate.Fire(this, position);
}
return true;
}
public override void _Process(double delta)
{
base._Process(delta);
//如果上次受到伤害的时间与当前时间的时间差大于健康条显示时间,则隐藏健康条
var timeSpan = DateTime.Now - _lastDamageTime;
if (timeSpan > Config.HealthBarDisplaysTime)
{
if (_healthBar != null)
{
_healthBar.Visible = false;
}
}
}
/// <summary>
/// <para>Update the role's health bar</para>
/// <para>更新角色的健康条</para>
/// </summary>
protected void UpDataHealthBar(DamageTemplate damageTemplate)
{
if (_healthBar == null)
{
return;
}
if (GameSceneNodeHolder.Player == null)
{
//We didn't know who the player was, so we showed it as a hostile color
//我们不知道玩家是谁,所以我们将其显示为敌对颜色
_healthBar.SetEnemyTones();
}
else
{
//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);
if (CampManager.CanCauseHarm(targetCamp, playerCamp))
{
if (targetCamp.ID == playerCamp.ID)
{
//If an attack is allowed and you are on the same side, it is displayed as a friendly color (friend damage).
//如果允许攻击,且属于同一阵营,则显示为友好颜色(友伤)
_healthBar.SetFriendlyTones();
}
else
{
//If the injured target is an enemy of the player, it is displayed as an enemy color
//如果受伤的目标是玩家的敌人,则显示为敌对颜色
_healthBar.SetEnemyTones();
}
}
else
{
_healthBar.SetFriendlyTones();
}
}
_healthBar.Visible = true;
_healthBar.Value = CurrentHp;
}
/// <summary>
/// <para>Deal damage to the character</para>
/// <para>对角色造成伤害</para>
/// </summary>
/// <param name="damageTemplate">
///<para>Damage template</para>
///<para>伤害模板</para>
/// </param>
/// <returns>
///<para>Return whether the damage was done successfully</para>
///<para>返回是否成功造成了伤害</para>
/// </returns>
public bool Damage(DamageTemplate damageTemplate)
{
_lastDamageTime = DateTime.Now;
DamageNumber.Display(damageTemplate);
CurrentHp -= damageTemplate.Damage;
OnHit(damageTemplate);
if (CurrentHp <= 0)
{
//角色死亡
OnDie(damageTemplate);
return true;
}
UpDataHealthBar(damageTemplate);
return true;
}
/// <summary>
/// <para>Add power to the character</para>
/// <para>在角色身上添加力</para>
/// </summary>
/// <param name="force"></param>
public void AddForce(Vector2 force)
{
additionalForce = force;
}
protected virtual void OnHit(DamageTemplate damageTemplate)
{
}
/// <summary>
/// <para>Handle the event of character death</para>
/// <para>处理角色死亡的事件</para>
/// </summary>
/// <param name="damageTemplate"></param>
protected virtual void OnDie(DamageTemplate damageTemplate)
{
QueueFree();
}
/// <summary>
/// <para>When an object enters the picking range</para>
/// <para>当有物体进入拾捡范围时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void EnterThePickingRangeBody(Node node)
{
_pickingRangeBodies.Add(node);
}
/// <summary>
/// <para>When an object exit the picking range</para>
/// <para>当有物体离开拾捡范围时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void ExitThePickingRangeBody(Node node)
{
_pickingRangeBodies.Remove(node);
}
/// <summary>
/// <para>Flip sprites or animations</para>
/// <para>翻转精灵或动画</para>
/// </summary>
protected virtual void Flip()
{
AnimatedSprite2D.FlipH = FacingLeft;
}
public sealed override void _PhysicsProcess(double delta)
{
//We continuously set the position of the items to prevent them from changing as we zoom in and out of the window.
//我们持续设置物品的位置,为了防止放大缩小窗口时物品位置的变化。
if (CurrentItem != null)
{
CurrentItem.Position = Vector2.Zero;
}
var velocity = Velocity;
// Add the gravity.
//增加重力。
if (!IsOnFloor())
velocity.Y += gravity * (float)delta;
// The ref keyword passes its pointer to the following method so that it can be modified in the method.
// ref关键字将其指针传递给下面的方法以便在方法中修改它。
HookPhysicsProcess(ref velocity, delta);
Velocity = velocity + additionalForce;
additionalForce = Vector2.Zero;
MoveAndSlide();
}
/// <summary>
/// <para>Aim the held item at a point</para>
/// <para>使持有的物品瞄准某个点</para>
/// </summary>
public void AimTheCurrentItemAtAPoint(Vector2 position)
{
if (CurrentItem == null)
{
//Do not currently hold any items.
//当前没有持有任何物品。
return;
}
// 将旋转角度应用于节点
CurrentItem.LookAt(position);
}
protected virtual void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
}
}

432
scripts/character/Player.cs Normal file
View File

@ -0,0 +1,432 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ColdMint.scripts;
using ColdMint.scripts.character;
using ColdMint.scripts.damage;
using ColdMint.scripts.database;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
/// <summary>
/// <para>玩家角色</para>
/// </summary>
public partial class Player : CharacterTemplate
{
private PackedScene FloatLabelPackedScene;
protected Control FloatLabel;
//抛物线
private Line2D Parabola;
//用于检测玩家是否站在平台上的射线
private RayCast2D PlatformDetectionRayCast2D;
//在拾捡范围内,可拾起的物品数量
private int TotalNumberOfPickups = 0;
private const float promptTextDistance = 50;
//玩家可拾捡的物品
private Node2D PickAbleItem;
//抛出物品的飞行速度
private float throwingVelocity = Config.CellSize * 13;
//射线是否与平台碰撞
private bool CollidingWithPlatform = false;
//How long does it take for the character to recover from a collision with the platform after jumping off the platform (in seconds)
//角色从平台上跳下后,多少时间后恢复与平台的碰撞(单位:秒)
private double PlatformCollisionRecoveryTime = 0.2f;
//物品被扔出后多长时间恢复与地面和平台的碰撞(单位:秒)
private double ItemCollisionRecoveryTime = 0.045f;
public override void _Ready()
{
base._Ready();
_characterName = TranslationServer.Translate("default_player_name");
FloatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
Parabola = GetNode<Line2D>("Parabola");
PlatformDetectionRayCast2D = GetNode<RayCast2D>("PlatformDetectionRayCast");
UpdateOperationTip();
GameSceneNodeHolder.HealthBarUi.MaxHp = MaxHp;
GameSceneNodeHolder.HealthBarUi.CurrentHp = CurrentHp;
}
/// <summary>
/// <para>Update operation prompt</para>
/// <para>更新操作提示</para>
/// </summary>
private void UpdateOperationTip()
{
var operationTipBuilder = new StringBuilder();
if (TotalNumberOfPickups > 0)
{
//If there's anything around to pick up
//如果周围有能捡的东西
if (CurrentItem == null)
{
if (PickAbleItem != null)
{
string name = null;
if (PickAbleItem is WeaponTemplate weaponTemplate)
{
//When the weapon has no owner, a pick up prompt is displayed.
//当武器没有主人时,显示捡起提示。
if (weaponTemplate.Owner == null || weaponTemplate.Owner == this)
{
name = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (name != null)
{
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("pick_up"));
operationTipBuilder.Append(name);
}
}
}
else
{
string pickAbleItemName = null;
string currentItemName = null;
string mustBeThrown = TranslationServer.Translate("must_be_thrown");
if (PickAbleItem != null)
{
//可捡的物品是武器
if (PickAbleItem is WeaponTemplate weaponTemplate)
{
pickAbleItemName = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (CurrentItem != null)
{
//当前持有的物品是武器
if (CurrentItem is WeaponTemplate weaponTemplate)
{
currentItemName = TranslationServer.Translate(weaponTemplate.Name);
}
}
if (pickAbleItemName != null && currentItemName != null && mustBeThrown != "must_be_thrown")
{
operationTipBuilder.Append(string.Format(mustBeThrown, currentItemName, pickAbleItemName));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("throw"));
operationTipBuilder.Append(currentItemName);
}
}
GameSceneNodeHolder.OperationTipLabel.Text = operationTipBuilder.ToString();
return;
}
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_left")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("move_left"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_right")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("move_right"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_up")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("jump"));
if (CollidingWithPlatform)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("jump_down"));
}
if (CurrentItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("throw"));
if (CurrentItem is WeaponTemplate weaponTemplate)
{
operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.Name));
//提示武器攻击
operationTipBuilder.Append(' ');
operationTipBuilder.Append(
TranslationServer.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
operationTipBuilder.Append(TranslationServer.Translate("use_item"));
operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.Name));
}
}
GameSceneNodeHolder.OperationTipLabel.Text = operationTipBuilder.ToString();
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
//在平台检测射线与平台碰撞状态改变时
if (PlatformDetectionRayCast2D.IsColliding() != CollidingWithPlatform)
{
//当状态改变时,更新操作提示
CollidingWithPlatform = PlatformDetectionRayCast2D.IsColliding();
UpdateOperationTip();
}
//如果角色正在地面上,按下跳跃键时,给予一个向上的速度
if (Input.IsActionJustPressed("ui_up") && IsOnFloor())
velocity.Y = JumpVelocity;
//左右移动
var axis = Input.GetAxis("ui_left", "ui_right");
velocity.X = axis * Speed;
//使用物品
if (Input.IsActionPressed("use_item"))
{
UseItem(GetGlobalMousePosition());
}
//捡起物品
if (Input.IsActionJustPressed("pick_up"))
{
PickUpAction();
}
if (Input.IsActionJustPressed("ui_down"))
{
if (CollidingWithPlatform)
{
//当角色站在平台上按下 ui_down 键时,我们取消角色与平台的碰撞
var timer = new Timer();
AddChild(timer);
timer.WaitTime = PlatformCollisionRecoveryTime;
timer.OneShot = true;
timer.Start();
timer.Timeout += () =>
{
SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
SetCollisionMaskValue(Config.LayerNumber.Platform, false);
}
}
//抛出物品时,显示抛物线
if (Input.IsActionPressed("throw"))
{
if (CurrentItem != null)
{
Parabola.Points =
ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), gravity, 0.1f);
}
}
//抬起手时,抛出物品
if (Input.IsActionJustReleased("throw"))
{
if (CurrentItem != null)
{
Parabola.Points = new[] { Vector2.Zero };
CurrentItem.Reparent(GameSceneNodeHolder.WeaponContainer);
if (CurrentItem is WeaponTemplate weaponTemplate)
{
var timer = new Timer();
weaponTemplate.AddChild(timer);
timer.WaitTime = ItemCollisionRecoveryTime;
timer.OneShot = true;
timer.Timeout += () =>
{
//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.
//仍出武器时,我们不能立即恢复物理碰撞,立即恢复会导致武器更早的与地面和平台碰撞,阻止武器的飞行。
weaponTemplate.EnableContactInjury = true;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
timer.Start();
weaponTemplate.Sleeping = false;
weaponTemplate.LinearVelocity = Vector2.Zero;
}
if (CurrentItem is CharacterBody2D characterBody2D)
{
characterBody2D.Velocity = GetThrowVelocity();
}
if (CurrentItem is RigidBody2D rigidBody2D)
{
rigidBody2D.LinearVelocity = GetThrowVelocity();
}
CurrentItem = null;
TotalNumberOfPickups++;
UpdateOperationTip();
}
}
}
private async Task PickUpAction()
{
var success = PickItem(PickAbleItem);
if (success)
{
//在背包内添加物品
var dataPackDbContext = DataBaseManager.GetRequiredService<DataPackDbContext>();
var itemInfoDbSet = dataPackDbContext.ItemInfo;
var query = from itemInfoData in itemInfoDbSet
where itemInfoData.Id == "staffOfTheDead" && itemInfoData.Namespace == Config.DefaultNamespace
select itemInfoData;
var itemInfo = query.FirstOrDefault();
if (itemInfo != null)
{
var item = new LocalItem(itemInfo);
GameSceneNodeHolder.HotBar.AddItem(item);
}
PickAbleItem = null;
TotalNumberOfPickups--;
if (FloatLabel != null)
{
FloatLabel.QueueFree();
FloatLabel = null;
}
UpdateOperationTip();
}
}
private Vector2 GetThrowVelocity()
{
//我们拿到鼠标的位置,将其归一化处理,然后乘以玩家可扔出的距离
return GetLocalMousePosition().Normalized() * throwingVelocity;
}
public override void _Process(double delta)
{
AimTheCurrentItemAtAPoint(GetGlobalMousePosition());
var itemMarker2DPosition = ItemMarker2D.Position;
var axis = Input.GetAxis("ui_left", "ui_right");
switch (axis)
{
case -1:
//-1向左移动
FacingLeft = true;
itemMarker2DPosition.X = -ReadOnlyItemMarkerOriginalX;
Flip();
break;
case 1:
//1向右移动
FacingLeft = false;
itemMarker2DPosition.X = ReadOnlyItemMarkerOriginalX;
Flip();
break;
default:
//0没有按下时
break;
}
ItemMarker2D.Position = itemMarker2DPosition;
}
protected override void Flip()
{
base.Flip();
//如果有武器的话,也要翻转
if (CurrentItem != null)
{
if (CurrentItem is WeaponTemplate weapon)
{
weapon.Flip(FacingLeft);
}
}
}
protected override void EnterThePickingRangeBody(Node node)
{
if (node is not Node2D)
{
return;
}
var node2D = node as Node2D;
TotalNumberOfPickups++;
PickAbleItem = node2D;
if (FloatLabel != null)
{
FloatLabel.QueueFree();
}
FloatLabel = (Control)FloatLabelPackedScene.Instantiate();
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
if (rotationDegreesNode2DAbs > 90)
{
FloatLabel.Position = new Vector2(0, promptTextDistance);
}
else
{
FloatLabel.Position = new Vector2(0, -promptTextDistance);
}
FloatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = FloatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
{
var stringBuilder = new StringBuilder();
if (weapon.Owner != null)
{
if (weapon.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.CharacterName);
stringBuilder.Append(TranslationServer.Translate("de"));
}
}
stringBuilder.Append(TranslationServer.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
}
node.AddChild(FloatLabel);
UpdateOperationTip();
}
protected override void ExitThePickingRangeBody(Node node)
{
if (node is not Node2D)
{
return;
}
TotalNumberOfPickups--;
if (TotalNumberOfPickups == 0)
{
//如果没有可捡的物品了设置为null
PickAbleItem = null;
}
if (FloatLabel != null)
{
FloatLabel.QueueFree();
FloatLabel = null;
}
UpdateOperationTip();
}
protected override void OnHit(DamageTemplate damageTemplate)
{
base.OnHit(damageTemplate);
GameSceneNodeHolder.HealthBarUi.CurrentHp = CurrentHp;
}
}

6
scripts/damage/Damage.cs Normal file
View File

@ -0,0 +1,6 @@
namespace ColdMint.scripts.damage;
public class Damage : DamageTemplate
{
}

View File

@ -0,0 +1,48 @@
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.damage;
/// <summary>
/// <para>DamageNumber</para>
/// <para>伤害数字</para>
/// </summary>
public partial class DamageNumber : CharacterBody2D
{
private VisibleOnScreenNotifier2D _visibleOnScreenNotifier2D;
public override void _Ready()
{
_visibleOnScreenNotifier2D = GetNode<VisibleOnScreenNotifier2D>("VisibleOnScreenNotifier2D");
_visibleOnScreenNotifier2D.ScreenExited += ScreenExited;
}
private void ScreenExited()
{
//When the damage number leaves the screen, destroy the damage number
//当伤害数字离开屏幕时,销毁伤害数字
QueueFree();
}
private float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
private bool enableGravity;
public void SetVelocity(Vector2 velocity)
{
Velocity = velocity;
enableGravity = true;
}
public override void _PhysicsProcess(double delta)
{
var velocity = Velocity;
if (enableGravity)
{
velocity.Y += gravity * (float)delta;
}
Velocity = velocity;
MoveAndSlide();
}
}

View File

@ -0,0 +1,148 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.damage;
/// <summary>
/// <para>Node representing the damage number</para>
/// <para>表示伤害数字的节点</para>
/// </summary>
public partial class DamageNumberNodeSpawn : Marker2D
{
private PackedScene _damageNumberPackedScene;
private Node2D _rootNode;
/// <summary>
/// <para>The horizontal velocity is in the X positive direction</para>
/// <para>水平速度的X正方向</para>
/// </summary>
private int horizontalVelocityPositiveDirection;
/// <summary>
/// <para>Horizontal velocity in the negative X direction</para>
/// <para>水平速度的X负方向</para>
/// </summary>
private int horizontalVelocityNegativeDirection;
/// <summary>
/// <para>vertical height</para>
/// <para>垂直高度</para>
/// </summary>
private int verticalHeight;
/// <summary>
/// <para>物理渐变色</para>
/// <para>physical Gradient</para>
/// </summary>
private Gradient physicalGradient;
/// <summary>
/// <para>魔法渐变色</para>
/// <para>magic Gradient</para>
/// </summary>
private Gradient magicGradient;
/// <summary>
/// <para>默认渐变色</para>
/// <para>default Gradient</para>
/// </summary>
private Gradient defaultGradient;
public override void _Ready()
{
base._Ready();
_damageNumberPackedScene = GD.Load("res://prefab/ui/DamageNumber.tscn") as PackedScene;
_rootNode = GetNode<Node2D>("/root/Game/DamageNumberContainer");
horizontalVelocityPositiveDirection = Config.CellSize * Config.HorizontalSpeedOfDamageNumbers;
horizontalVelocityNegativeDirection = -horizontalVelocityPositiveDirection;
verticalHeight = -Config.CellSize * Config.VerticalVelocityOfDamageNumbers;
physicalGradient = new Gradient();
//物理色 从OpenColor2 到 OpenColor6红色
physicalGradient.SetColor(0, new Color("#ffc9c9"));
physicalGradient.SetColor(1, new Color("#fa5252"));
magicGradient = new Gradient();
//魔法色 从OpenColor2 到 OpenColor6(紫色)
magicGradient.SetColor(0, new Color("#d0bfff"));
magicGradient.SetColor(1, new Color("#7950f2"));
defaultGradient = new Gradient();
//默认行为
defaultGradient.SetColor(0, new Color("#ff8787"));
defaultGradient.SetColor(1, new Color("#fa5252"));
}
/// <summary>
/// <para>Added a damage digital node</para>
/// <para>添加伤害数字节点</para>
/// </summary>
/// <param name="damageNumber"></param>
private void AddDamageNumberNode(Node2D damageNumber)
{
_rootNode.AddChild(damageNumber);
}
/// <summary>
/// <para>Show damage</para>
/// <para>显示伤害</para>
/// </summary>
/// <param name="damageTemplate"></param>
public void Display(DamageTemplate damageTemplate)
{
if (_damageNumberPackedScene == null)
{
return;
}
if (_damageNumberPackedScene.Instantiate() is not DamageNumber damageNumber)
{
return;
}
CallDeferred("AddDamageNumberNode", damageNumber);
damageNumber.Position = GlobalPosition;
if (damageTemplate.MoveLeft)
{
damageNumber.SetVelocity(new Vector2(horizontalVelocityNegativeDirection, verticalHeight));
}
else
{
damageNumber.SetVelocity(new Vector2(horizontalVelocityPositiveDirection, verticalHeight));
}
var damageLabel = damageNumber.GetNode<Label>("Label");
damageLabel.Text = damageTemplate.Damage.ToString();
var labelSettings = new LabelSettings();
var offset = damageTemplate.Damage / (float)damageTemplate.MaxDamage;
labelSettings.FontColor = GetDamageColorByType(damageTemplate.Type).Sample(offset);
if (damageTemplate.IsCriticalStrike)
{
labelSettings.FontSize = Config.CritDamageTextSize;
}
else
{
labelSettings.FontSize = Config.NormalDamageTextSize;
}
damageLabel.LabelSettings = labelSettings;
damageLabel.Position = Vector2.Zero;
}
/// <summary>
/// <para>Gets text color based on damage type</para>
/// <para>根据伤害类型获取文本颜色</para>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private Gradient GetDamageColorByType(int type)
{
return type switch
{
Config.DamageType.Physical => physicalGradient,
Config.DamageType.Magic => magicGradient,
_ => defaultGradient
};
}
}

View File

@ -0,0 +1,91 @@
using System;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.damage;
/// <summary>
/// <para>DamageTemplate</para>
/// <para>伤害模板</para>
/// </summary>
public abstract class DamageTemplate
{
/// <summary>
/// <para>Damage must be assigned a certain value</para>
/// <para>伤害必须指定确定的数值</para>
/// </summary>
public int Damage => _damage;
/// <summary>
/// <para>Critical Hit probability (unit: percent)</para>
/// <para>暴击几率(单位:百分比)</para>
/// </summary>
public int CriticalStrikeProbability = 5;
private int _damage;
private bool _isCriticalStrike;
/// <summary>
/// <para></para>
/// <para>攻击者</para>
/// </summary>
public Node2D Attacker { get; set; }
/// <summary>
/// <para>Whether the damage text moves to the left</para>
/// <para>伤害文本是否向左移动</para>
/// </summary>
public bool MoveLeft { get; set; }
/// <summary>
/// <para>Create actual damage with maximum and minimum values</para>
/// <para>通过最大值和最小值创建实际伤害</para>
/// </summary>
public void CreateDamage()
{
_damage = GD.RandRange(MinDamage, MaxDamage);
_isCriticalStrike = GD.RandRange(1, 100) <= CriticalStrikeProbability;
if (_isCriticalStrike)
{
_damage = (int)Math.Round(_damage * Config.CriticalHitMultiplier);
}
}
/// <summary>
/// <para>Maximum injury</para>
/// <para>最大伤害</para>
/// </summary>
public int MaxDamage { get; set; }
/// <summary>
/// <para>Minimum damage value</para>
/// <para>最小伤害值</para>
/// </summary>
public int MinDamage { get; set; }
/// <summary>
/// <para>Whether the damage is critical</para>
/// <para>本次伤害是否为暴击</para>
/// </summary>
public bool IsCriticalStrike => _isCriticalStrike;
/// <summary>
/// <para>Types of damage</para>
/// <para>伤害的类型</para>
/// </summary>
public int Type { get; set; }
/// <summary>
/// <para>An event performed before harming the enemy</para>
/// <para>在伤害敌人之前执行的事件</para>
/// </summary>
public Action<Node2D> BeforeDamage;
/// <summary>
/// <para>After damaging the enemy</para>
/// <para>在伤害敌人之后</para>
/// </summary>
public Action<Node2D> AfterDamage;
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.dataPack.local;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using Microsoft.EntityFrameworkCore.Storage;
using Array = System.Array;
namespace ColdMint.scripts.dataPack;
/// <summary>
/// <para>Packet manager</para>
/// <para>数据包管理器</para>
/// </summary>
public static class DataPackManager
{
/// <summary>
/// <para>When a packet is scanned</para>
/// <para>当一个数据包被扫描到时</para>
/// </summary>
public static Action<IDataPack>? OnScanComplete;
/// <summary>
/// <para>Load all packets in a directory</para>
/// <para>加载某个目录下的所有数据包</para>
/// </summary>
/// <param name="path"></param>
public static async Task<IDataPack[]> ScanAllDataPack(string path)
{
if (!Directory.Exists(path))
{
return Array.Empty<IDataPack>();
}
var dataPacks = new List<IDataPack>();
var files = Directory.GetFiles(path);
foreach (var file in files)
{
var dataPack = await ScanSingleDataPack(file);
if (dataPack == null)
{
continue;
}
dataPacks.Add(dataPack);
}
return dataPacks.ToArray();
}
/// <summary>
/// <para>Load a single packet</para>
/// <para>加载单个数据包</para>
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public async static Task<IDataPack?> ScanSingleDataPack(string path)
{
if (!File.Exists(path))
{
return null;
}
var dataPack = new LocalDataPack(path);
await dataPack.BuildIndex();
await dataPack.LoadManifest();
if (OnScanComplete != null)
{
OnScanComplete.Invoke(dataPack);
}
return dataPack;
}
}

View File

@ -0,0 +1,40 @@
using Godot;
namespace ColdMint.scripts.dataPack;
public class EmptyManifest : IDataPackManifest
{
private EmptyManifest()
{
}
public string? ID { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? VersionName { get; set; }
public int? VersionCode { get; set; }
public string? Author { get; set; }
public string? Namespace { get; set; }
/// <summary>
/// <para>Create an empty manifest file</para>
/// <para>创建空的清单文件</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static EmptyManifest CreateEmptyManifest(string id)
{
var emptyManifest = new EmptyManifest();
var unknown = TranslationServer.Translate("unknown");
emptyManifest.ID = id;
emptyManifest.Author = unknown;
emptyManifest.Name = unknown;
emptyManifest.Description = unknown;
emptyManifest.VersionName = unknown;
emptyManifest.Namespace = Config.EmptyNamespace;
emptyManifest.VersionCode = 0;
return emptyManifest;
}
}

View File

@ -0,0 +1,17 @@
namespace ColdMint.scripts.dataPack;
/// <summary>
/// <para>DataPack</para>
/// <para>数据包</para>
/// </summary>
public interface IDataPack
{
IDataPackManifest Manifest { get; }
/// <summary>
/// <para>Get the item's data</para>
/// <para>获取物品的数据</para>
/// </summary>
/// <returns></returns>
string GetItemsData();
}

View File

@ -0,0 +1,16 @@
namespace ColdMint.scripts.dataPack;
/// <summary>
/// <para>DataPackManifest</para>
/// <para>数据包清单文件</para>
/// </summary>
public interface IDataPackManifest
{
string? ID { get; set; }
string? Name { get; set; }
string? Description { get; set; }
string? VersionName { get; set; }
int? VersionCode { get; set; }
string? Author { get; set; }
string? Namespace { get; set; }
}

View File

@ -0,0 +1,94 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.dataPack.local;
using ColdMint.scripts.serialization;
namespace ColdMint.scripts.dataPack.entryLoader;
/// <summary>
/// <para>Load the manifest file in the zip package and write it to the data table</para>
/// <para>在zip包内加载清单文件将其写入数据表</para>
/// </summary>
public class DataPackManifestLoader : IEntryLoader
{
public string Namespace => _namespace;
//清单文件的命名空间
private string _namespace;
public bool NeedLoad(ZipArchiveEntry archiveEntry)
{
return archiveEntry.FullName == Config.DataPackManifestName;
}
public async Task ExecutionLoad(string namespaceString, string zipFileName, DataPackDbContext dataPackDbContext,
ZipArchiveEntry archiveEntry)
{
//Do not use namespaceString within the DataPackManifestLoader's ExecutionLoad method, as this value is assigned in the following code.
//不要在DataPackManifestLoader的ExecutionLoad方法内使用namespaceString因为这个值是在下面的代码内赋值的。
var nowDateTime = DateTime.Now;
IDataPackManifest? dataPackManifest = null;
//When the manifest file is obtained, load the file information
//在获取到清单文件时,加载文件信息
await using (var stream = archiveEntry.Open())
{
var localDataPackManifest =
await JsonSerialization.ReadJsonFileToObj<LocalDataPackManifest>(stream);
if (localDataPackManifest == null)
{
dataPackManifest = EmptyManifest.CreateEmptyManifest(zipFileName);
}
else
{
dataPackManifest = localDataPackManifest;
}
}
if (dataPackManifest != null)
{
var dataPackInfoDbSet = dataPackDbContext.DataPackInfo;
var dataPackQuery = from dataPack in dataPackInfoDbSet
where dataPack.ZipFileName == zipFileName
select dataPack;
var oldDataPackInfo = dataPackQuery.FirstOrDefault();
if (oldDataPackInfo == null)
{
//There was no list to record before, create one.
//之前没有清单记录,创建一份。
await dataPackDbContext.DataPackInfo.AddAsync(new DataPackInfo
{
ID = dataPackManifest.ID,
Author = dataPackManifest.Author,
Description = dataPackManifest.Description,
Name = dataPackManifest.Name,
Namespace = dataPackManifest.Namespace,
VersionCode = dataPackManifest.VersionCode,
VersionName = dataPackManifest.VersionName,
ZipFileName = zipFileName,
UpdateTime = nowDateTime,
CrateTime = nowDateTime
});
}
else
{
//It's already on the record. Update.
//已经有记录了,更新。
oldDataPackInfo.Name = dataPackManifest.Name;
oldDataPackInfo.Author = dataPackManifest.Author;
oldDataPackInfo.Description = dataPackManifest.Description;
oldDataPackInfo.Namespace = dataPackManifest.Namespace;
oldDataPackInfo.VersionCode = dataPackManifest.VersionCode;
oldDataPackInfo.VersionName = dataPackManifest.VersionName;
oldDataPackInfo.UpdateTime = nowDateTime;
dataPackDbContext.DataPackInfo.Update(oldDataPackInfo);
}
_namespace = dataPackManifest.Namespace ?? string.Empty;
}
}
}

View File

@ -0,0 +1,31 @@
using System.IO.Compression;
using System.Threading.Tasks;
using ColdMint.scripts.database;
namespace ColdMint.scripts.dataPack.entryLoader;
public interface IEntryLoader
{
/// <summary>
/// <para>Whether to load</para>
/// <para>是否需要加载</para>
/// </summary>
/// <param name="archiveEntry"></param>
/// <returns></returns>
bool NeedLoad(ZipArchiveEntry archiveEntry);
/// <summary>
/// <para>Execution load</para>
/// <para>执行加载</para>
/// </summary>
///<remarks>
///<para>It is only necessary to add or update data to dataPackDbContext in this method. When the scan is completed, the upper layer code will be uniformly submitted to the database</para>
///<para>仅需要在此方法内将数据add或者update到dataPackDbContext内当扫描结束后上层代码会统一提交到数据库</para>
/// <para>Do not query the existence of the old project from the database within this method, because the save request is also submitted to the database.</para>
/// <para>不要在此方法内从数据库查询旧的项目是否存在,因为还为向数据库提交保存请求。</para>
/// </remarks>
/// <param name="archiveEntry"></param>
Task ExecutionLoad(string namespaceString, string zipFileName, DataPackDbContext dataPackDbContext,
ZipArchiveEntry archiveEntry);
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.debug;
using ColdMint.scripts.serialization;
namespace ColdMint.scripts.dataPack.entryLoader;
/// <summary>
/// <para>Load item information into the data table in the manifest file</para>
/// <para>在清单文件内加载物品信息到数据表</para>
/// </summary>
public class ItemLoader : IEntryLoader
{
private HashSet<string> _itemIdSet = new HashSet<string>();
public bool NeedLoad(ZipArchiveEntry archiveEntry)
{
return archiveEntry.FullName.StartsWith(Config.ItemStartPathName) &&
archiveEntry.FullName.EndsWith(Config.DataPackSourceFileFomat);
}
public async Task ExecutionLoad(string namespaceString, string zipFileName, DataPackDbContext dataPackDbContext,
ZipArchiveEntry archiveEntry)
{
await using var stream = archiveEntry.Open();
//从文件中读取物品信息
var itemInfo = await JsonSerialization.ReadJsonFileToObj<ItemInfo>(stream);
if (itemInfo == null)
{
return;
}
if (_itemIdSet.Contains(itemInfo.Id))
{
LogCat.LogErrorWithFormat("duplicate_at_path_id", zipFileName, archiveEntry.FullName, itemInfo.Id);
return;
}
if (itemInfo.MaxStackQuantity <= 0 || itemInfo.MaxStackQuantity > Config.MaxStackQuantity)
{
itemInfo.MaxStackQuantity = Config.MaxStackQuantity;
}
if (itemInfo.Quantity <= 0)
{
itemInfo.Quantity = 1;
}
if (itemInfo.Quantity > Config.MaxStackQuantity)
{
itemInfo.Quantity = Config.MaxStackQuantity;
}
itemInfo.Namespace = namespaceString;
var itemDbSet = dataPackDbContext.ItemInfo;
itemInfo.ZipFileName = zipFileName;
itemInfo.CrateTime = DateTime.Now;
await itemDbSet.AddAsync(itemInfo);
_itemIdSet.Add(itemInfo.Id);
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.debug;
namespace ColdMint.scripts.dataPack.entryLoader;
public class SpriteLoader : IEntryLoader
{
private HashSet<string> _spriteNameSet = new HashSet<string>();
public bool NeedLoad(ZipArchiveEntry archiveEntry)
{
return archiveEntry.FullName.StartsWith(Config.SpriteStartPathName);
}
public async Task ExecutionLoad(string namespaceString, string zipFileName, DataPackDbContext dataPackDbContext,
ZipArchiveEntry archiveEntry)
{
var fileName = Path.GetFileNameWithoutExtension(archiveEntry.FullName);
if (_spriteNameSet.Contains(fileName))
{
LogCat.LogErrorWithFormat("duplicate_at_path_id", zipFileName, archiveEntry.FullName, fileName);
return;
}
var spriteDbSet = dataPackDbContext.SpriteInfo;
//如果没有记录,创建一份。
var spriteInfo = new SpriteInfo
{
FileName = fileName,
Namespace = namespaceString,
FullName = archiveEntry.FullName,
ZipFileName = zipFileName,
CrateTime = DateTime.Now
};
await spriteDbSet.AddAsync(spriteInfo);
_spriteNameSet.Add(fileName);
}
}

View File

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.dataPack.entryLoader;
using ColdMint.scripts.debug;
using ColdMint.scripts.serialization;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.dataPack.local;
/// <summary>
/// <para>LocalDataPack</para>
/// <para>本地数据包</para>
/// </summary>
public class LocalDataPack : IDataPack
{
private string zipFilePath;
private string zipFileName;
private IDataPackManifest? manifest;
public LocalDataPack(string zipFilePath)
{
this.zipFilePath = zipFilePath;
zipFileName = Path.GetFileName(zipFilePath);
}
/// <summary>
/// <para>Create index information for packets in the database</para>
/// <para>在数据库内为数据包创建索引信息</para>
/// </summary>
public async Task BuildIndex()
{
//我们首先根据文件名在数据表内查找对应的Md5值在判断Md5值是否发生变化。
var entryLoaders = new List<IEntryLoader>();
entryLoaders.Add(new ItemLoader());
entryLoaders.Add(new SpriteLoader());
var md5 = Md5Utils.GetFileMd5(zipFilePath);
var dataPackDbContext = DataBaseManager.GetRequiredService<DataPackDbContext>();
var zipFileInfoDbSet = dataPackDbContext.ZipFileInfo;
var query = from zipFileInfo in zipFileInfoDbSet
where zipFileInfo.ZipFileName == zipFileName
select zipFileInfo;
var oldZipFileInfo = query.FirstOrDefault();
if (oldZipFileInfo == null || oldZipFileInfo.ZipFileMd5 != md5)
{
//Get the list file GetEntry internal Dictionary based implementation, very fast
//获取清单文件 GetEntry内部基于Dictionary实现速度很快
//If there is no manifest file, we do not scan the zip and only save the Md5 value for next check
//如果没有清单文件我们不扫描zip仅保存Md5值以便下次检查
using var archive = ZipFile.Open(zipFilePath, ZipArchiveMode.Read, Encoding.GetEncoding("GBK"));
var manifestEntry = archive.GetEntry(Config.DataPackManifestName);
if (manifestEntry == null)
{
LogCat.LogErrorWithFormat("no_manifest_file", zipFilePath);
}
else
{
var dataPackManifestLoader = new DataPackManifestLoader();
await dataPackManifestLoader.ExecutionLoad(string.Empty, zipFileName, dataPackDbContext, manifestEntry);
LogCat.LogWithFormat("build_an_index", zipFilePath);
var zipEntryInfoDbSet = dataPackDbContext.ZipEntryInfo;
if (oldZipFileInfo != null)
{
//Delete old records that are outdated
//删除过时的旧记录
var entriesToDelete = zipEntryInfoDbSet
.Where(entry => entry.FileName == zipFileName)
.ToList();
dataPackDbContext.ZipEntryInfo.RemoveRange(entriesToDelete);
var itemsToDelete = dataPackDbContext.ItemInfo
.Where(item => item.ZipFileName == zipFileName)
.ToList();
dataPackDbContext.ItemInfo.RemoveRange(itemsToDelete);
var spritesToDelete = dataPackDbContext.SpriteInfo
.Where(sprite => sprite.ZipFileName == zipFileName)
.ToList();
dataPackDbContext.SpriteInfo.RemoveRange(spritesToDelete);
await dataPackDbContext.SaveChangesAsync();
}
foreach (var entry in archive.Entries)
{
if (entry.FullName.EndsWith(Config.ZipPathCharacter))
{
//Ignore folders
//忽略文件夹
continue;
}
var nowDateTime = DateTime.Now;
foreach (var entryLoader in entryLoaders)
{
var needLoad = entryLoader.NeedLoad(entry);
if (needLoad)
{
await entryLoader.ExecutionLoad(dataPackManifestLoader.Namespace, zipFileName,
dataPackDbContext, entry);
}
}
var zipEntryInfo = new ZipEntryInfo
{
FullName = entry.FullName,
FileName = zipFileName,
CrateTime = nowDateTime
};
LogCat.LogWithFormat("add_file_index", entry.FullName);
await zipEntryInfoDbSet.AddAsync(zipEntryInfo);
}
}
if (oldZipFileInfo == null)
{
//创建一份
var zipNowDateTime = DateTime.Now;
var zipFileInfo = new ZipFileInfo
{
ZipFileName = zipFileName,
ZipFileMd5 = md5,
EntryCount = archive.Entries.Count,
CrateTime = zipNowDateTime,
UpdateTime = zipNowDateTime
};
dataPackDbContext.ZipFileInfo.Add(zipFileInfo);
}
else
{
var zipNowDateTime = DateTime.Now;
oldZipFileInfo.UpdateTime = zipNowDateTime;
oldZipFileInfo.ZipFileMd5 = md5;
oldZipFileInfo.EntryCount = archive.Entries.Count;
dataPackDbContext.Update(oldZipFileInfo);
}
try
{
await dataPackDbContext.SaveChangesAsync();
}
catch (Exception e)
{
LogCat.LogError(e);
}
LogCat.LogWithFormat("index_updated", zipFilePath);
return;
}
//没有变化什么也不做
LogCat.LogWithFormat("index_is_up_to_date", zipFilePath);
}
/// <summary>
/// <para>Load manifest file</para>
/// <para>加载清单文件</para>
/// </summary>
public async Task LoadManifest()
{
var dataPackDbContext = DataBaseManager.GetRequiredService<DataPackDbContext>();
var dataPackInfoDbSet = dataPackDbContext.DataPackInfo;
var dataPackInfo = from dataPack in dataPackInfoDbSet
where dataPack.ZipFileName == zipFileName
select dataPack;
if (dataPackInfo != null)
{
manifest = dataPackInfo.FirstOrDefault();
}
}
public IDataPackManifest Manifest => manifest ?? EmptyManifest.CreateEmptyManifest(zipFileName);
/// <summary>
/// <para>Get the item's data</para>
/// <para>获取物品的数据</para>
/// </summary>
/// <returns></returns>
public string GetItemsData()
{
return Path.Join(zipFilePath, "items");
}
}

View File

@ -0,0 +1,18 @@
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using ColdMint.scripts.serialization;
namespace ColdMint.scripts.dataPack.local;
public class LocalDataPackManifest: IDataPackManifest
{
public string? ID { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? VersionName { get; set; }
public int? VersionCode { get; set; }
public string? Author { get; set; }
public string? Namespace { get; set; }
}

View File

@ -0,0 +1,48 @@
using System.IO;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.debug;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace ColdMint.scripts.database;
/// <summary>
/// <para>Game database manager</para>
/// <para>游戏数据库管理器</para>
/// </summary>
public static class DataBaseManager
{
private static ServiceProvider serviceProvider;
/// <summary>
/// <para>Initialize database</para>
/// <para>初始化数据库</para>
/// </summary>
public static void InitDataBases(string databasePath)
{
if (!Directory.Exists(databasePath))
{
return;
}
var serviceCollection = new ServiceCollection();
serviceCollection.AddDbContext<DataPackDbContext>(options =>
options.UseSqlite($"Data Source={Path.Join(databasePath, "DataPack.db")}"));
serviceProvider = serviceCollection.BuildServiceProvider();
var dataPackDbContext = GetRequiredService<DataPackDbContext>();
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
{
var scope = serviceProvider.CreateScope();
return scope.ServiceProvider.GetRequiredService<T>();
}
}

View File

@ -0,0 +1,21 @@
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.debug;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace ColdMint.scripts.database;
public class DataPackDbContext : DbContext
{
public DbSet<DataPackInfo> DataPackInfo { get; set; }
public DbSet<ZipEntryInfo> ZipEntryInfo { get; set; }
public DbSet<ZipFileInfo> ZipFileInfo { get; set; }
public DbSet<ItemInfo> ItemInfo { get; set; }
public DbSet<SpriteInfo> SpriteInfo { get; set; }
public DataPackDbContext(DbContextOptions<DataPackDbContext> options) : base(options)
{
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using ColdMint.scripts.dataPack;
namespace ColdMint.scripts.database.dataPackEntity;
public class DataPackInfo : IDataPackManifest
{
[Key] public string? ID { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? VersionName { get; set; }
public int? VersionCode { get; set; }
public string? Author { get; set; }
public string? Namespace { get; set; }
/// <summary>
/// <para>Whether the status is enabled</para>
/// <para>是否为启用状态</para>
/// </summary>
public bool IsEnabled { get; set; }
public string ZipFileName { get; set; }
public DateTime CrateTime { get; set; }
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,26 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using ColdMint.scripts.inventory;
using Godot;
namespace ColdMint.scripts.database.dataPackEntity;
public class ItemInfo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Index { get; set; }
public string Id { get; set; }
public int Quantity { get; set; }
public int MaxStackQuantity { get; set; }
public string? Icon { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string ZipFileName { get; set; }
public string Namespace { get; set; }
public DateTime CrateTime { get; set; }
}

View File

@ -0,0 +1,26 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ColdMint.scripts.database.dataPackEntity;
public class SpriteInfo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Index { get; set; }
/// <summary>
/// <para>The file name is the Id of the Sprite</para>
/// <para>文件名就是精灵的Id</para>
/// </summary>
public string FileName { get; set; }
public string FullName { get; set; }
public string ZipFileName { get; set; }
public string Namespace { get; set; }
public DateTime CrateTime { get; set; }
}

View File

@ -0,0 +1,35 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ColdMint.scripts.database.dataPackEntity;
/// <summary>
/// <para>entry table in Zip file</para>
/// <para>Zip文件内的entry表</para>
/// </summary>
public class ZipEntryInfo
{
/// <summary>
/// <para>Primary key, auto increment</para>
/// <para>主键,自动递增</para>
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
/// <summary>
/// <para>This record is from that zip file</para>
/// <para>这段记录是来源于那个zip文件的</para>
/// </summary>
public string FileName { get; set; }
/// <summary>
/// <para>The path within the zip file</para>
/// <para>位于zip文件内的路径</para>
/// </summary>
public string FullName { get; set; }
public DateTime CrateTime { get; set; }
}

View File

@ -0,0 +1,15 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace ColdMint.scripts.database.dataPackEntity;
public class ZipFileInfo
{
[Key] public string ZipFileName { get; set; }
public string ZipFileMd5 { get; set; }
public int EntryCount { get; set; }
public DateTime CrateTime { get; set; }
public DateTime UpdateTime { get; set; }
}

65
scripts/debug/LogCat.cs Normal file
View File

@ -0,0 +1,65 @@
using System;
using System.Text;
using Godot;
namespace ColdMint.scripts.debug;
public class LogCat
{
private static readonly StringBuilder _stringBuilder = new StringBuilder();
private static StringBuilder HandleMessage(string message)
{
_stringBuilder.Clear();
_stringBuilder.Append(DateTime.Now.ToString("yyyy-M-d HH:mm:ss |"));
_stringBuilder.Append(TranslationServer.Translate(message));
return _stringBuilder;
}
/// <summary>
/// <para>Print log</para>
/// <para>打印日志</para>
/// </summary>
/// <param name="message">
///<para>message</para>
///<para>消息</para>
/// <para>This message supports localized output, assuming there is already a translation key, Hello = 你好, passing hello will output 你好.</para>
/// <para>这个消息支持本地化输出假设已存在翻译keyHello = 你好传入Hello则会输出你好。</para>
/// </param>
public static void Log(string message)
{
GD.Print(HandleMessage(message));
}
/// <summary>
/// <para>Print error log</para>
/// <para>打印错误日志</para>
/// </summary>
/// <param name="message">
///<para>message</para>
///<para>消息</para>
/// <para>This message supports localized output, assuming there is already a translation key, Hello = 你好, passing hello will output 你好.</para>
/// <para>这个消息支持本地化输出假设已存在翻译keyHello = 你好传入Hello则会输出你好。</para>
/// </param>
public static void LogError(string message)
{
GD.PrintErr(HandleMessage(message));
}
public static void LogErrorWithFormat(string message, params object?[] args)
{
GD.PrintErr(string.Format(HandleMessage(message).ToString(), args));
}
public static void LogWithFormat(string message, params object?[] args)
{
GD.Print(string.Format(HandleMessage(message).ToString(), args));
}
public static void LogError(Exception e)
{
GD.PrintErr(HandleMessage(e.Message).Append('\n').Append(e.StackTrace));
}
}

View File

@ -0,0 +1,38 @@
using Godot;
namespace ColdMint.scripts.health;
/// <summary>
/// <para>Health bar</para>
/// <para>健康条</para>
/// </summary>
public partial class HealthBar : TextureProgressBar
{
/// <summary>
/// <para>Set to a player-friendly blood bar style</para>
/// <para>设置为与玩家友好的血条样式</para>
/// </summary>
public void SetFriendlyTones()
{
//边框颜色为Open Gray 0
TintOver = new Color("#f8f9fa");
//背景色为Open Green 1
TintUnder = new Color("#d3f9d8");
//进度条颜色为Open Green 5
TintProgress = new Color("#51cf66");
}
/// <summary>
/// <para>Set the blood bar style to be hostile to the player</para>
/// <para>设置为与玩家敌对的血条样式</para>
/// </summary>
public void SetEnemyTones()
{
//边框颜色为Open Gray 0
TintOver = new Color("#f8f9fa");
//背景色为Open Red 1
TintUnder = new Color("#ffe3e3");
//进度条颜色为Open Red 5
TintProgress = new Color("#ff6b6b");
}
}

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using Godot;
namespace ColdMint.scripts.inventory;
public partial class HotBar : HBoxContainer
{
private PackedScene _itemSlotPackedScene;
private List<ItemSlotNode> _itemSlotNodes;
public override void _Ready()
{
base._Ready();
_itemSlotNodes = new List<ItemSlotNode>();
_itemSlotPackedScene = GD.Load<PackedScene>("res://prefab/ui/ItemSlot.tscn");
for (int i = 0; i < 10; i++)
{
AddItemSlot();
}
}
/// <summary>
/// <para>Add an item to the HotBar</para>
/// <para>在HotBar内添加一个物品</para>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool AddItem(IItem item)
{
return _itemSlotNodes.Count != 0 && _itemSlotNodes.Any(itemSlotNode => itemSlotNode.SetItem(item));
}
/// <summary>
/// <para>Add items tank</para>
/// <para>添加物品槽</para>
/// </summary>
private void AddItemSlot()
{
var node = _itemSlotPackedScene.Instantiate();
AddChild(node);
_itemSlotNodes.Add(node as ItemSlotNode);
}
}

View File

@ -0,0 +1,51 @@
using System;
using Godot;
namespace ColdMint.scripts.inventory;
public interface IItem
{
/// <summary>
/// <para>Item and ID</para>
/// <para>物品还有ID</para>
/// </summary>
string Id { get; }
/// <summary>
/// <para>Represents the quantity of this item</para>
/// <para>表示此物品的数量</para>
/// </summary>
int Quantity { get; set; }
/// <summary>
/// <para>How many can this item stack up to</para>
/// <para>这个物品最多叠加到多少个</para>
/// </summary>
int MaxStackQuantity { get; }
/// <summary>
/// <para>Items can be set with Icon</para>
/// <para>物品可以设置图标</para>
/// </summary>
Texture2D Icon { get; set; }
/// <summary>
/// <para>Item has a name</para>
/// <para>物品有名称</para>
/// </summary>
string Name { get; }
string Namespace { get; }
/// <summary>
/// <para>When using items</para>
/// <para>当使用物品时</para>
/// </summary>
Action<IItem> OnUse { get; set; }
/// <summary>
/// <para>When removing items from the backpack, instantiate them</para>
/// <para>当从背包内取出,实例化物品时</para>
/// </summary>
Func<IItem, Node> OnInstantiation { get; set; }
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace ColdMint.scripts.inventory;
public static class ItemManager
{
private static Dictionary<string, IItem> _dictionary = new Dictionary<string, IItem>();
/// <summary>
/// <para>Add items to Item Manager</para>
/// <para>在物品管理器内添加物品</para>
/// </summary>
/// <param name="item"></param>
public static void AddItem(IItem item)
{
var key = GetKey(item);
if (_dictionary.ContainsKey(key))
{
return;
}
_dictionary.Add(key, item);
}
private static string GetKey(IItem item)
{
return item.Namespace + item.Id;
}
}

View File

@ -0,0 +1,90 @@
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>A slot in the inventory</para>
/// <para>物品栏内的一个插槽</para>
/// </summary>
public partial class ItemSlotNode : MarginContainer
{
private IItem? _item;
private TextureRect _backgroundTextureRect;
private TextureRect _iconTextureRect;
private Label _quantityLabel;
/// <summary>
/// <para>Sets items for the item slot</para>
/// <para>为物品槽设置物品</para>
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool SetItem(IItem item)
{
if (_item == null)
{
if (item.Icon != null)
{
_iconTextureRect.Texture = item.Icon;
}
_item = item;
UpdateQuantityLabel(item.Quantity);
return true;
}
else
{
//This inventory already has items, but the items in this inventory are not the same as the incoming items
//这个物品栏已经有物品了,但是这个物品栏的物品和传入的物品不一样
if (_item.Id != item.Id)
{
return false;
}
var newQuantity = _item.Quantity + item.Quantity;
if (newQuantity > item.MaxStackQuantity)
{
//If the amount of the current item exceeds the maximum stack amount after placing it in this inventory
//如果将当前物品放置到这个物品栏后,数量超过了最大叠加数量
return false;
}
_item.Quantity = newQuantity;
UpdateQuantityLabel(newQuantity);
return true;
}
}
/// <summary>
/// <para>Update quantity label</para>
/// <para>更新数量标签</para>
/// </summary>
/// <param name="quantity"></param>
private void UpdateQuantityLabel(int? quantity)
{
switch (quantity)
{
case null:
_quantityLabel.Visible = false;
return;
case > 1:
//When the quantity is greater than 1, we display the quantity.
//当数量大于1时我们显示数量
_quantityLabel.Text = quantity.ToString();
_quantityLabel.Visible = true;
break;
default:
_quantityLabel.Visible = false;
break;
}
}
public override void _Ready()
{
_backgroundTextureRect =
GetNode<TextureRect>("BackgroundTexture");
_iconTextureRect = GetNode<TextureRect>("BackgroundTexture/CenterContainer/IconTextureRect");
_quantityLabel = GetNode<Label>("Control/QuantityLabel");
_quantityLabel.Visible = false;
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ColdMint.scripts.database;
using ColdMint.scripts.database.dataPackEntity;
using ColdMint.scripts.debug;
using ColdMint.scripts.serialization;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Local Item</para>
/// <para>本地Item</para>
/// </summary>
public class LocalItem : IItem
{
private ItemInfo _itemInfo;
private int quantity;
public LocalItem(ItemInfo itemInfo)
{
_itemInfo = itemInfo;
quantity = itemInfo.Quantity;
}
public string Id => _itemInfo.Id;
public int Quantity
{
get => quantity;
set { quantity = value; }
}
public int MaxStackQuantity => _itemInfo.MaxStackQuantity;
public Texture2D Icon { get; set; }
public string Name => _itemInfo.Name;
public string Namespace => _itemInfo.Namespace;
public Action<IItem> OnUse { get; set; }
public Func<IItem, Node> OnInstantiation { get; set; }
}

View File

@ -0,0 +1,87 @@
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.loader.uiLoader;
using ColdMint.scripts.map;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
using ColdMint.scripts.map.roomHolder;
using ColdMint.scripts.map.RoomPlacer;
using ColdMint.scripts.map.RoomProvider;
using ColdMint.scripts.map.slotsMatcher;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.loader.sceneLoader;
public partial class GameSceneLoader : SceneLoaderTemplate
{
private IMapGenerator _mapGenerator;
private IMapGeneratorConfig _mapGeneratorConfig;
public override void InitializeData()
{
//加载血条场景
var healthBarUI = GetNode<HealthBarUi>("CanvasLayer/Control/VBoxContainer/HealthBarUi");
NodeUtils.DeleteAllChild(healthBarUI);
GameSceneNodeHolder.HealthBarUi = healthBarUI;
//加载HotBar
var hotBar = GetNode<HotBar>("CanvasLayer/Control/VBoxContainer/HotBar");
NodeUtils.DeleteAllChild(hotBar);
GameSceneNodeHolder.HotBar = hotBar;
//加载操作提示
var operationTip = GetNode<Label>("CanvasLayer/Control/VBoxContainer/OperationTip");
GameSceneNodeHolder.OperationTipLabel = operationTip;
//加载武器容器
var weaponContainer = GetNode<Node2D>("WeaponContainer");
GameSceneNodeHolder.WeaponContainer = weaponContainer;
_mapGenerator = new MapGenerator();
_mapGenerator.TimeOutPeriod = 15;
_mapGenerator.RoomHolder = new RoomHolder();
_mapGenerator.RoomSlotsMatcher = new RoomSlotsMatcher();
var roomProvider = new RoomProvider();
//添加房间模板
var initialRoom = new RoomTemplate("res://prefab/roomTemplates/dungeon/initialRoom.tscn");
var utilityRoom = new RoomTemplate("res://prefab/roomTemplates/dungeon/utilityRoom.tscn");
initialRoom.MaxNumber = 1;
var horizontalCorridorWithSewer =
new RoomTemplate("res://prefab/roomTemplates/dungeon/horizontalCorridorWithSewer.tscn");
var horizontalCorridor = new RoomTemplate("res://prefab/roomTemplates/dungeon/horizontalCorridor.tscn");
roomProvider.AddRoom(initialRoom);
roomProvider.AddRoom(horizontalCorridor);
roomProvider.AddRoom(horizontalCorridorWithSewer);
roomProvider.AddRoom(utilityRoom);
_mapGenerator.RoomProvider = roomProvider;
var roomPlacer = new RoomPlacer();
_mapGeneratorConfig = new MapGeneratorConfig(GetNode<Node2D>("MapRoot"), 1);
roomPlacer.MapGeneratorConfig = _mapGeneratorConfig;
_mapGenerator.RoomPlacer = roomPlacer;
}
public override void LoadScene()
{
_mapGenerator.Generate(_mapGeneratorConfig);
var packedScene = GD.Load<PackedScene>("res://prefab/entitys/Character.tscn");
//在持有者内注册玩家
GameSceneNodeHolder.Player = (Player)packedScene.Instantiate();
var node2D = (Node2D)packedScene.Instantiate();
var gameRoot = GetNode<Node2D>(".");
gameRoot.AddChild(node2D);
node2D.Position = new Vector2(55, 70);
var delivererOfDarkMagicPackedScene = GD.Load<PackedScene>("res://prefab/entitys/DelivererOfDarkMagic.tscn");
var delivererOfDarkMagicPackedSceneNode2D = (Node2D)delivererOfDarkMagicPackedScene.Instantiate();
gameRoot.AddChild(delivererOfDarkMagicPackedSceneNode2D);
delivererOfDarkMagicPackedSceneNode2D.Position = new Vector2(70, 70);
//加载武器
var w = GD.Load<PackedScene>("res://prefab/weapons/staffOfTheUndead.tscn");
for (int i = 0; i < 3; i++)
{
var wn = (Node2D)w.Instantiate();
wn.Position = new Vector2(55, 90);
GameSceneNodeHolder.WeaponContainer.AddChild(wn);
}
}
}

View File

@ -0,0 +1,16 @@
namespace ColdMint.scripts.loader.sceneLoader;
public interface ISceneLoaderContract
{
/// <summary>
/// <para>initialization data</para>
/// <para>初始化数据</para>
/// </summary>
void InitializeData();
/// <summary>
/// <para>load scene</para>
/// <para>加载场景</para>
/// </summary>
void LoadScene();
}

Some files were not shown because too many files have changed in this diff Show More