commit 960c39730326be6749ff42e827c20c8fa21a8e7f
Author: TsubakiLoL <2789646812@qq.com>
Date: Fri Oct 25 15:41:39 2024 +0800
add
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0af181c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+# Godot 4+ specific ignores
+.godot/
+/android/
diff --git a/TEST/mystic_woods_free_2.2/read_me.txt b/TEST/mystic_woods_free_2.2/read_me.txt
new file mode 100644
index 0000000..ed05e65
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/read_me.txt
@@ -0,0 +1,13 @@
+Howdy! Thank you for downloading the Mystic Woods asset pack.
+
+This is an ongoing project that I will be adding content to over time, so let me know if there's anything you'd like for me to create.
+
+License - Free Version
+ - You can only use these assets in non-commercial projects.
+ - You can modify the assets.
+ - You can not redistribute or resale, even if modified.
+
+Follow me on Twitter for updates on all of my projects.
+https://twitter.com/GameEndeavor
+
+If you enjoy this then leave a rating and comment. It helps to support this project!
\ No newline at end of file
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/README.txt b/TEST/mystic_woods_free_2.2/sprites/characters/README.txt
new file mode 100644
index 0000000..edebaa5
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/characters/README.txt
@@ -0,0 +1,18 @@
+Player and skeleton sprites are on a 48x48 grid.
+Slime is on a 32x32 grid.
+
+Flip right facing sprites to get the left facing sprites.
+
+Animations [rows (0 based for us programmers)]
+Player:
+[0 - 2] idle
+[3 - 5] move
+[6 - 8] attack
+[9] death
+
+Enemies:
+[0 - 2] idle
+[3 - 5] move
+[6 - 8] attack
+[9 - 11] damaged
+[12] death
\ No newline at end of file
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/player.png b/TEST/mystic_woods_free_2.2/sprites/characters/player.png
new file mode 100644
index 0000000..b22f4b3
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/characters/player.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/player.png.import b/TEST/mystic_woods_free_2.2/sprites/characters/player.png.import
new file mode 100644
index 0000000..66f2bc6
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/characters/player.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b8mr80al21rka"
+path="res://.godot/imported/player.png-6c5d9809679900d37526f87878eeca81.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/characters/player.png"
+dest_files=["res://.godot/imported/player.png-6c5d9809679900d37526f87878eeca81.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png
new file mode 100644
index 0000000..763b6ee
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png.import b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png.import
new file mode 100644
index 0000000..09a29f8
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://csh4bp80n0jqu"
+path="res://.godot/imported/skeleton.png-c741bdfc3b3bceef37fc96f2409dde34.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/characters/skeleton.png"
+dest_files=["res://.godot/imported/skeleton.png-c741bdfc3b3bceef37fc96f2409dde34.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png
new file mode 100644
index 0000000..3dbe1e2
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png.import b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png.import
new file mode 100644
index 0000000..9ad6c2c
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qg24llaiojac"
+path="res://.godot/imported/skeleton_swordless.png-cb67559e57d51e44b259e3220ec81f18.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/characters/skeleton_swordless.png"
+dest_files=["res://.godot/imported/skeleton_swordless.png-cb67559e57d51e44b259e3220ec81f18.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/slime.png b/TEST/mystic_woods_free_2.2/sprites/characters/slime.png
new file mode 100644
index 0000000..3b5dc54
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/characters/slime.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/characters/slime.png.import b/TEST/mystic_woods_free_2.2/sprites/characters/slime.png.import
new file mode 100644
index 0000000..456755c
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/characters/slime.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cs24y4rmilkg6"
+path="res://.godot/imported/slime.png-017622d32381f7a679938c4e151998ab.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/characters/slime.png"
+dest_files=["res://.godot/imported/slime.png-017622d32381f7a679938c4e151998ab.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png b/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png
new file mode 100644
index 0000000..d799879
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png.import
new file mode 100644
index 0000000..310639e
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bgow2a3rs7q4j"
+path="res://.godot/imported/chest_01.png-c2e99bfd7e7e892ed7f8651520769f4f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/chest_01.png"
+dest_files=["res://.godot/imported/chest_01.png-c2e99bfd7e7e892ed7f8651520769f4f.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png b/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png
new file mode 100644
index 0000000..361b386
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png.import
new file mode 100644
index 0000000..8f494bd
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5yvqfk63mcq8"
+path="res://.godot/imported/chest_02.png-b6c6487f808784e97570961d0c4dfc50.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/chest_02.png"
+dest_files=["res://.godot/imported/chest_02.png-b6c6487f808784e97570961d0c4dfc50.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/objects.png b/TEST/mystic_woods_free_2.2/sprites/objects/objects.png
new file mode 100644
index 0000000..53175d6
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/objects.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/objects.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/objects.png.import
new file mode 100644
index 0000000..076541c
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/objects.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ox0pbgoe4gpa"
+path="res://.godot/imported/objects.png-88a8f30761719fe7ea83996f3040f370.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/objects.png"
+dest_files=["res://.godot/imported/objects.png-88a8f30761719fe7ea83996f3040f370.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png
new file mode 100644
index 0000000..5c9d12e
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png.import
new file mode 100644
index 0000000..b390ce0
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hsr83f2epk1x"
+path="res://.godot/imported/rock_in_water-sheet.png-494a098f01fbbb8d4eeab900b8b7f91b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water-sheet.png"
+dest_files=["res://.godot/imported/rock_in_water-sheet.png-494a098f01fbbb8d4eeab900b8b7f91b.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png
new file mode 100644
index 0000000..5c9d12e
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png.import
new file mode 100644
index 0000000..ed75811
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vt57eibv22qt"
+path="res://.godot/imported/rock_in_water_01-sheet.png-7c5ed78fe97440039700ee7e4305fcf1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01-sheet.png"
+dest_files=["res://.godot/imported/rock_in_water_01-sheet.png-7c5ed78fe97440039700ee7e4305fcf1.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png
new file mode 100644
index 0000000..6aeb07c
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png.import
new file mode 100644
index 0000000..c5e6e2f
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cp73joxb56lep"
+path="res://.godot/imported/rock_in_water_01.png-d49d566ddad27e087af789f831d1a4b5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_01.png"
+dest_files=["res://.godot/imported/rock_in_water_01.png-d49d566ddad27e087af789f831d1a4b5.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png
new file mode 100644
index 0000000..38d10a5
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png.import
new file mode 100644
index 0000000..738cc03
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cw0fk2o5sdl5k"
+path="res://.godot/imported/rock_in_water_02.png-532b7ae0b3325cc350e0ef0275c8a8cd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_02.png"
+dest_files=["res://.godot/imported/rock_in_water_02.png-532b7ae0b3325cc350e0ef0275c8a8cd.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png
new file mode 100644
index 0000000..aba065c
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png.import
new file mode 100644
index 0000000..92be37e
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mr47gn76hwg7"
+path="res://.godot/imported/rock_in_water_03.png-af0df681573d1e19c87e766443c69a92.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_03.png"
+dest_files=["res://.godot/imported/rock_in_water_03.png-af0df681573d1e19c87e766443c69a92.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png
new file mode 100644
index 0000000..79ba4ea
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png.import
new file mode 100644
index 0000000..302bb4e
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7sxt5h40wjd8"
+path="res://.godot/imported/rock_in_water_04.png-ba1e6f9c526e81869be3c6c84a5ff2ab.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_04.png"
+dest_files=["res://.godot/imported/rock_in_water_04.png-ba1e6f9c526e81869be3c6c84a5ff2ab.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png
new file mode 100644
index 0000000..3b5e385
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png.import
new file mode 100644
index 0000000..25b76ad
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ck44rro1efn4c"
+path="res://.godot/imported/rock_in_water_05.png-084ad4b304e8e3fa1e175b817b48e09b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_05.png"
+dest_files=["res://.godot/imported/rock_in_water_05.png-084ad4b304e8e3fa1e175b817b48e09b.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png
new file mode 100644
index 0000000..38d10a5
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png.import b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png.import
new file mode 100644
index 0000000..358c69a
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kdvr8cdq1qtp"
+path="res://.godot/imported/rock_in_water_06.png-f9656dc55bf81469416ee3cf6ecc193b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/objects/rock_in_water_06.png"
+dest_files=["res://.godot/imported/rock_in_water_06.png-f9656dc55bf81469416ee3cf6ecc193b.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png b/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png
new file mode 100644
index 0000000..85398a5
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png.import b/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png.import
new file mode 100644
index 0000000..5acfdaa
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d38emyi3bc20w"
+path="res://.godot/imported/dust_particles_01.png-a6f4ecc279a7be030480bfba7330f796.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/particles/dust_particles_01.png"
+dest_files=["res://.godot/imported/dust_particles_01.png-a6f4ecc279a7be030480bfba7330f796.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png
new file mode 100644
index 0000000..db267ed
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png.import
new file mode 100644
index 0000000..14488b5
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://84gy53ru31tj"
+path="res://.godot/imported/decor_16x16.png-ebc18fc236a98a70e81737810ca73cd1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/decor_16x16.png"
+dest_files=["res://.godot/imported/decor_16x16.png-ebc18fc236a98a70e81737810ca73cd1.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png
new file mode 100644
index 0000000..8b91ef7
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png.import
new file mode 100644
index 0000000..e1b606a
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c66txlefa0g1m"
+path="res://.godot/imported/decor_8x8.png-8afa989d76666e299ba1bfa26b472e2e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/decor_8x8.png"
+dest_files=["res://.godot/imported/decor_8x8.png-8afa989d76666e299ba1bfa26b472e2e.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png
new file mode 100644
index 0000000..b1c7856
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png.import
new file mode 100644
index 0000000..2c29d25
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dqkj0y6pww0bc"
+path="res://.godot/imported/fences.png-b9d16689c79f4ae187aad63d1e5a4689.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/fences.png"
+dest_files=["res://.godot/imported/fences.png-b9d16689c79f4ae187aad63d1e5a4689.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png
new file mode 100644
index 0000000..8532f3d
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png.import
new file mode 100644
index 0000000..d5cfef9
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://madjrbxrxb18"
+path="res://.godot/imported/carpet.png-c7c1c7527f7abaa3bc874c3c2fc72934.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/floors/carpet.png"
+dest_files=["res://.godot/imported/carpet.png-c7c1c7527f7abaa3bc874c3c2fc72934.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png
new file mode 100644
index 0000000..2a4bfb8
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png.import
new file mode 100644
index 0000000..3fd907e
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdhd8cgpqqtqd"
+path="res://.godot/imported/flooring.png-43d7f9ac4b16312ab64770bc17144840.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/floors/flooring.png"
+dest_files=["res://.godot/imported/flooring.png-43d7f9ac4b16312ab64770bc17144840.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png
new file mode 100644
index 0000000..f2989f3
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png.import
new file mode 100644
index 0000000..29fcf85
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://t16xm66rao0u"
+path="res://.godot/imported/wooden.png-d8637089a4903542d1c41347324bdeec.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/floors/wooden.png"
+dest_files=["res://.godot/imported/wooden.png-d8637089a4903542d1c41347324bdeec.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png
new file mode 100644
index 0000000..6fbef63
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png.import
new file mode 100644
index 0000000..eda986f
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deevu4iws0aul"
+path="res://.godot/imported/grass.png-b38d151724ddf2b6325fcfc9c526874c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/grass.png"
+dest_files=["res://.godot/imported/grass.png-b38d151724ddf2b6325fcfc9c526874c.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png
new file mode 100644
index 0000000..e91002d
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png.import
new file mode 100644
index 0000000..38526cc
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://br51oca80dq3i"
+path="res://.godot/imported/plains.png-8fc6377fe63b8d212a1ec687887b3df5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/plains.png"
+dest_files=["res://.godot/imported/plains.png-8fc6377fe63b8d212a1ec687887b3df5.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png
new file mode 100644
index 0000000..d6c79f8
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png.import
new file mode 100644
index 0000000..4d731f2
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ds5gmt2oas2j3"
+path="res://.godot/imported/walls.png-2920d9fb288c762e49098bb246c0cd22.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/walls/walls.png"
+dest_files=["res://.godot/imported/walls.png-2920d9fb288c762e49098bb246c0cd22.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png
new file mode 100644
index 0000000..a7db1a9
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png.import
new file mode 100644
index 0000000..104085f
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://eobrojwn66mi"
+path="res://.godot/imported/wooden_door.png-abc43f7cd3766c96e8f1fb22023e6b11.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door.png"
+dest_files=["res://.godot/imported/wooden_door.png-abc43f7cd3766c96e8f1fb22023e6b11.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png
new file mode 100644
index 0000000..ad9fdfc
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png.import
new file mode 100644
index 0000000..f4b3100
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cerem13mn5ucr"
+path="res://.godot/imported/wooden_door_b.png-b154ee80d2fc97a5c2957eb07c6f615b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/walls/wooden_door_b.png"
+dest_files=["res://.godot/imported/wooden_door_b.png-b154ee80d2fc97a5c2957eb07c6f615b.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png
new file mode 100644
index 0000000..1ee035c
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png.import
new file mode 100644
index 0000000..26b4e4d
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pg743cc5uv6g"
+path="res://.godot/imported/water-sheet.png-97e1bd4345ba3193b9b0d736d3624019.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water-sheet.png"
+dest_files=["res://.godot/imported/water-sheet.png-97e1bd4345ba3193b9b0d736d3624019.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png
new file mode 100644
index 0000000..8dcfa5b
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png.import
new file mode 100644
index 0000000..25f3953
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b740fvavkjn44"
+path="res://.godot/imported/water1.png-6365ebf5cadbe6d647db816d6d87b5f2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water1.png"
+dest_files=["res://.godot/imported/water1.png-6365ebf5cadbe6d647db816d6d87b5f2.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png
new file mode 100644
index 0000000..f164d6a
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png.import
new file mode 100644
index 0000000..e23a04b
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djtnlgm0k15fg"
+path="res://.godot/imported/water2.png-36a91ccfcca65406ba32a4f7f919f0e5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water2.png"
+dest_files=["res://.godot/imported/water2.png-36a91ccfcca65406ba32a4f7f919f0e5.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png
new file mode 100644
index 0000000..f7872ae
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png.import
new file mode 100644
index 0000000..8e45467
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkku4jpjmesbb"
+path="res://.godot/imported/water3.png-6fb7cc1c2a7f82ba36d70bae328646c7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water3.png"
+dest_files=["res://.godot/imported/water3.png-6fb7cc1c2a7f82ba36d70bae328646c7.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png
new file mode 100644
index 0000000..4373c39
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png.import
new file mode 100644
index 0000000..18c57ce
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dy5xp1e2kd156"
+path="res://.godot/imported/water4.png-828fd72c261e630dc9cce3b5693f77c8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water4.png"
+dest_files=["res://.godot/imported/water4.png-828fd72c261e630dc9cce3b5693f77c8.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png
new file mode 100644
index 0000000..f164d6a
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png.import
new file mode 100644
index 0000000..9100798
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ccf3j70kvu4rx"
+path="res://.godot/imported/water5.png-e40c6a26d825c2c93b0d25a7f3fef30a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water5.png"
+dest_files=["res://.godot/imported/water5.png-e40c6a26d825c2c93b0d25a7f3fef30a.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png
new file mode 100644
index 0000000..4465224
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png.import
new file mode 100644
index 0000000..e8f5a39
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dk5lb6o5iypcv"
+path="res://.godot/imported/water6.png-ba003e892683f429157a8b6b2a450ec2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water6.png"
+dest_files=["res://.godot/imported/water6.png-ba003e892683f429157a8b6b2a450ec2.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png
new file mode 100644
index 0000000..3cacb04
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png.import
new file mode 100644
index 0000000..a119da6
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vhtvtjpb6lgx"
+path="res://.godot/imported/water_decorations.png-41081b5d89150170a462622f47f0db92.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water_decorations.png"
+dest_files=["res://.godot/imported/water_decorations.png-41081b5d89150170a462622f47f0db92.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
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png
new file mode 100644
index 0000000..f16513c
Binary files /dev/null and b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png differ
diff --git a/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png.import b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png.import
new file mode 100644
index 0000000..8d1f514
--- /dev/null
+++ b/TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b52dx8ej5geti"
+path="res://.godot/imported/water_lillies.png-505badebc331d33e5ec126469a676a7a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://TEST/mystic_woods_free_2.2/sprites/tilesets/water_lillies.png"
+dest_files=["res://.godot/imported/water_lillies.png-505badebc331d33e5ec126469a676a7a.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
diff --git a/addons/beehave/LICENSE b/addons/beehave/LICENSE
new file mode 100644
index 0000000..caabbff
--- /dev/null
+++ b/addons/beehave/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 bitbrain
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/beehave/blackboard.gd b/addons/beehave/blackboard.gd
new file mode 100644
index 0000000..3948212
--- /dev/null
+++ b/addons/beehave/blackboard.gd
@@ -0,0 +1,51 @@
+@icon("icons/blackboard.svg")
+class_name Blackboard extends Node
+
+const DEFAULT = "default"
+
+## The blackboard is an object that can be used to store and access data between
+## multiple nodes of the behavior tree.
+@export var blackboard: Dictionary = {}:
+ set(b):
+ blackboard = b
+ _data[DEFAULT] = blackboard
+
+var _data: Dictionary = {}
+
+
+func _ready():
+ _data[DEFAULT] = blackboard
+
+
+func keys() -> Array[String]:
+ var keys: Array[String]
+ keys.assign(_data.keys().duplicate())
+ return keys
+
+
+func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void:
+ if not _data.has(blackboard_name):
+ _data[blackboard_name] = {}
+
+ _data[blackboard_name][key] = value
+
+
+func get_value(
+ key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT
+) -> Variant:
+ if has_value(key, blackboard_name):
+ return _data[blackboard_name].get(key, default_value)
+ return default_value
+
+
+func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool:
+ return (
+ _data.has(blackboard_name)
+ and _data[blackboard_name].has(key)
+ and _data[blackboard_name][key] != null
+ )
+
+
+func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void:
+ if _data.has(blackboard_name):
+ _data[blackboard_name][key] = null
diff --git a/addons/beehave/debug/debugger.gd b/addons/beehave/debug/debugger.gd
new file mode 100644
index 0000000..73bdc50
--- /dev/null
+++ b/addons/beehave/debug/debugger.gd
@@ -0,0 +1,96 @@
+@tool
+extends EditorDebuggerPlugin
+
+const DebuggerTab := preload("debugger_tab.gd")
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+var debugger_tab := DebuggerTab.new()
+var floating_window: Window
+var session: EditorDebuggerSession
+
+
+func _has_capture(prefix: String) -> bool:
+ return prefix == "beehave"
+
+
+func _capture(message: String, data: Array, session_id: int) -> bool:
+ # in case the behavior tree has invalid setup this might be null
+ if debugger_tab == null:
+ return false
+
+ if message == "beehave:register_tree":
+ debugger_tab.register_tree(data[0])
+ return true
+ if message == "beehave:unregister_tree":
+ debugger_tab.unregister_tree(data[0])
+ return true
+ if message == "beehave:process_tick":
+ debugger_tab.graph.process_tick(data[0], data[1])
+ return true
+ if message == "beehave:process_begin":
+ debugger_tab.graph.process_begin(data[0])
+ return true
+ if message == "beehave:process_end":
+ debugger_tab.graph.process_end(data[0])
+ return true
+ return false
+
+
+func _setup_session(session_id: int) -> void:
+ session = get_session(session_id)
+ session.started.connect(debugger_tab.start)
+ session.stopped.connect(debugger_tab.stop)
+
+ debugger_tab.name = "🐝 Beehave"
+ debugger_tab.make_floating.connect(_on_make_floating)
+ debugger_tab.session = session
+ session.add_session_tab(debugger_tab)
+
+
+func _on_make_floating() -> void:
+ var plugin := BeehaveUtils.get_plugin()
+ if not plugin:
+ return
+ if floating_window:
+ _on_window_close_requested()
+ return
+
+ var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale()
+ var editor_interface: EditorInterface = plugin.get_editor_interface()
+ var editor_main_screen = editor_interface.get_editor_main_screen()
+ debugger_tab.get_parent().remove_child(debugger_tab)
+
+ floating_window = Window.new()
+
+ var panel := Panel.new()
+ panel.add_theme_stylebox_override(
+ "panel",
+ editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles")
+ )
+ panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ floating_window.add_child(panel)
+
+ var margin := MarginContainer.new()
+ margin.add_child(debugger_tab)
+ margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ margin.add_theme_constant_override("margin_right", border_size.x)
+ margin.add_theme_constant_override("margin_left", border_size.x)
+ margin.add_theme_constant_override("margin_top", border_size.y)
+ margin.add_theme_constant_override("margin_bottom", border_size.y)
+ panel.add_child(margin)
+
+ floating_window.title = "🐝 Beehave"
+ floating_window.wrap_controls = true
+ floating_window.min_size = Vector2i(600, 350)
+ floating_window.size = debugger_tab.size
+ floating_window.position = editor_main_screen.global_position
+ floating_window.transient = true
+ floating_window.close_requested.connect(_on_window_close_requested)
+ editor_interface.get_base_control().add_child(floating_window)
+
+
+func _on_window_close_requested() -> void:
+ debugger_tab.get_parent().remove_child(debugger_tab)
+ session.add_session_tab(debugger_tab)
+ floating_window.queue_free()
+ floating_window = null
diff --git a/addons/beehave/debug/debugger_messages.gd b/addons/beehave/debug/debugger_messages.gd
new file mode 100644
index 0000000..6e0a055
--- /dev/null
+++ b/addons/beehave/debug/debugger_messages.gd
@@ -0,0 +1,30 @@
+class_name BeehaveDebuggerMessages
+
+
+static func can_send_message() -> bool:
+ return not Engine.is_editor_hint() and OS.has_feature("editor")
+
+
+static func register_tree(beehave_tree: Dictionary) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:register_tree", [beehave_tree])
+
+
+static func unregister_tree(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:unregister_tree", [instance_id])
+
+
+static func process_tick(instance_id: int, status: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_tick", [instance_id, status])
+
+
+static func process_begin(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_begin", [instance_id])
+
+
+static func process_end(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_end", [instance_id])
diff --git a/addons/beehave/debug/debugger_tab.gd b/addons/beehave/debug/debugger_tab.gd
new file mode 100644
index 0000000..758d8b6
--- /dev/null
+++ b/addons/beehave/debug/debugger_tab.gd
@@ -0,0 +1,125 @@
+@tool
+class_name BeehaveDebuggerTab extends PanelContainer
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+signal make_floating
+
+const OldBeehaveGraphEdit := preload("old_graph_edit.gd")
+const NewBeehaveGraphEdit := preload("new_graph_edit.gd")
+
+const TREE_ICON := preload("../icons/tree.svg")
+
+var graph
+var container: HSplitContainer
+var item_list: ItemList
+var message: Label
+
+var active_trees: Dictionary
+var active_tree_id: int = -1
+var session: EditorDebuggerSession
+
+
+func _ready() -> void:
+ container = HSplitContainer.new()
+ add_child(container)
+
+ item_list = ItemList.new()
+ item_list.custom_minimum_size = Vector2(200, 0)
+ item_list.item_selected.connect(_on_item_selected)
+ container.add_child(item_list)
+ if Engine.get_version_info().minor >= 2:
+ graph = NewBeehaveGraphEdit.new(BeehaveUtils.get_frames())
+ else:
+ graph = OldBeehaveGraphEdit.new(BeehaveUtils.get_frames())
+
+ container.add_child(graph)
+
+ message = Label.new()
+ message.text = "Run Project for debugging"
+ message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ message.set_anchors_preset(Control.PRESET_CENTER)
+ add_child(message)
+
+ var button := Button.new()
+ button.flat = true
+ button.name = "MakeFloatingButton"
+ button.icon = get_theme_icon(&"ExternalLink", &"EditorIcons")
+ button.pressed.connect(func(): make_floating.emit())
+ button.tooltip_text = "Make floating"
+ button.focus_mode = Control.FOCUS_NONE
+ graph.get_menu_container().add_child(button)
+
+ var toggle_button := Button.new()
+ toggle_button.flat = true
+ toggle_button.name = "TogglePanelButton"
+ toggle_button.icon = get_theme_icon(&"Back", &"EditorIcons")
+ toggle_button.pressed.connect(_on_toggle_button_pressed.bind(toggle_button))
+ toggle_button.tooltip_text = "Toggle Panel"
+ toggle_button.focus_mode = Control.FOCUS_NONE
+ graph.get_menu_container().add_child(toggle_button)
+ graph.get_menu_container().move_child(toggle_button, 0)
+
+ stop()
+ visibility_changed.connect(_on_visibility_changed)
+
+
+func start() -> void:
+ container.visible = true
+ message.visible = false
+
+
+func stop() -> void:
+ container.visible = false
+ message.visible = true
+
+ active_trees.clear()
+ item_list.clear()
+ graph.beehave_tree = {}
+
+
+func register_tree(data: Dictionary) -> void:
+ if not active_trees.has(data.id):
+ var idx := item_list.add_item(data.name, TREE_ICON)
+ item_list.set_item_tooltip(idx, data.path)
+ item_list.set_item_metadata(idx, data.id)
+
+ active_trees[data.id] = data
+
+ if active_tree_id == data.id.to_int():
+ graph.beehave_tree = data
+
+
+func unregister_tree(instance_id: int) -> void:
+ var id := str(instance_id)
+ for i in item_list.item_count:
+ if item_list.get_item_metadata(i) == id:
+ item_list.remove_item(i)
+ break
+
+ active_trees.erase(id)
+
+ if graph.beehave_tree.get("id", "") == id:
+ graph.beehave_tree = {}
+
+
+func _on_toggle_button_pressed(toggle_button: Button) -> void:
+ item_list.visible = !item_list.visible
+ toggle_button.icon = get_theme_icon(
+ &"Back" if item_list.visible else &"Forward", &"EditorIcons"
+ )
+
+
+func _on_item_selected(idx: int) -> void:
+ var id: StringName = item_list.get_item_metadata(idx)
+ graph.beehave_tree = active_trees.get(id, {})
+
+ active_tree_id = id.to_int()
+ if session != null:
+ session.send_message("beehave:activate_tree", [active_tree_id])
+
+
+func _on_visibility_changed() -> void:
+ if session != null:
+ session.send_message("beehave:visibility_changed", [visible and is_visible_in_tree()])
diff --git a/addons/beehave/debug/global_debugger.gd b/addons/beehave/debug/global_debugger.gd
new file mode 100644
index 0000000..7350b3e
--- /dev/null
+++ b/addons/beehave/debug/global_debugger.gd
@@ -0,0 +1,38 @@
+extends Node
+
+var _registered_trees: Dictionary
+var _active_tree
+
+
+func _enter_tree() -> void:
+ EngineDebugger.register_message_capture("beehave", _on_debug_message)
+
+
+func _on_debug_message(message: String, data: Array) -> bool:
+ if message == "activate_tree":
+ _set_active_tree(data[0])
+ return true
+ if message == "visibility_changed":
+ if _active_tree && is_instance_valid(_active_tree):
+ _active_tree._can_send_message = data[0]
+ return true
+ return false
+
+
+func _set_active_tree(tree_id: int) -> void:
+ var tree = _registered_trees.get(tree_id, null)
+ if not tree:
+ return
+
+ if _active_tree && is_instance_valid(_active_tree):
+ _active_tree._can_send_message = false
+ _active_tree = tree
+ _active_tree._can_send_message = true
+
+
+func register_tree(tree) -> void:
+ _registered_trees[tree.get_instance_id()] = tree
+
+
+func unregister_tree(tree) -> void:
+ _registered_trees.erase(tree.get_instance_id())
diff --git a/addons/beehave/debug/icons/horizontal_layout.svg b/addons/beehave/debug/icons/horizontal_layout.svg
new file mode 100644
index 0000000..235dc62
--- /dev/null
+++ b/addons/beehave/debug/icons/horizontal_layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/horizontal_layout.svg.import b/addons/beehave/debug/icons/horizontal_layout.svg.import
new file mode 100644
index 0000000..539e518
--- /dev/null
+++ b/addons/beehave/debug/icons/horizontal_layout.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bah77esichnyx"
+path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/horizontal_layout.svg"
+dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_bottom.svg b/addons/beehave/debug/icons/port_bottom.svg
new file mode 100644
index 0000000..3ce70e7
--- /dev/null
+++ b/addons/beehave/debug/icons/port_bottom.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_bottom.svg.import b/addons/beehave/debug/icons/port_bottom.svg.import
new file mode 100644
index 0000000..8845c5b
--- /dev/null
+++ b/addons/beehave/debug/icons/port_bottom.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://da3b236rjbqns"
+path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_bottom.svg"
+dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_left.svg b/addons/beehave/debug/icons/port_left.svg
new file mode 100644
index 0000000..c1f6717
--- /dev/null
+++ b/addons/beehave/debug/icons/port_left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_left.svg.import b/addons/beehave/debug/icons/port_left.svg.import
new file mode 100644
index 0000000..7ea9827
--- /dev/null
+++ b/addons/beehave/debug/icons/port_left.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bnufc8p6spdtn"
+path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_left.svg"
+dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_right.svg b/addons/beehave/debug/icons/port_right.svg
new file mode 100644
index 0000000..2560af5
--- /dev/null
+++ b/addons/beehave/debug/icons/port_right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_right.svg.import b/addons/beehave/debug/icons/port_right.svg.import
new file mode 100644
index 0000000..20931cd
--- /dev/null
+++ b/addons/beehave/debug/icons/port_right.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbmd6vk23ympm"
+path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_right.svg"
+dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_top.svg b/addons/beehave/debug/icons/port_top.svg
new file mode 100644
index 0000000..d3b99e1
--- /dev/null
+++ b/addons/beehave/debug/icons/port_top.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_top.svg.import b/addons/beehave/debug/icons/port_top.svg.import
new file mode 100644
index 0000000..dec7820
--- /dev/null
+++ b/addons/beehave/debug/icons/port_top.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bw8wmxdfom8eh"
+path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_top.svg"
+dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/vertical_layout.svg b/addons/beehave/debug/icons/vertical_layout.svg
new file mode 100644
index 0000000..d59ffb0
--- /dev/null
+++ b/addons/beehave/debug/icons/vertical_layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/vertical_layout.svg.import b/addons/beehave/debug/icons/vertical_layout.svg.import
new file mode 100644
index 0000000..8ddcfca
--- /dev/null
+++ b/addons/beehave/debug/icons/vertical_layout.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bpyxu6i1dx5qh"
+path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/vertical_layout.svg"
+dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/new_frames.gd b/addons/beehave/debug/new_frames.gd
new file mode 100644
index 0000000..4b739fd
--- /dev/null
+++ b/addons/beehave/debug/new_frames.gd
@@ -0,0 +1,69 @@
+@tool
+extends RefCounted
+
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+
+const SUCCESS_COLOR := Color("#07783a")
+const NORMAL_COLOR := Color("#15181e")
+const FAILURE_COLOR := Color("#82010b")
+const RUNNING_COLOR := Color("#c29c06")
+
+var panel_normal: StyleBoxFlat
+var panel_success: StyleBoxFlat
+var panel_failure: StyleBoxFlat
+var panel_running: StyleBoxFlat
+
+var titlebar_normal: StyleBoxFlat
+var titlebar_success: StyleBoxFlat
+var titlebar_failure: StyleBoxFlat
+var titlebar_running: StyleBoxFlat
+
+
+func _init() -> void:
+ var plugin := BeehaveUtils.get_plugin()
+ if not plugin:
+ return
+
+
+ titlebar_normal = (
+ plugin
+ .get_editor_interface()
+ .get_base_control()
+ .get_theme_stylebox(&"titlebar", &"GraphNode")\
+ .duplicate()
+ )
+ titlebar_success = titlebar_normal.duplicate()
+ titlebar_failure = titlebar_normal.duplicate()
+ titlebar_running = titlebar_normal.duplicate()
+
+ titlebar_success.bg_color = SUCCESS_COLOR
+ titlebar_failure.bg_color = FAILURE_COLOR
+ titlebar_running.bg_color = RUNNING_COLOR
+
+ titlebar_success.border_color = SUCCESS_COLOR
+ titlebar_failure.border_color = FAILURE_COLOR
+ titlebar_running.border_color = RUNNING_COLOR
+
+
+ panel_normal = (
+ plugin
+ .get_editor_interface()
+ .get_base_control()
+ .get_theme_stylebox(&"panel", &"GraphNode")
+ .duplicate()
+ )
+ panel_success = (
+ plugin
+ .get_editor_interface()
+ .get_base_control()
+ .get_theme_stylebox(&"panel_selected", &"GraphNode")
+ .duplicate()
+ )
+ panel_failure = panel_success.duplicate()
+ panel_running = panel_success.duplicate()
+
+ panel_success.border_color = SUCCESS_COLOR
+ panel_failure.border_color = FAILURE_COLOR
+ panel_running.border_color = RUNNING_COLOR
diff --git a/addons/beehave/debug/new_graph_edit.gd b/addons/beehave/debug/new_graph_edit.gd
new file mode 100644
index 0000000..71161ab
--- /dev/null
+++ b/addons/beehave/debug/new_graph_edit.gd
@@ -0,0 +1,296 @@
+@tool
+extends GraphEdit
+
+const BeehaveGraphNode := preload("new_graph_node.gd")
+
+const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
+const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
+
+const PROGRESS_SHIFT: int = 50
+const INACTIVE_COLOR: Color = Color("#898989")
+const ACTIVE_COLOR: Color = Color("#c29c06")
+const SUCCESS_COLOR: Color = Color("#07783a")
+
+
+var updating_graph: bool = false
+var arraging_nodes: bool = false
+var beehave_tree: Dictionary:
+ set(value):
+ if beehave_tree == value:
+ return
+ beehave_tree = value
+ active_nodes.clear()
+ _update_graph()
+
+var horizontal_layout: bool = false:
+ set(value):
+ if updating_graph or arraging_nodes:
+ return
+ if horizontal_layout == value:
+ return
+ horizontal_layout = value
+ _update_layout_button()
+ _update_graph()
+
+
+var frames:RefCounted
+var active_nodes: Array[String]
+var progress: int = 0
+var layout_button: Button
+
+
+func _init(frames:RefCounted) -> void:
+ self.frames = frames
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(100, 300)
+ set("show_arrange_button", true)
+ minimap_enabled = false
+ layout_button = Button.new()
+ layout_button.flat = true
+ layout_button.focus_mode = Control.FOCUS_NONE
+ layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
+ get_menu_container().add_child(layout_button)
+ _update_layout_button()
+
+
+func _update_graph() -> void:
+ if updating_graph:
+ return
+
+ updating_graph = true
+
+ clear_connections()
+
+ for child in _get_child_nodes():
+ remove_child(child)
+ child.queue_free()
+
+ if not beehave_tree.is_empty():
+ _add_nodes(beehave_tree)
+ _connect_nodes(beehave_tree)
+ _arrange_nodes.call_deferred(beehave_tree)
+
+ updating_graph = false
+
+
+func _add_nodes(node: Dictionary) -> void:
+ if node.is_empty():
+ return
+ var gnode := BeehaveGraphNode.new(frames, horizontal_layout)
+ add_child(gnode)
+ gnode.title_text = node.name
+ gnode.name = node.id
+ gnode.icon = _get_icon(node.type.back())
+
+ if node.type.has(&"BeehaveTree"):
+ gnode.set_slots(false, true)
+ elif node.type.has(&"Leaf"):
+ gnode.set_slots(true, false)
+ elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
+ gnode.set_slots(true, true)
+
+ for child in node.get("children", []):
+ _add_nodes(child)
+
+
+func _connect_nodes(node: Dictionary) -> void:
+ for child in node.get("children", []):
+ connect_node(node.id, 0, child.id, 0)
+ _connect_nodes(child)
+
+
+func _arrange_nodes(node: Dictionary) -> void:
+ if arraging_nodes:
+ return
+
+ arraging_nodes = true
+
+ var tree_node := _create_tree_nodes(node)
+ tree_node.update_positions(horizontal_layout)
+ _place_nodes(tree_node)
+
+ arraging_nodes = false
+
+
+func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
+ var tree_node := TreeNode.new(get_node(node.id), root)
+ for child in node.get("children", []):
+ var child_node := _create_tree_nodes(child, tree_node)
+ tree_node.children.push_back(child_node)
+ return tree_node
+
+
+func _place_nodes(node: TreeNode) -> void:
+ node.item.position_offset = Vector2(node.x, node.y)
+ for child in node.children:
+ _place_nodes(child)
+
+
+func _get_icon(type: StringName) -> Texture2D:
+ var classes := ProjectSettings.get_global_class_list()
+ for c in classes:
+ if c["class"] == type:
+ var icon_path := c.get("icon", String())
+ if not icon_path.is_empty():
+ return load(icon_path)
+ return null
+
+
+func get_menu_container() -> Control:
+ return call("get_menu_hbox")
+
+
+func get_status(status: int) -> String:
+ if status == 0:
+ return "SUCCESS"
+ elif status == 1:
+ return "FAILURE"
+ return "RUNNING"
+
+
+func process_begin(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in _get_child_nodes():
+ child.set_meta("status", -1)
+
+
+func process_tick(instance_id: int, status: int) -> void:
+ var node := get_node_or_null(str(instance_id))
+ if node:
+ node.text = "Status: %s" % get_status(status)
+ node.set_status(status)
+ node.set_meta("status", status)
+ if status == 0 or status == 2:
+ if not active_nodes.has(node.name):
+ active_nodes.push_back(node.name)
+
+
+func process_end(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in _get_child_nodes():
+ var status := child.get_meta("status", -1)
+ match status:
+ 0:
+ active_nodes.erase(child.name)
+ child.set_color(SUCCESS_COLOR)
+ 1:
+ active_nodes.erase(child.name)
+ child.set_color(INACTIVE_COLOR)
+ 2:
+ child.set_color(ACTIVE_COLOR)
+ _:
+ child.text = " "
+ child.set_status(status)
+ child.set_color(INACTIVE_COLOR)
+
+
+func _is_same_tree(instance_id: int) -> bool:
+ return str(instance_id) == beehave_tree.get("id", "")
+
+
+func _get_child_nodes() -> Array[Node]:
+ return get_children().filter(func(child): return child is BeehaveGraphNode)
+
+
+func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
+ for child in _get_child_nodes():
+ for port in child.get_input_port_count():
+ if not (child.position_offset + child.get_input_port_position(port)).is_equal_approx(to_position):
+ continue
+ to_position = child.position_offset + child.get_custom_input_port_position(horizontal_layout)
+ for port in child.get_output_port_count():
+ if not (child.position_offset + child.get_output_port_position(port)).is_equal_approx(from_position):
+ continue
+ from_position = child.position_offset + child.get_custom_output_port_position(horizontal_layout)
+ return _get_elbow_connection_line(from_position, to_position)
+
+
+func _get_elbow_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
+ var points: PackedVector2Array
+
+ points.push_back(from_position)
+
+ var mid_position := ((to_position + from_position) / 2).round()
+ if horizontal_layout:
+ points.push_back(Vector2(mid_position.x, from_position.y))
+ points.push_back(Vector2(mid_position.x, to_position.y))
+ else:
+ points.push_back(Vector2(from_position.x, mid_position.y))
+ points.push_back(Vector2(to_position.x, mid_position.y))
+
+ points.push_back(to_position)
+
+ return points
+
+
+func _process(delta: float) -> void:
+ if not active_nodes.is_empty():
+ progress += 10 if delta >= 0.05 else 1
+ if progress >= 1000:
+ progress = 0
+ queue_redraw()
+
+
+func _draw() -> void:
+ if active_nodes.is_empty():
+ return
+
+ var circle_size: float = max(3, 6 * zoom)
+ var progress_shift: float = PROGRESS_SHIFT * zoom
+
+ var connections := get_connection_list()
+ for c in connections:
+ var from_node: StringName
+ var to_node: StringName
+
+ from_node = c.from_node
+ to_node = c.to_node
+
+ if not from_node in active_nodes or not c.to_node in active_nodes:
+ continue
+
+ var from := get_node(String(from_node))
+ var to := get_node(String(to_node))
+
+ if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
+ return
+
+ var output_port_position: Vector2
+ var input_port_position: Vector2
+
+ var scale_factor: float = from.get_rect().size.x / from.size.x
+
+ var line := _get_elbow_connection_line(
+ from.position + from.get_custom_output_port_position(horizontal_layout) * scale_factor,
+ to.position + to.get_custom_input_port_position(horizontal_layout) * scale_factor
+ )
+
+ var curve = Curve2D.new()
+ for l in line:
+ curve.add_point(l)
+
+ var max_steps := int(curve.get_baked_length())
+ var current_shift := progress % max_steps
+ var p := curve.sample_baked(current_shift)
+ draw_circle(p, circle_size, ACTIVE_COLOR)
+
+ var shift := current_shift - progress_shift
+ while shift >= 0:
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift -= progress_shift
+
+ shift = current_shift + progress_shift
+ while shift <= curve.get_baked_length():
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift += progress_shift
+
+
+func _update_layout_button() -> void:
+ layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
+ layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
diff --git a/addons/beehave/debug/new_graph_node.gd b/addons/beehave/debug/new_graph_node.gd
new file mode 100644
index 0000000..70f06dc
--- /dev/null
+++ b/addons/beehave/debug/new_graph_node.gd
@@ -0,0 +1,155 @@
+@tool
+extends GraphNode
+
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+const PORT_TOP_ICON := preload("icons/port_top.svg")
+const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
+const PORT_LEFT_ICON := preload("icons/port_left.svg")
+const PORT_RIGHT_ICON := preload("icons/port_right.svg")
+
+
+@export var title_text: String:
+ set(value):
+ title_text = value
+ if title_label:
+ title_label.text = value
+
+@export var text: String:
+ set(value):
+ text = value
+ if label:
+ label.text = " " if text.is_empty() else text
+
+@export var icon: Texture2D:
+ set(value):
+ icon = value
+ if icon_rect:
+ icon_rect.texture = value
+
+var layout_size: float:
+ get:
+ return size.y if horizontal else size.x
+
+
+var icon_rect: TextureRect
+var title_label: Label
+var label: Label
+var titlebar_hbox: HBoxContainer
+
+var frames: RefCounted
+var horizontal: bool = false
+
+
+func _init(frames:RefCounted, horizontal: bool = false) -> void:
+ self.frames = frames
+ self.horizontal = horizontal
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
+ draggable = false
+
+ add_theme_color_override("close_color", Color.TRANSPARENT)
+ add_theme_icon_override("close", ImageTexture.new())
+
+ # For top port
+ var top_port: Control = Control.new()
+ add_child(top_port)
+
+ icon_rect = TextureRect.new()
+ icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+
+ titlebar_hbox = get_titlebar_hbox()
+ titlebar_hbox.get_child(0).queue_free()
+ titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN
+ titlebar_hbox.add_child(icon_rect)
+
+ title_label = Label.new()
+ title_label.add_theme_color_override("font_color", Color.WHITE)
+ var title_font: Font = get_theme_font("title_font").duplicate()
+ if title_font is FontVariation:
+ title_font.variation_embolden = 1
+ elif title_font is FontFile:
+ title_font.font_weight = 700
+ title_label.add_theme_font_override("font", title_font)
+ title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ title_label.text = title_text
+ titlebar_hbox.add_child(title_label)
+
+ label = Label.new()
+ label.text = " " if text.is_empty() else text
+ add_child(label)
+
+ # For bottom port
+ add_child(Control.new())
+
+ minimum_size_changed.connect(_on_size_changed)
+ _on_size_changed.call_deferred()
+
+
+func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void:
+ if horizontal:
+ if is_slot_enabled_left(1):
+ draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-4, -5), color)
+ if is_slot_enabled_right(1):
+ draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-5, -4.5), color)
+ else:
+ if slot_index == 0 and is_slot_enabled_left(0):
+ draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-4.5, -7), color)
+ elif slot_index == 1:
+ draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-4.5, -5), color)
+
+
+func get_custom_input_port_position(horizontal: bool) -> Vector2:
+ if horizontal:
+ return Vector2(0, size.y / 2)
+ else:
+ return Vector2(size.x/2, 0)
+
+
+func get_custom_output_port_position(horizontal: bool) -> Vector2:
+ if horizontal:
+ return Vector2(size.x, size.y / 2)
+ else:
+ return Vector2(size.x / 2, size.y)
+
+
+func set_status(status: int) -> void:
+ match status:
+ 0: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success)
+ 1: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure)
+ 2: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running)
+ _: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal)
+
+
+func set_slots(left_enabled: bool, right_enabled: bool) -> void:
+ if horizontal:
+ set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON)
+ else:
+ set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null)
+ set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, Color.WHITE, null, PORT_BOTTOM_ICON)
+
+
+func set_color(color: Color) -> void:
+ set_input_color(color)
+ set_output_color(color)
+
+
+func set_input_color(color: Color) -> void:
+ set_slot_color_left(1 if horizontal else 0, color)
+
+
+func set_output_color(color: Color) -> void:
+ set_slot_color_right(1 if horizontal else 2, color)
+
+
+func _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void:
+ add_theme_stylebox_override("panel", panel_stylebox)
+ add_theme_stylebox_override("titlebar", titlebar_stylebox)
+
+
+func _on_size_changed():
+ add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x))
diff --git a/addons/beehave/debug/old_frames.gd b/addons/beehave/debug/old_frames.gd
new file mode 100644
index 0000000..a9f6aa8
--- /dev/null
+++ b/addons/beehave/debug/old_frames.gd
@@ -0,0 +1,47 @@
+@tool
+extends RefCounted
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+const SUCCESS_COLOR := Color("#009944c8")
+const NORMAL_COLOR := Color("#15181e")
+const FAILURE_COLOR := Color("#cf000f80")
+const RUNNING_COLOR := Color("#ffcc00c8")
+
+var empty: StyleBoxEmpty
+var normal: StyleBoxFlat
+var success: StyleBoxFlat
+var failure: StyleBoxFlat
+var running: StyleBoxFlat
+
+
+func _init() -> void:
+ var plugin := BeehaveUtils.get_plugin()
+ if not plugin:
+ return
+
+ var editor_scale := BeehaveUtils.get_editor_scale()
+
+ empty = StyleBoxEmpty.new()
+
+ normal = (
+ plugin
+ . get_editor_interface()
+ . get_base_control()
+ . get_theme_stylebox(&"frame", &"GraphNode")
+ . duplicate()
+ )
+
+ success = (
+ plugin
+ . get_editor_interface()
+ . get_base_control()
+ . get_theme_stylebox(&"selected_frame", &"GraphNode")
+ . duplicate()
+ )
+ failure = success.duplicate()
+ running = success.duplicate()
+
+ success.border_color = SUCCESS_COLOR
+ failure.border_color = FAILURE_COLOR
+ running.border_color = RUNNING_COLOR
diff --git a/addons/beehave/debug/old_graph_edit.gd b/addons/beehave/debug/old_graph_edit.gd
new file mode 100644
index 0000000..bae64c8
--- /dev/null
+++ b/addons/beehave/debug/old_graph_edit.gd
@@ -0,0 +1,286 @@
+@tool
+extends GraphEdit
+
+const BeehaveGraphNode := preload("old_graph_node.gd")
+
+const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
+const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
+
+const PROGRESS_SHIFT: int = 50
+const INACTIVE_COLOR: Color = Color("#898989aa")
+const ACTIVE_COLOR: Color = Color("#ffcc00c8")
+const SUCCESS_COLOR: Color = Color("#009944c8")
+
+var updating_graph: bool = false
+var arraging_nodes: bool = false
+var beehave_tree: Dictionary:
+ set(value):
+ if beehave_tree == value:
+ return
+ beehave_tree = value
+ active_nodes.clear()
+ _update_graph()
+
+var horizontal_layout: bool = false:
+ set(value):
+ if updating_graph or arraging_nodes:
+ return
+ if horizontal_layout == value:
+ return
+ horizontal_layout = value
+ _update_layout_button()
+ _update_graph()
+
+var frames: RefCounted
+var active_nodes: Array[String]
+var progress: int = 0
+var layout_button: Button
+
+
+func _init(frames: RefCounted) -> void:
+ self.frames = frames
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(100, 300)
+ set("arrange_nodes_button_hidden", true)
+ minimap_enabled = false
+ layout_button = Button.new()
+ layout_button.flat = true
+ layout_button.focus_mode = Control.FOCUS_NONE
+ layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
+ get_menu_container().add_child(layout_button)
+ _update_layout_button()
+
+
+func _update_graph() -> void:
+ if updating_graph:
+ return
+
+ updating_graph = true
+
+ clear_connections()
+
+ for child in _get_child_nodes():
+ remove_child(child)
+ child.queue_free()
+
+ if not beehave_tree.is_empty():
+ _add_nodes(beehave_tree)
+ _connect_nodes(beehave_tree)
+ _arrange_nodes.call_deferred(beehave_tree)
+
+ updating_graph = false
+
+
+func _add_nodes(node: Dictionary) -> void:
+ if node.is_empty():
+ return
+ var gnode := BeehaveGraphNode.new(frames, horizontal_layout)
+ add_child(gnode)
+ gnode.title_text = node.name
+ gnode.name = node.id
+ gnode.icon = _get_icon(node.type.back())
+
+ if node.type.has(&"BeehaveTree"):
+ gnode.set_slots(false, true)
+ elif node.type.has(&"Leaf"):
+ gnode.set_slots(true, false)
+ elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
+ gnode.set_slots(true, true)
+
+ for child in node.get("children", []):
+ _add_nodes(child)
+
+
+func _connect_nodes(node: Dictionary) -> void:
+ for child in node.get("children", []):
+ connect_node(node.id, 0, child.id, 0)
+ _connect_nodes(child)
+
+
+func _arrange_nodes(node: Dictionary) -> void:
+ if arraging_nodes:
+ return
+
+ arraging_nodes = true
+
+ var tree_node := _create_tree_nodes(node)
+ tree_node.update_positions(horizontal_layout)
+ _place_nodes(tree_node)
+
+ arraging_nodes = false
+
+
+func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
+ var tree_node := TreeNode.new(get_node(node.id), root)
+ for child in node.get("children", []):
+ var child_node := _create_tree_nodes(child, tree_node)
+ tree_node.children.push_back(child_node)
+ return tree_node
+
+
+func _place_nodes(node: TreeNode) -> void:
+ node.item.position_offset = Vector2(node.x, node.y)
+ for child in node.children:
+ _place_nodes(child)
+
+
+func _get_icon(type: StringName) -> Texture2D:
+ var classes := ProjectSettings.get_global_class_list()
+ for c in classes:
+ if c["class"] == type:
+ var icon_path := c.get("icon", String())
+ if not icon_path.is_empty():
+ return load(icon_path)
+ return null
+
+
+func get_menu_container() -> Control:
+ return call("get_zoom_hbox")
+
+
+func get_status(status: int) -> String:
+ if status == 0:
+ return "SUCCESS"
+ elif status == 1:
+ return "FAILURE"
+ return "RUNNING"
+
+
+func process_begin(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in _get_child_nodes():
+ child.set_meta("status", -1)
+
+
+func process_tick(instance_id: int, status: int) -> void:
+ var node := get_node_or_null(str(instance_id))
+ if node:
+ node.text = "Status: %s" % get_status(status)
+ node.set_status(status)
+ node.set_meta("status", status)
+ if status == 0 or status == 2:
+ if not active_nodes.has(node.name):
+ active_nodes.push_back(node.name)
+
+
+func process_end(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in _get_child_nodes():
+ var status := child.get_meta("status", -1)
+ match status:
+ 0:
+ active_nodes.erase(child.name)
+ child.set_color(SUCCESS_COLOR)
+ 1:
+ active_nodes.erase(child.name)
+ child.set_color(INACTIVE_COLOR)
+ 2:
+ child.set_color(ACTIVE_COLOR)
+ _:
+ child.text = " "
+ child.set_status(status)
+ child.set_color(INACTIVE_COLOR)
+
+
+func _is_same_tree(instance_id: int) -> bool:
+ return str(instance_id) == beehave_tree.get("id", "")
+
+
+func _get_child_nodes() -> Array[Node]:
+ return get_children().filter(func(child): return child is BeehaveGraphNode)
+
+
+func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
+ var points: PackedVector2Array
+
+ from_position = from_position.round()
+ to_position = to_position.round()
+
+ points.push_back(from_position)
+
+ var mid_position := ((to_position + from_position) / 2).round()
+ if horizontal_layout:
+ points.push_back(Vector2(mid_position.x, from_position.y))
+ points.push_back(Vector2(mid_position.x, to_position.y))
+ else:
+ points.push_back(Vector2(from_position.x, mid_position.y))
+ points.push_back(Vector2(to_position.x, mid_position.y))
+
+ points.push_back(to_position)
+
+ return points
+
+
+func _process(delta: float) -> void:
+ if not active_nodes.is_empty():
+ progress += 10 if delta >= 0.05 else 1
+ if progress >= 1000:
+ progress = 0
+ queue_redraw()
+
+
+func _draw() -> void:
+ if active_nodes.is_empty():
+ return
+
+ var circle_size: float = max(3, 6 * zoom)
+ var progress_shift: float = PROGRESS_SHIFT * zoom
+
+ var connections := get_connection_list()
+ for c in connections:
+ var from_node: StringName
+ var to_node: StringName
+
+ from_node = c.from
+ to_node = c.to
+
+ if not from_node in active_nodes or not c.to_node in active_nodes:
+ continue
+
+ var from := get_node(String(from_node))
+ var to := get_node(String(to_node))
+
+ if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
+ return
+
+ var output_port_position: Vector2
+ var input_port_position: Vector2
+
+ output_port_position = (
+ from.position + from.call("get_connection_output_position", c.from_port)
+ )
+ input_port_position = to.position + to.call("get_connection_input_position", c.to_port)
+
+ var line := _get_connection_line(output_port_position, input_port_position)
+
+ var curve = Curve2D.new()
+ for l in line:
+ curve.add_point(l)
+
+ var max_steps := int(curve.get_baked_length())
+ var current_shift := progress % max_steps
+ var p := curve.sample_baked(current_shift)
+ draw_circle(p, circle_size, ACTIVE_COLOR)
+
+ var shift := current_shift - progress_shift
+ while shift >= 0:
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift -= progress_shift
+
+ shift = current_shift + progress_shift
+ while shift <= curve.get_baked_length():
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift += progress_shift
+
+
+func _update_layout_button() -> void:
+ layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
+ layout_button.tooltip_text = (
+ "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
+ )
diff --git a/addons/beehave/debug/old_graph_node.gd b/addons/beehave/debug/old_graph_node.gd
new file mode 100644
index 0000000..a0a978b
--- /dev/null
+++ b/addons/beehave/debug/old_graph_node.gd
@@ -0,0 +1,166 @@
+@tool
+extends GraphNode
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+const DEFAULT_COLOR := Color("#dad4cb")
+
+const PORT_TOP_ICON := preload("icons/port_top.svg")
+const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
+const PORT_LEFT_ICON := preload("icons/port_left.svg")
+const PORT_RIGHT_ICON := preload("icons/port_right.svg")
+
+@export var title_text: String:
+ set(value):
+ title_text = value
+ if title_label:
+ title_label.text = value
+
+@export var text: String:
+ set(value):
+ text = value
+ if label:
+ label.text = " " if text.is_empty() else text
+
+@export var icon: Texture2D:
+ set(value):
+ icon = value
+ if icon_rect:
+ icon_rect.texture = value
+
+var layout_size: float:
+ get:
+ return size.y if horizontal else size.x
+
+var panel: PanelContainer
+var icon_rect: TextureRect
+var title_label: Label
+var container: VBoxContainer
+var label: Label
+
+var frames: RefCounted
+var horizontal: bool = false
+
+
+func _init(frames: RefCounted, horizontal: bool = false) -> void:
+ self.frames = frames
+ self.horizontal = horizontal
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
+ draggable = false
+
+ add_theme_stylebox_override("frame", frames.empty if frames != null else null)
+ add_theme_stylebox_override("selected_frame", frames.empty if frames != null else null)
+ add_theme_color_override("close_color", Color.TRANSPARENT)
+ add_theme_icon_override("close", ImageTexture.new())
+
+ # For top port
+ add_child(Control.new())
+
+ panel = PanelContainer.new()
+ panel.mouse_filter = Control.MOUSE_FILTER_PASS
+ panel.add_theme_stylebox_override("panel", frames.normal if frames != null else null)
+ add_child(panel)
+
+ var vbox_container := VBoxContainer.new()
+ panel.add_child(vbox_container)
+
+ var title_size := 24 * BeehaveUtils.get_editor_scale()
+ var margin_container := MarginContainer.new()
+ margin_container.add_theme_constant_override(
+ "margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale()
+ )
+ margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
+ vbox_container.add_child(margin_container)
+
+ var title_container := HBoxContainer.new()
+ title_container.add_child(Control.new())
+ title_container.mouse_filter = Control.MOUSE_FILTER_PASS
+ title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ margin_container.add_child(title_container)
+
+ icon_rect = TextureRect.new()
+ icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ title_container.add_child(icon_rect)
+
+ title_label = Label.new()
+ title_label.add_theme_color_override("font_color", DEFAULT_COLOR)
+ title_label.add_theme_font_override("font", get_theme_font("title_font"))
+ title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ title_label.text = title_text
+ title_container.add_child(title_label)
+
+ title_container.add_child(Control.new())
+
+ container = VBoxContainer.new()
+ container.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ panel.add_child(container)
+
+ label = Label.new()
+ label.text = " " if text.is_empty() else text
+ container.add_child(label)
+
+ # For bottom port
+ add_child(Control.new())
+
+ minimum_size_changed.connect(_on_size_changed)
+ _on_size_changed.call_deferred()
+
+
+func set_status(status: int) -> void:
+ panel.add_theme_stylebox_override("panel", _get_stylebox(status))
+
+
+func _get_stylebox(status: int) -> StyleBox:
+ match status:
+ 0:
+ return frames.success
+ 1:
+ return frames.failure
+ 2:
+ return frames.running
+ _:
+ return frames.normal
+
+
+func set_slots(left_enabled: bool, right_enabled: bool) -> void:
+ if horizontal:
+ set_slot(
+ 1,
+ left_enabled,
+ 0,
+ Color.WHITE,
+ right_enabled,
+ 0,
+ Color.WHITE,
+ PORT_LEFT_ICON,
+ PORT_RIGHT_ICON
+ )
+ else:
+ set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null)
+ set_slot(
+ 2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON
+ )
+
+
+func set_color(color: Color) -> void:
+ set_input_color(color)
+ set_output_color(color)
+
+
+func set_input_color(color: Color) -> void:
+ set_slot_color_left(1 if horizontal else 0, color)
+
+
+func set_output_color(color: Color) -> void:
+ set_slot_color_right(1 if horizontal else 2, color)
+
+
+func _on_size_changed():
+ add_theme_constant_override(
+ "port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0)
+ )
diff --git a/addons/beehave/debug/tree_node.gd b/addons/beehave/debug/tree_node.gd
new file mode 100644
index 0000000..1377970
--- /dev/null
+++ b/addons/beehave/debug/tree_node.gd
@@ -0,0 +1,275 @@
+class_name TreeNode
+extends RefCounted
+
+# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/
+
+const SIBLING_DISTANCE: float = 20.0
+const LEVEL_DISTANCE: float = 40.0
+
+const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
+
+var x: float
+var y: float
+var mod: float
+var parent: TreeNode
+var children: Array[TreeNode]
+
+var item: GraphNode
+
+
+func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void:
+ parent = p_parent
+ item = p_item
+
+
+func is_leaf() -> bool:
+ return children.is_empty()
+
+
+func is_most_left() -> bool:
+ if not parent:
+ return true
+ return parent.children.front() == self
+
+
+func is_most_right() -> bool:
+ if not parent:
+ return true
+ return parent.children.back() == self
+
+
+func get_previous_sibling() -> TreeNode:
+ if not parent or is_most_left():
+ return null
+ return parent.children[parent.children.find(self) - 1]
+
+
+func get_next_sibling() -> TreeNode:
+ if not parent or is_most_right():
+ return null
+ return parent.children[parent.children.find(self) + 1]
+
+
+func get_most_left_sibling() -> TreeNode:
+ if not parent:
+ return null
+
+ if is_most_left():
+ return self
+
+ return parent.children.front()
+
+
+func get_most_left_child() -> TreeNode:
+ if children.is_empty():
+ return null
+ return children.front()
+
+
+func get_most_right_child() -> TreeNode:
+ if children.is_empty():
+ return null
+ return children.back()
+
+
+func update_positions(horizontally: bool = false) -> void:
+ _initialize_nodes(self, 0)
+ _calculate_initial_x(self)
+
+ _check_all_children_on_screen(self)
+ _calculate_final_positions(self, 0)
+
+ if horizontally:
+ _swap_x_y(self)
+ _calculate_x(self, 0)
+ else:
+ _calculate_y(self, 0)
+
+
+func _initialize_nodes(node: TreeNode, depth: int) -> void:
+ node.x = -1
+ node.y = depth
+ node.mod = 0
+
+ for child in node.children:
+ _initialize_nodes(child, depth + 1)
+
+
+func _calculate_initial_x(node: TreeNode) -> void:
+ for child in node.children:
+ _calculate_initial_x(child)
+ if node.is_leaf():
+ if not node.is_most_left():
+ node.x = (
+ node.get_previous_sibling().x
+ + node.get_previous_sibling().item.layout_size
+ + SIBLING_DISTANCE
+ )
+ else:
+ node.x = 0
+ else:
+ var mid: float
+ if node.children.size() == 1:
+ var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2
+ mid = node.children.front().x + offset
+ else:
+ var left_child := node.get_most_left_child()
+ var right_child := node.get_most_right_child()
+ mid = (
+ (
+ left_child.x
+ + right_child.x
+ + right_child.item.layout_size
+ - node.item.layout_size
+ )
+ / 2
+ )
+
+ if node.is_most_left():
+ node.x = mid
+ else:
+ node.x = (
+ node.get_previous_sibling().x
+ + node.get_previous_sibling().item.layout_size
+ + SIBLING_DISTANCE
+ )
+ node.mod = node.x - mid
+
+ if not node.is_leaf() and not node.is_most_left():
+ _check_for_conflicts(node)
+
+
+func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void:
+ node.x += mod_sum
+ mod_sum += node.mod
+
+ for child in node.children:
+ _calculate_final_positions(child, mod_sum)
+
+
+func _check_all_children_on_screen(node: TreeNode) -> void:
+ var node_contour: Dictionary = {}
+ _get_left_contour(node, 0, node_contour)
+
+ var shift_amount: float = 0
+ for y in node_contour.keys():
+ if node_contour[y] + shift_amount < 0:
+ shift_amount = (node_contour[y] * -1)
+
+ if shift_amount > 0:
+ node.x += shift_amount
+ node.mod += shift_amount
+
+
+func _check_for_conflicts(node: TreeNode) -> void:
+ var min_distance := SIBLING_DISTANCE
+ var shift_value: float = 0
+ var shift_sibling: TreeNode = null
+
+ var node_contour: Dictionary = {} # { int, float }
+ _get_left_contour(node, 0, node_contour)
+
+ var sibling := node.get_most_left_sibling()
+ while sibling != null and sibling != node:
+ var sibling_contour: Dictionary = {}
+ _get_right_contour(sibling, 0, sibling_contour)
+
+ for level in range(
+ node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1
+ ):
+ var distance: float = node_contour[level] - sibling_contour[level]
+ if distance + shift_value < min_distance:
+ shift_value = min_distance - distance
+ shift_sibling = sibling
+
+ sibling = sibling.get_next_sibling()
+
+ if shift_value > 0:
+ node.x += shift_value
+ node.mod += shift_value
+ _center_nodes_between(shift_sibling, node)
+
+
+func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void:
+ var left_index := left_node.parent.children.find(left_node)
+ var right_index := left_node.parent.children.find(right_node)
+
+ var num_nodes_between: int = (right_index - left_index) - 1
+ if num_nodes_between > 0:
+ # The extra distance that needs to be split into num_nodes_between + 1
+ # in order to find the new node spacing so that nodes are equally spaced
+ var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size
+ # Subtract sizes on nodes in between
+ for i in range(left_index + 1, right_index):
+ distance_to_allocate -= left_node.parent.children[i].item.layout_size
+ # Divide space equally
+ var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1)
+
+ var prev_node := left_node
+ var middle_node := left_node.get_next_sibling()
+ while middle_node != right_node:
+ var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes
+ var offset := desire_x - middle_node.x
+ middle_node.x += offset
+ middle_node.mod += offset
+ prev_node = middle_node
+ middle_node = middle_node.get_next_sibling()
+
+
+func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
+ var node_left: float = node.x + mod_sum
+ var depth := int(node.y)
+ if not values.has(depth):
+ values[depth] = node_left
+ else:
+ values[depth] = min(values[depth], node_left)
+
+ mod_sum += node.mod
+ for child in node.children:
+ _get_left_contour(child, mod_sum, values)
+
+
+func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
+ var node_right: float = node.x + mod_sum + node.item.layout_size
+ var depth := int(node.y)
+ if not values.has(depth):
+ values[depth] = node_right
+ else:
+ values[depth] = max(values[depth], node_right)
+
+ mod_sum += node.mod
+ for child in node.children:
+ _get_right_contour(child, mod_sum, values)
+
+
+func _swap_x_y(node: TreeNode) -> void:
+ for child in node.children:
+ _swap_x_y(child)
+
+ var temp := node.x
+ node.x = node.y
+ node.y = temp
+
+
+func _calculate_x(node: TreeNode, offset: int) -> void:
+ node.x = offset
+ var sibling := node.get_most_left_sibling()
+ var max_size: int = node.item.size.x
+ while sibling != null:
+ max_size = max(sibling.item.size.x, max_size)
+ sibling = sibling.get_next_sibling()
+
+ for child in node.children:
+ _calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
+
+
+func _calculate_y(node: TreeNode, offset: int) -> void:
+ node.y = offset
+ var sibling := node.get_most_left_sibling()
+ var max_size: int = node.item.size.y
+ while sibling != null:
+ max_size = max(sibling.item.size.y, max_size)
+ sibling = sibling.get_next_sibling()
+
+ for child in node.children:
+ _calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
diff --git a/addons/beehave/icons/action.svg b/addons/beehave/icons/action.svg
new file mode 100644
index 0000000..3916c89
--- /dev/null
+++ b/addons/beehave/icons/action.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/action.svg.import b/addons/beehave/icons/action.svg.import
new file mode 100644
index 0000000..cf8a612
--- /dev/null
+++ b/addons/beehave/icons/action.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btrq8e0kyxthg"
+path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/action.svg"
+dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/blackboard.svg b/addons/beehave/icons/blackboard.svg
new file mode 100644
index 0000000..e4948a5
--- /dev/null
+++ b/addons/beehave/icons/blackboard.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/blackboard.svg.import b/addons/beehave/icons/blackboard.svg.import
new file mode 100644
index 0000000..4650058
--- /dev/null
+++ b/addons/beehave/icons/blackboard.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dw7rom0hiff6c"
+path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/blackboard.svg"
+dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_bt.svg b/addons/beehave/icons/category_bt.svg
new file mode 100644
index 0000000..8be61ae
--- /dev/null
+++ b/addons/beehave/icons/category_bt.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_bt.svg.import b/addons/beehave/icons/category_bt.svg.import
new file mode 100644
index 0000000..c11e4f2
--- /dev/null
+++ b/addons/beehave/icons/category_bt.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qpdd6ue7x82h"
+path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_bt.svg"
+dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_composite.svg b/addons/beehave/icons/category_composite.svg
new file mode 100644
index 0000000..aa8b866
--- /dev/null
+++ b/addons/beehave/icons/category_composite.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_composite.svg.import b/addons/beehave/icons/category_composite.svg.import
new file mode 100644
index 0000000..0496273
--- /dev/null
+++ b/addons/beehave/icons/category_composite.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://863s568sneja"
+path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_composite.svg"
+dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_decorator.svg b/addons/beehave/icons/category_decorator.svg
new file mode 100644
index 0000000..165e3d6
--- /dev/null
+++ b/addons/beehave/icons/category_decorator.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_decorator.svg.import b/addons/beehave/icons/category_decorator.svg.import
new file mode 100644
index 0000000..492f32e
--- /dev/null
+++ b/addons/beehave/icons/category_decorator.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c2ie8m4ddawlb"
+path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_decorator.svg"
+dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_leaf.svg b/addons/beehave/icons/category_leaf.svg
new file mode 100644
index 0000000..1482fe6
--- /dev/null
+++ b/addons/beehave/icons/category_leaf.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_leaf.svg.import b/addons/beehave/icons/category_leaf.svg.import
new file mode 100644
index 0000000..4ef9604
--- /dev/null
+++ b/addons/beehave/icons/category_leaf.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://eq0sp4g3s75r"
+path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_leaf.svg"
+dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/condition.svg b/addons/beehave/icons/condition.svg
new file mode 100644
index 0000000..37b2c7a
--- /dev/null
+++ b/addons/beehave/icons/condition.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/condition.svg.import b/addons/beehave/icons/condition.svg.import
new file mode 100644
index 0000000..ef59099
--- /dev/null
+++ b/addons/beehave/icons/condition.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ck4toqx0nggiu"
+path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/condition.svg"
+dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/cooldown.svg b/addons/beehave/icons/cooldown.svg
new file mode 100644
index 0000000..fbdfd6a
--- /dev/null
+++ b/addons/beehave/icons/cooldown.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/cooldown.svg.import b/addons/beehave/icons/cooldown.svg.import
new file mode 100644
index 0000000..1414358
--- /dev/null
+++ b/addons/beehave/icons/cooldown.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ckao52pluvfs1"
+path="res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/cooldown.svg"
+dest_files=["res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.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
diff --git a/addons/beehave/icons/delayer.svg b/addons/beehave/icons/delayer.svg
new file mode 100644
index 0000000..21cb617
--- /dev/null
+++ b/addons/beehave/icons/delayer.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/addons/beehave/icons/delayer.svg.import b/addons/beehave/icons/delayer.svg.import
new file mode 100644
index 0000000..33f4b7a
--- /dev/null
+++ b/addons/beehave/icons/delayer.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://daears4ljyeng"
+path="res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/delayer.svg"
+dest_files=["res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.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
diff --git a/addons/beehave/icons/failer.svg b/addons/beehave/icons/failer.svg
new file mode 100644
index 0000000..968f7e1
--- /dev/null
+++ b/addons/beehave/icons/failer.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/failer.svg.import b/addons/beehave/icons/failer.svg.import
new file mode 100644
index 0000000..989b556
--- /dev/null
+++ b/addons/beehave/icons/failer.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2fj7htaqvcud"
+path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/failer.svg"
+dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/inverter.svg b/addons/beehave/icons/inverter.svg
new file mode 100644
index 0000000..d4e791e
--- /dev/null
+++ b/addons/beehave/icons/inverter.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/inverter.svg.import b/addons/beehave/icons/inverter.svg.import
new file mode 100644
index 0000000..e9050a8
--- /dev/null
+++ b/addons/beehave/icons/inverter.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cffmoc3og8hux"
+path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/inverter.svg"
+dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/limiter.svg b/addons/beehave/icons/limiter.svg
new file mode 100644
index 0000000..7b3fa1d
--- /dev/null
+++ b/addons/beehave/icons/limiter.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/limiter.svg.import b/addons/beehave/icons/limiter.svg.import
new file mode 100644
index 0000000..7b56b08
--- /dev/null
+++ b/addons/beehave/icons/limiter.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c7akxvsg0f2by"
+path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/limiter.svg"
+dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/repeater.svg b/addons/beehave/icons/repeater.svg
new file mode 100644
index 0000000..47c46e9
--- /dev/null
+++ b/addons/beehave/icons/repeater.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/addons/beehave/icons/repeater.svg.import b/addons/beehave/icons/repeater.svg.import
new file mode 100644
index 0000000..cd94266
--- /dev/null
+++ b/addons/beehave/icons/repeater.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mkdpp16e57j0"
+path="res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/repeater.svg"
+dest_files=["res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.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
diff --git a/addons/beehave/icons/selector.svg b/addons/beehave/icons/selector.svg
new file mode 100644
index 0000000..0ae3b7a
--- /dev/null
+++ b/addons/beehave/icons/selector.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/selector.svg.import b/addons/beehave/icons/selector.svg.import
new file mode 100644
index 0000000..ef7326d
--- /dev/null
+++ b/addons/beehave/icons/selector.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b2c5d20doh4sp"
+path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector.svg"
+dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/selector_random.svg b/addons/beehave/icons/selector_random.svg
new file mode 100644
index 0000000..6f631e9
--- /dev/null
+++ b/addons/beehave/icons/selector_random.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/addons/beehave/icons/selector_random.svg.import b/addons/beehave/icons/selector_random.svg.import
new file mode 100644
index 0000000..6306f76
--- /dev/null
+++ b/addons/beehave/icons/selector_random.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmnkcmk7bkdjd"
+path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector_random.svg"
+dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/selector_reactive.svg b/addons/beehave/icons/selector_reactive.svg
new file mode 100644
index 0000000..6db005f
--- /dev/null
+++ b/addons/beehave/icons/selector_reactive.svg
@@ -0,0 +1,45 @@
+
+
diff --git a/addons/beehave/icons/selector_reactive.svg.import b/addons/beehave/icons/selector_reactive.svg.import
new file mode 100644
index 0000000..12a8c5b
--- /dev/null
+++ b/addons/beehave/icons/selector_reactive.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crkbov0h8sb8l"
+path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector_reactive.svg"
+dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence.svg b/addons/beehave/icons/sequence.svg
new file mode 100644
index 0000000..3ebedd9
--- /dev/null
+++ b/addons/beehave/icons/sequence.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/sequence.svg.import b/addons/beehave/icons/sequence.svg.import
new file mode 100644
index 0000000..5dadbe2
--- /dev/null
+++ b/addons/beehave/icons/sequence.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5gw354thiofm"
+path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence.svg"
+dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence_random.svg b/addons/beehave/icons/sequence_random.svg
new file mode 100644
index 0000000..34e4a12
--- /dev/null
+++ b/addons/beehave/icons/sequence_random.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/sequence_random.svg.import b/addons/beehave/icons/sequence_random.svg.import
new file mode 100644
index 0000000..3907462
--- /dev/null
+++ b/addons/beehave/icons/sequence_random.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bat8ptdw5qt1d"
+path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence_random.svg"
+dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence_reactive.svg b/addons/beehave/icons/sequence_reactive.svg
new file mode 100644
index 0000000..33d219b
--- /dev/null
+++ b/addons/beehave/icons/sequence_reactive.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/addons/beehave/icons/sequence_reactive.svg.import b/addons/beehave/icons/sequence_reactive.svg.import
new file mode 100644
index 0000000..ab0fa25
--- /dev/null
+++ b/addons/beehave/icons/sequence_reactive.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rmiu1slwfkh7"
+path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence_reactive.svg"
+dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/simple_parallel.svg b/addons/beehave/icons/simple_parallel.svg
new file mode 100644
index 0000000..e9c8b00
--- /dev/null
+++ b/addons/beehave/icons/simple_parallel.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/addons/beehave/icons/simple_parallel.svg.import b/addons/beehave/icons/simple_parallel.svg.import
new file mode 100644
index 0000000..b15ec65
--- /dev/null
+++ b/addons/beehave/icons/simple_parallel.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://lgxtdlmsrnm4"
+path="res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/simple_parallel.svg"
+dest_files=["res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.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
diff --git a/addons/beehave/icons/succeeder.svg b/addons/beehave/icons/succeeder.svg
new file mode 100644
index 0000000..10f5912
--- /dev/null
+++ b/addons/beehave/icons/succeeder.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/succeeder.svg.import b/addons/beehave/icons/succeeder.svg.import
new file mode 100644
index 0000000..0cb7334
--- /dev/null
+++ b/addons/beehave/icons/succeeder.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dl6wo332kglbe"
+path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/succeeder.svg"
+dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/tree.svg b/addons/beehave/icons/tree.svg
new file mode 100644
index 0000000..6c85ea1
--- /dev/null
+++ b/addons/beehave/icons/tree.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/tree.svg.import b/addons/beehave/icons/tree.svg.import
new file mode 100644
index 0000000..9ac0308
--- /dev/null
+++ b/addons/beehave/icons/tree.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deryyg2hbmaaw"
+path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/tree.svg"
+dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.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=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/until_fail.svg b/addons/beehave/icons/until_fail.svg
new file mode 100644
index 0000000..c64a0a0
--- /dev/null
+++ b/addons/beehave/icons/until_fail.svg
@@ -0,0 +1,45 @@
+
+
diff --git a/addons/beehave/icons/until_fail.svg.import b/addons/beehave/icons/until_fail.svg.import
new file mode 100644
index 0000000..8a04fbf
--- /dev/null
+++ b/addons/beehave/icons/until_fail.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dbonu175jh64l"
+path="res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/until_fail.svg"
+dest_files=["res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.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
diff --git a/addons/beehave/metrics/beehave_global_metrics.gd b/addons/beehave/metrics/beehave_global_metrics.gd
new file mode 100644
index 0000000..ef29db2
--- /dev/null
+++ b/addons/beehave/metrics/beehave_global_metrics.gd
@@ -0,0 +1,54 @@
+extends Node
+
+var _tree_count: int = 0
+var _active_tree_count: int = 0
+var _registered_trees: Array = []
+
+
+func _enter_tree() -> void:
+ Performance.add_custom_monitor("beehave/total_trees", _get_total_trees)
+ Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees)
+
+
+func register_tree(tree) -> void:
+ if _registered_trees.has(tree):
+ return
+
+ _registered_trees.append(tree)
+ _tree_count += 1
+
+ if tree.enabled:
+ _active_tree_count += 1
+
+ tree.tree_enabled.connect(_on_tree_enabled)
+ tree.tree_disabled.connect(_on_tree_disabled)
+
+
+func unregister_tree(tree) -> void:
+ if not _registered_trees.has(tree):
+ return
+
+ _registered_trees.erase(tree)
+ _tree_count -= 1
+
+ if tree.enabled:
+ _active_tree_count -= 1
+
+ tree.tree_enabled.disconnect(_on_tree_enabled)
+ tree.tree_disabled.disconnect(_on_tree_disabled)
+
+
+func _get_total_trees() -> int:
+ return _tree_count
+
+
+func _get_total_enabled_trees() -> int:
+ return _active_tree_count
+
+
+func _on_tree_enabled() -> void:
+ _active_tree_count += 1
+
+
+func _on_tree_disabled() -> void:
+ _active_tree_count -= 1
diff --git a/addons/beehave/nodes/beehave_node.gd b/addons/beehave/nodes/beehave_node.gd
new file mode 100644
index 0000000..9ab8d6e
--- /dev/null
+++ b/addons/beehave/nodes/beehave_node.gd
@@ -0,0 +1,46 @@
+@tool
+class_name BeehaveNode extends Node
+
+## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or
+## `RUNNING` when ticked.
+
+enum { SUCCESS, FAILURE, RUNNING }
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+
+ if get_children().any(func(x): return not (x is BeehaveNode)):
+ warnings.append("All children of this node should inherit from BeehaveNode class.")
+
+ return warnings
+
+
+## Executes this node and returns a status code.
+## This method must be overwritten.
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ return SUCCESS
+
+
+## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS.
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+## Called before the first time it ticks by the parent.
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+## Called after the last time it ticks and returns
+## [code]SUCCESS[/code] or [code]FAILURE[/code].
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+func get_class_name() -> Array[StringName]:
+ return [&"BeehaveNode"]
+
+
+func can_send_message(blackboard: Blackboard) -> bool:
+ return blackboard.get_value("can_send_message", false)
diff --git a/addons/beehave/nodes/beehave_tree.gd b/addons/beehave/nodes/beehave_tree.gd
new file mode 100644
index 0000000..e27edbd
--- /dev/null
+++ b/addons/beehave/nodes/beehave_tree.gd
@@ -0,0 +1,329 @@
+@tool
+@icon("../icons/tree.svg")
+class_name BeehaveTree extends Node
+
+## Controls the flow of execution of the entire behavior tree.
+
+enum { SUCCESS, FAILURE, RUNNING }
+
+enum ProcessThread { IDLE, PHYSICS }
+
+signal tree_enabled
+signal tree_disabled
+
+## Whether this behavior tree should be enabled or not.
+@export var enabled: bool = true:
+ set(value):
+ enabled = value
+ set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
+ set_process(enabled and process_thread == ProcessThread.IDLE)
+ if value:
+ tree_enabled.emit()
+ else:
+ interrupt()
+ tree_disabled.emit()
+
+ get:
+ return enabled
+
+## How often the tree should tick, in frames. The default value of 1 means
+## tick() runs every frame.
+@export var tick_rate: int = 1
+
+## An optional node path this behavior tree should apply to.
+@export_node_path var actor_node_path: NodePath:
+ set(anp):
+ actor_node_path = anp
+ if actor_node_path != null and str(actor_node_path) != "..":
+ actor = get_node(actor_node_path)
+ else:
+ actor = get_parent()
+ if Engine.is_editor_hint():
+ update_configuration_warnings()
+
+## Whether to run this tree in a physics or idle thread.
+@export var process_thread: ProcessThread = ProcessThread.PHYSICS:
+ set(value):
+ process_thread = value
+ set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
+ set_process(enabled and process_thread == ProcessThread.IDLE)
+
+## Custom blackboard node. An internal blackboard will be used
+## if no blackboard is provided explicitly.
+@export var blackboard: Blackboard:
+ set(b):
+ blackboard = b
+ if blackboard and _internal_blackboard:
+ remove_child(_internal_blackboard)
+ _internal_blackboard.free()
+ _internal_blackboard = null
+ elif not blackboard and not _internal_blackboard:
+ _internal_blackboard = Blackboard.new()
+ add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
+ get:
+ # in case blackboard is accessed before this node is,
+ # we need to ensure that the internal blackboard is used.
+ if not blackboard and not _internal_blackboard:
+ _internal_blackboard = Blackboard.new()
+ add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
+ return blackboard if blackboard else _internal_blackboard
+
+## When enabled, this tree is tracked individually
+## as a custom monitor.
+@export var custom_monitor = false:
+ set(b):
+ custom_monitor = b
+ if custom_monitor and _process_time_metric_name != "":
+ Performance.add_custom_monitor(
+ _process_time_metric_name, _get_process_time_metric_value
+ )
+ _get_global_metrics().register_tree(self)
+ else:
+ if _process_time_metric_name != "":
+ # Remove tree metric from the engine
+ Performance.remove_custom_monitor(_process_time_metric_name)
+ _get_global_metrics().unregister_tree(self)
+
+ BeehaveDebuggerMessages.unregister_tree(get_instance_id())
+
+@export var actor: Node:
+ set(a):
+ actor = a
+ if actor == null:
+ actor = get_parent()
+ if Engine.is_editor_hint():
+ update_configuration_warnings()
+
+var status: int = -1
+var last_tick: int = 0
+
+var _internal_blackboard: Blackboard
+var _process_time_metric_name: String
+var _process_time_metric_value: float = 0.0
+var _can_send_message: bool = false
+
+
+func _ready() -> void:
+ var connect_scene_tree_signal = func(signal_name: String, is_added: bool):
+ if not get_tree().is_connected(signal_name, _on_scene_tree_node_added_removed.bind(is_added)):
+ get_tree().connect(signal_name, _on_scene_tree_node_added_removed.bind(is_added))
+ connect_scene_tree_signal.call("node_added", true)
+ connect_scene_tree_signal.call("node_removed", false)
+
+ if not process_thread:
+ process_thread = ProcessThread.PHYSICS
+
+ if not actor:
+ if actor_node_path:
+ actor = get_node(actor_node_path)
+ else:
+ actor = get_parent()
+
+ if not blackboard:
+ # invoke setter to auto-initialise the blackboard.
+ self.blackboard = null
+
+ # Get the name of the parent node name for metric
+ _process_time_metric_name = (
+ "beehave [microseconds]/process_time_%s-%s" % [actor.name, get_instance_id()]
+ )
+
+ set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
+ set_process(enabled and process_thread == ProcessThread.IDLE)
+
+ # Register custom metric to the engine
+ if custom_monitor and not Engine.is_editor_hint():
+ Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
+ _get_global_metrics().register_tree(self)
+
+ if Engine.is_editor_hint():
+ update_configuration_warnings.call_deferred()
+ else:
+ _get_global_debugger().register_tree(self)
+ BeehaveDebuggerMessages.register_tree(_get_debugger_data(self))
+
+ # Randomize at what frames tick() will happen to avoid stutters
+ last_tick = randi_range(0, tick_rate - 1)
+
+
+func _on_scene_tree_node_added_removed(node: Node, is_added: bool) -> void:
+ if Engine.is_editor_hint():
+ return
+
+ if node is BeehaveNode and is_ancestor_of(node):
+ var sgnal := node.ready if is_added else node.tree_exited
+ if is_added:
+ sgnal.connect(
+ func() -> void: BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)),
+ CONNECT_ONE_SHOT
+ )
+ else:
+ sgnal.connect(
+ func() -> void:
+ BeehaveDebuggerMessages.unregister_tree(get_instance_id())
+ request_ready()
+ )
+
+
+func _physics_process(_delta: float) -> void:
+ _process_internally()
+
+
+func _process(_delta: float) -> void:
+ _process_internally()
+
+
+func _process_internally() -> void:
+ if Engine.is_editor_hint():
+ return
+
+ if last_tick < tick_rate - 1:
+ last_tick += 1
+ return
+
+ last_tick = 0
+
+ # Start timing for metric
+ var start_time = Time.get_ticks_usec()
+
+ blackboard.set_value("can_send_message", _can_send_message)
+
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_begin(get_instance_id())
+
+ if self.get_child_count() == 1:
+ tick()
+
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_end(get_instance_id())
+
+ # Check the cost for this frame and save it for metric report
+ _process_time_metric_value = Time.get_ticks_usec() - start_time
+
+
+func tick() -> int:
+ if actor == null or get_child_count() == 0:
+ return FAILURE
+ var child := self.get_child(0)
+ if status != RUNNING:
+ child.before_run(actor, blackboard)
+
+ status = child.tick(actor, blackboard)
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status)
+ BeehaveDebuggerMessages.process_tick(get_instance_id(), status)
+
+ # Clear running action if nothing is running
+ if status != RUNNING:
+ blackboard.set_value("running_action", null, str(actor.get_instance_id()))
+ child.after_run(actor, blackboard)
+
+ return status
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+
+ if actor == null:
+ warnings.append("Configure target node on tree")
+
+ if get_children().any(func(x): return not (x is BeehaveNode)):
+ warnings.append("All children of this node should inherit from BeehaveNode class.")
+
+ if get_child_count() != 1:
+ warnings.append("BeehaveTree should have exactly one child node.")
+
+ return warnings
+
+
+## Returns the currently running action
+func get_running_action() -> ActionLeaf:
+ return blackboard.get_value("running_action", null, str(actor.get_instance_id()))
+
+
+## Returns the last condition that was executed
+func get_last_condition() -> ConditionLeaf:
+ return blackboard.get_value("last_condition", null, str(actor.get_instance_id()))
+
+
+## Returns the status of the last executed condition
+func get_last_condition_status() -> String:
+ if blackboard.has_value("last_condition_status", str(actor.get_instance_id())):
+ var status = blackboard.get_value(
+ "last_condition_status", null, str(actor.get_instance_id())
+ )
+ if status == SUCCESS:
+ return "SUCCESS"
+ elif status == FAILURE:
+ return "FAILURE"
+ else:
+ return "RUNNING"
+ return ""
+
+
+## interrupts this tree if anything was running
+func interrupt() -> void:
+ if self.get_child_count() != 0:
+ var first_child = self.get_child(0)
+ if "interrupt" in first_child:
+ first_child.interrupt(actor, blackboard)
+
+
+## Enables this tree.
+func enable() -> void:
+ self.enabled = true
+
+
+## Disables this tree.
+func disable() -> void:
+ self.enabled = false
+
+
+func _exit_tree() -> void:
+ if custom_monitor:
+ if _process_time_metric_name != "":
+ # Remove tree metric from the engine
+ Performance.remove_custom_monitor(_process_time_metric_name)
+ _get_global_metrics().unregister_tree(self)
+
+ BeehaveDebuggerMessages.unregister_tree(get_instance_id())
+
+
+# Called by the engine to profile this tree
+func _get_process_time_metric_value() -> int:
+ return int(_process_time_metric_value)
+
+
+func _get_debugger_data(node: Node) -> Dictionary:
+ if not (node is BeehaveTree or node is BeehaveNode):
+ return {}
+
+ var data := {
+ path = node.get_path(),
+ name = node.name,
+ type = node.get_class_name(),
+ id = str(node.get_instance_id())
+ }
+ if node.get_child_count() > 0:
+ data.children = []
+ for child in node.get_children():
+ var child_data := _get_debugger_data(child)
+ if not child_data.is_empty():
+ data.children.push_back(child_data)
+ return data
+
+
+func get_class_name() -> Array[StringName]:
+ return [&"BeehaveTree"]
+
+
+# required to avoid lifecycle issues on initial load
+# due to loading order problems with autoloads
+func _get_global_metrics() -> Node:
+ return get_tree().root.get_node("BeehaveGlobalMetrics")
+
+
+# required to avoid lifecycle issues on initial load
+# due to loading order problems with autoloads
+func _get_global_debugger() -> Node:
+ return get_tree().root.get_node("BeehaveGlobalDebugger")
diff --git a/addons/beehave/nodes/composites/composite.gd b/addons/beehave/nodes/composites/composite.gd
new file mode 100644
index 0000000..53a8e90
--- /dev/null
+++ b/addons/beehave/nodes/composites/composite.gd
@@ -0,0 +1,34 @@
+@tool
+@icon("../../icons/category_composite.svg")
+class_name Composite extends BeehaveNode
+
+## A Composite node controls the flow of execution of its children in a specific manner.
+
+var running_child: BeehaveNode = null
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = super._get_configuration_warnings()
+
+ if get_children().filter(func(x): return x is BeehaveNode).size() < 2:
+ warnings.append(
+ "Any composite node should have at least two children. Otherwise it is not useful."
+ )
+
+ return warnings
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = null
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ running_child = null
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Composite")
+ return classes
diff --git a/addons/beehave/nodes/composites/randomized_composite.gd b/addons/beehave/nodes/composites/randomized_composite.gd
new file mode 100644
index 0000000..2afa5e9
--- /dev/null
+++ b/addons/beehave/nodes/composites/randomized_composite.gd
@@ -0,0 +1,176 @@
+@tool
+class_name RandomizedComposite extends Composite
+
+const WEIGHTS_PREFIX = "Weights/"
+
+## Sets a predicable seed
+@export var random_seed: int = 0:
+ set(rs):
+ random_seed = rs
+ if random_seed != 0:
+ seed(random_seed)
+ else:
+ randomize()
+
+## Wether to use weights for every child or not.
+@export var use_weights: bool:
+ set(value):
+ use_weights = value
+ if use_weights:
+ _update_weights(get_children())
+ _connect_children_changing_signals()
+ notify_property_list_changed()
+
+var _weights: Dictionary
+var _exiting_tree: bool
+
+
+func _ready():
+ _connect_children_changing_signals()
+
+
+func _connect_children_changing_signals():
+ if not child_entered_tree.is_connected(_on_child_entered_tree):
+ child_entered_tree.connect(_on_child_entered_tree)
+
+ if not child_exiting_tree.is_connected(_on_child_exiting_tree):
+ child_exiting_tree.connect(_on_child_exiting_tree)
+
+
+func get_shuffled_children() -> Array[Node]:
+ var children_bag: Array[Node] = get_children().duplicate()
+ if use_weights:
+ var weights: Array[int]
+ weights.assign(children_bag.map(func(child): return _weights[child.name]))
+ children_bag.assign(_weighted_shuffle(children_bag, weights))
+ else:
+ children_bag.shuffle()
+ return children_bag
+
+
+## Returns a shuffled version of a given array using the supplied array of weights.
+## Think of weights as the chance of a given item being the first in the array.
+func _weighted_shuffle(items: Array, weights: Array[int]) -> Array:
+ if len(items) != len(weights):
+ push_error(
+ (
+ "items and weights size mismatch: expected %d weights, got %d instead."
+ % [len(items), len(weights)]
+ )
+ )
+ return items
+
+ # This method is based on the weighted random sampling algorithm
+ # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)).
+
+ # For each index, it will calculate random_value^(1/weight).
+ var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])]
+ var random_distribuition = range(len(items)).map(chance_calc)
+
+ # Now we just have to order by the calculated value, descending.
+ random_distribuition.sort_custom(func(a, b): return a[1] > b[1])
+
+ return random_distribuition.map(func(dist): return items[dist[0]])
+
+
+func _get_property_list():
+ var properties = []
+
+ if use_weights:
+ for key in _weights.keys():
+ properties.append(
+ {
+ "name": WEIGHTS_PREFIX + key,
+ "type": TYPE_INT,
+ "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
+ "hint": PROPERTY_HINT_RANGE,
+ "hint_string": "1,100"
+ }
+ )
+
+ return properties
+
+
+func _set(property: StringName, value: Variant) -> bool:
+ if property.begins_with(WEIGHTS_PREFIX):
+ var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
+ _weights[weight_name] = value
+ return true
+
+ return false
+
+
+func _get(property: StringName):
+ if property.begins_with(WEIGHTS_PREFIX):
+ var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
+ return _weights[weight_name]
+
+ return null
+
+
+func _update_weights(children: Array[Node]) -> void:
+ var new_weights = {}
+ for c in children:
+ if _weights.has(c.name):
+ new_weights[c.name] = _weights[c.name]
+ else:
+ new_weights[c.name] = 1
+ _weights = new_weights
+ notify_property_list_changed()
+
+
+func _exit_tree() -> void:
+ _exiting_tree = true
+
+
+func _enter_tree() -> void:
+ _exiting_tree = false
+
+
+func _on_child_entered_tree(node: Node):
+ _update_weights(get_children())
+
+ var renamed_callable = _on_child_renamed.bind(node.name, node)
+ if not node.renamed.is_connected(renamed_callable):
+ node.renamed.connect(renamed_callable)
+
+ if not node.tree_exited.is_connected(_on_child_tree_exited):
+ node.tree_exited.connect(_on_child_tree_exited.bind(node))
+
+
+func _on_child_exiting_tree(node: Node):
+ var renamed_callable = _on_child_renamed.bind(node.name, node)
+ if node.renamed.is_connected(renamed_callable):
+ node.renamed.disconnect(renamed_callable)
+
+
+func _on_child_tree_exited(node: Node) -> void:
+ # don't erase the individual child if the whole tree is exiting together
+ if not _exiting_tree:
+ var children = get_children()
+ children.erase(node)
+ _update_weights(children)
+
+ if node.tree_exited.is_connected(_on_child_tree_exited):
+ node.tree_exited.disconnect(_on_child_tree_exited)
+
+
+func _on_child_renamed(old_name: String, renamed_child: Node):
+ if old_name == renamed_child.name:
+ return # No need to update the weights.
+
+ # Disconnect signal with old name...
+ renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child))
+ # ...and connect with the new name.
+ renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child))
+
+ var original_weight = _weights[old_name]
+ _weights.erase(old_name)
+ _weights[renamed_child.name] = original_weight
+ notify_property_list_changed()
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"RandomizedComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector.gd b/addons/beehave/nodes/composites/selector.gd
new file mode 100644
index 0000000..618f602
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector.gd
@@ -0,0 +1,69 @@
+@tool
+@icon("../../icons/selector.svg")
+class_name SelectorComposite extends Composite
+
+## Selector nodes will attempt to execute each of its children until one of
+## them return `SUCCESS`. If all children return `FAILURE`, this node will also
+## return `FAILURE`.
+## If a child returns `RUNNING` it will tick again.
+
+var last_execution_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < last_execution_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _cleanup_running_task(c, actor, blackboard)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ _cleanup_running_task(c, actor, blackboard)
+ last_execution_index += 1
+ c.after_run(actor, blackboard)
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ last_execution_index = 0
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ last_execution_index = 0
+ super(actor, blackboard)
+
+
+## Changes `running_action` and `running_child` after the node finishes executing.
+func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
+ var blackboard_name = str(actor.get_instance_id())
+ if finished_action == running_child:
+ running_child = null
+ if finished_action == blackboard.get_value("running_action", null, blackboard_name):
+ blackboard.set_value("running_action", null, blackboard_name)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector_random.gd b/addons/beehave/nodes/composites/selector_random.gd
new file mode 100644
index 0000000..7a42300
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector_random.gd
@@ -0,0 +1,82 @@
+@tool
+@icon("../../icons/selector_random.svg")
+class_name SelectorRandomComposite extends RandomizedComposite
+
+## This node will attempt to execute all of its children just like a
+## [code]SelectorStar[/code] would, with the exception that the children
+## will be executed in a random order.
+
+## A shuffled list of the children that will be executed in reverse order.
+var _children_bag: Array[Node] = []
+var c: Node
+
+
+func _ready() -> void:
+ super()
+ if random_seed == 0:
+ randomize()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if _children_bag.is_empty():
+ _reset()
+
+ # We need to traverse the array in reverse since we will be manipulating it.
+ for i in _get_reversed_indexes():
+ c = _children_bag[i]
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _get_reversed_indexes() -> Array[int]:
+ var reversed: Array[int]
+ reversed.assign(range(_children_bag.size()))
+ reversed.reverse()
+ return reversed
+
+
+func _reset() -> void:
+ var new_order = get_shuffled_children()
+ _children_bag = new_order.duplicate()
+ _children_bag.reverse() # It needs to run the children in reverse order.
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorRandomComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector_reactive.gd b/addons/beehave/nodes/composites/selector_reactive.gd
new file mode 100644
index 0000000..fa164d0
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector_reactive.gd
@@ -0,0 +1,47 @@
+@tool
+@icon("../../icons/selector_reactive.svg")
+class_name SelectorReactiveComposite extends Composite
+
+## Selector Reactive nodes will attempt to execute each of its children until one of
+## them return `SUCCESS`. If all children return `FAILURE`, this node will also
+## return `FAILURE`.
+## If a child returns `RUNNING` it will restart.
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ # Interrupt any child that was RUNNING before.
+ if c != running_child:
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ c.after_run(actor, blackboard)
+ RUNNING:
+ if c != running_child:
+ interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorReactiveComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence.gd b/addons/beehave/nodes/composites/sequence.gd
new file mode 100644
index 0000000..d308e8d
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence.gd
@@ -0,0 +1,75 @@
+@tool
+@icon("../../icons/sequence.svg")
+class_name SequenceComposite extends Composite
+
+## Sequence nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and restart.
+## In case a child returns `RUNNING` this node will tick again.
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _cleanup_running_task(c, actor, blackboard)
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ _cleanup_running_task(c, actor, blackboard)
+ # Interrupt any child that was RUNNING before.
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ if c != running_child:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ successful_index = 0
+
+
+## Changes `running_action` and `running_child` after the node finishes executing.
+func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
+ var blackboard_name = str(actor.get_instance_id())
+ if finished_action == running_child:
+ running_child = null
+ if finished_action == blackboard.get_value("running_action", null, blackboard_name):
+ blackboard.set_value("running_action", null, blackboard_name)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_random.gd b/addons/beehave/nodes/composites/sequence_random.gd
new file mode 100644
index 0000000..a744f52
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_random.gd
@@ -0,0 +1,96 @@
+@tool
+@icon("../../icons/sequence_random.svg")
+class_name SequenceRandomComposite extends RandomizedComposite
+
+## This node will attempt to execute all of its children just like a
+## [code]SequenceStar[/code] would, with the exception that the children
+## will be executed in a random order.
+
+# Emitted whenever the children are shuffled.
+signal reset(new_order: Array[Node])
+
+## Whether the sequence should start where it left off after a previous failure.
+@export var resume_on_failure: bool = false
+## Whether the sequence should start where it left off after a previous interruption.
+@export var resume_on_interrupt: bool = false
+
+## A shuffled list of the children that will be executed in reverse order.
+var _children_bag: Array[Node] = []
+var c: Node
+
+
+func _ready() -> void:
+ super()
+ if random_seed == 0:
+ randomize()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if _children_bag.is_empty():
+ _reset()
+
+ # We need to traverse the array in reverse since we will be manipulating it.
+ for i in _get_reversed_indexes():
+ c = _children_bag[i]
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ FAILURE:
+ _children_bag.erase(c)
+ # Interrupt any child that was RUNNING before
+ # but do not reset!
+ super.interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return SUCCESS
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ if not resume_on_failure:
+ _reset()
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if not resume_on_interrupt:
+ _reset()
+ super(actor, blackboard)
+
+
+func _get_reversed_indexes() -> Array[int]:
+ var reversed: Array[int]
+ reversed.assign(range(_children_bag.size()))
+ reversed.reverse()
+ return reversed
+
+
+func _reset() -> void:
+ var new_order = get_shuffled_children()
+ _children_bag = new_order.duplicate()
+ _children_bag.reverse() # It needs to run the children in reverse order.
+ reset.emit(new_order)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceRandomComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_reactive.gd b/addons/beehave/nodes/composites/sequence_reactive.gd
new file mode 100644
index 0000000..f40d8ab
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_reactive.gd
@@ -0,0 +1,63 @@
+@tool
+@icon("../../icons/sequence_reactive.svg")
+class_name SequenceReactiveComposite extends Composite
+
+## Reactive Sequence nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and restart.
+## In case a child returns `RUNNING` this node will restart.
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ # Interrupt any child that was RUNNING before.
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ _reset()
+ if running_child != c:
+ interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ successful_index = 0
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceReactiveComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_star.gd b/addons/beehave/nodes/composites/sequence_star.gd
new file mode 100644
index 0000000..36bb19c
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_star.gd
@@ -0,0 +1,61 @@
+@tool
+@icon("../../icons/sequence_reactive.svg")
+class_name SequenceStarComposite extends Composite
+
+## Sequence Star nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and tick again.
+## In case a child returns `RUNNING` this node will tick again.
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ # Interrupt any child that was RUNNING before
+ # but do not reset!
+ super.interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ successful_index = 0
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceStarComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/simple_parallel.gd b/addons/beehave/nodes/composites/simple_parallel.gd
new file mode 100644
index 0000000..4133d60
--- /dev/null
+++ b/addons/beehave/nodes/composites/simple_parallel.gd
@@ -0,0 +1,122 @@
+@tool
+@icon("../../icons/simple_parallel.svg")
+class_name SimpleParallelComposite extends Composite
+
+## Simple Parallel nodes will attampt to execute all chidren at same time and
+## can only have exactly two children. First child as primary node, second
+## child as secondary node.
+## This node will always report primary node's state, and continue tick while
+## primary node return 'RUNNING'. The state of secondary node will be ignored
+## and executed like a subtree.
+## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt
+## secondary node and return primary node's result.
+## If this node is running under delay mode, it will wait seconday node
+## finish its action after primary node terminates.
+
+#how many times should secondary node repeat, zero means loop forever
+@export var secondary_node_repeat_count: int = 0
+
+#wether to wait secondary node finish its current action after primary node finished
+@export var delay_mode: bool = false
+
+var delayed_result := SUCCESS
+var main_task_finished: bool = false
+var secondary_node_running: bool = false
+var secondary_node_repeat_left: int = 0
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = super._get_configuration_warnings()
+
+ if get_child_count() != 2:
+ warnings.append("SimpleParallel should have exactly two child nodes.")
+
+ if not get_child(0) is ActionLeaf:
+ warnings.append("SimpleParallel should have an action leaf node as first child node.")
+
+ return warnings
+
+
+func tick(actor, blackboard: Blackboard):
+ for c in get_children():
+ var node_index = c.get_index()
+ if node_index == 0 and not main_task_finished:
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ delayed_result = response
+ match response:
+ SUCCESS, FAILURE:
+ _cleanup_running_task(c, actor, blackboard)
+ c.after_run(actor, blackboard)
+ main_task_finished = true
+ if not delay_mode:
+ if secondary_node_running:
+ get_child(1).interrupt(actor, blackboard)
+ _reset()
+ return delayed_result
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+
+ elif node_index == 1:
+ if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0:
+ if not secondary_node_running:
+ c.before_run(actor, blackboard)
+ var subtree_response = c.tick(actor, blackboard)
+ if subtree_response != RUNNING:
+ secondary_node_running = false
+ c.after_run(actor, blackboard)
+ if delay_mode and main_task_finished:
+ _reset()
+ return delayed_result
+ elif secondary_node_repeat_left > 0:
+ secondary_node_repeat_left -= 1
+ else:
+ secondary_node_running = true
+
+ return RUNNING
+
+
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ secondary_node_repeat_left = secondary_node_repeat_count
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if not main_task_finished:
+ get_child(0).interrupt(actor, blackboard)
+ if secondary_node_running:
+ get_child(1).interrupt(actor, blackboard)
+ _reset()
+ super(actor, blackboard)
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ main_task_finished = false
+ secondary_node_running = false
+
+
+## Changes `running_action` and `running_child` after the node finishes executing.
+func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
+ var blackboard_name = str(actor.get_instance_id())
+ if finished_action == running_child:
+ running_child = null
+ if finished_action == blackboard.get_value("running_action", null, blackboard_name):
+ blackboard.set_value("running_action", null, blackboard_name)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SimpleParallelComposite")
+ return classes
diff --git a/addons/beehave/nodes/decorators/cooldown.gd b/addons/beehave/nodes/decorators/cooldown.gd
new file mode 100644
index 0000000..84c27b7
--- /dev/null
+++ b/addons/beehave/nodes/decorators/cooldown.gd
@@ -0,0 +1,49 @@
+@tool
+@icon("../../icons/cooldown.svg")
+extends Decorator
+class_name CooldownDecorator
+
+## The Cooldown Decorator will return 'FAILURE' for a set amount of time
+## after executing its child.
+## The timer resets the next time its child is executed and it is not `RUNNING`
+
+## The wait time in seconds
+@export var wait_time := 0.0
+
+@onready var cache_key = "cooldown_%s" % self.get_instance_id()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+ var remaining_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
+ var response
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ if remaining_time > 0:
+ response = FAILURE
+
+ remaining_time -= get_physics_process_delta_time()
+ blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id()))
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
+ else:
+ response = c.tick(actor, blackboard)
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING and c is ActionLeaf:
+ running_child = c
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+
+ if response != RUNNING:
+ blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id()))
+
+ return response
diff --git a/addons/beehave/nodes/decorators/decorator.gd b/addons/beehave/nodes/decorators/decorator.gd
new file mode 100644
index 0000000..3b93498
--- /dev/null
+++ b/addons/beehave/nodes/decorators/decorator.gd
@@ -0,0 +1,33 @@
+@tool
+@icon("../../icons/category_decorator.svg")
+class_name Decorator extends BeehaveNode
+
+## Decorator nodes are used to transform the result received by its child.
+## Must only have one child.
+
+var running_child: BeehaveNode = null
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = super._get_configuration_warnings()
+
+ if get_child_count() != 1:
+ warnings.append("Decorator should have exactly one child node.")
+
+ return warnings
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = null
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ running_child = null
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Decorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/delayer.gd b/addons/beehave/nodes/decorators/delayer.gd
new file mode 100644
index 0000000..2b8b73c
--- /dev/null
+++ b/addons/beehave/nodes/decorators/delayer.gd
@@ -0,0 +1,49 @@
+@tool
+@icon("../../icons/delayer.svg")
+extends Decorator
+class_name DelayDecorator
+
+## The Delay Decorator will return 'RUNNING' for a set amount of time
+## before executing its child.
+## The timer resets when both it and its child are not `RUNNING`
+
+## The wait time in seconds
+@export var wait_time := 0.0
+
+@onready var cache_key = "time_limiter_%s" % self.get_instance_id()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+ var total_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
+ var response
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ if total_time < wait_time:
+ response = RUNNING
+
+ total_time += get_physics_process_delta_time()
+ blackboard.set_value(cache_key, total_time, str(actor.get_instance_id()))
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
+ else:
+ response = c.tick(actor, blackboard)
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING and c is ActionLeaf:
+ running_child = c
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+
+ if response != RUNNING:
+ blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id()))
+
+ return response
diff --git a/addons/beehave/nodes/decorators/failer.gd b/addons/beehave/nodes/decorators/failer.gd
new file mode 100644
index 0000000..e47312f
--- /dev/null
+++ b/addons/beehave/nodes/decorators/failer.gd
@@ -0,0 +1,35 @@
+@tool
+@icon("../../icons/failer.svg")
+class_name AlwaysFailDecorator extends Decorator
+
+## A Failer node will always return a `FAILURE` status code.
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ else:
+ c.after_run(actor, blackboard)
+ return FAILURE
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"AlwaysFailDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/inverter.gd b/addons/beehave/nodes/decorators/inverter.gd
new file mode 100644
index 0000000..2570677
--- /dev/null
+++ b/addons/beehave/nodes/decorators/inverter.gd
@@ -0,0 +1,43 @@
+@tool
+@icon("../../icons/inverter.svg")
+class_name InverterDecorator extends Decorator
+
+## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
+## code or `SUCCESS` in case its child returns a `FAILURE` status code.
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ c.after_run(actor, blackboard)
+ return FAILURE
+ FAILURE:
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _:
+ push_error("This should be unreachable")
+ return -1
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"InverterDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/limiter.gd b/addons/beehave/nodes/decorators/limiter.gd
new file mode 100644
index 0000000..a38d7f3
--- /dev/null
+++ b/addons/beehave/nodes/decorators/limiter.gd
@@ -0,0 +1,60 @@
+@tool
+@icon("../../icons/limiter.svg")
+class_name LimiterDecorator extends Decorator
+
+## The limiter will execute its `RUNNING` child `x` amount of times. When the number of
+## maximum ticks is reached, it will return a `FAILURE` status code.
+## The count resets the next time that a child is not `RUNNING`
+
+@onready var cache_key = "limiter_%s" % self.get_instance_id()
+
+@export var max_count: float = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if not get_child_count() == 1:
+ return FAILURE
+
+ var child = get_child(0)
+ var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id()))
+
+ if current_count < max_count:
+ blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id()))
+ var response = child.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
+
+ if child is ConditionLeaf:
+ blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if child is ActionLeaf and response == RUNNING:
+ running_child = child
+ blackboard.set_value("running_action", child, str(actor.get_instance_id()))
+
+ if response != RUNNING:
+ child.after_run(actor, blackboard)
+
+ return response
+ else:
+ interrupt(actor, blackboard)
+ child.after_run(actor, blackboard)
+ return FAILURE
+
+
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ blackboard.set_value(cache_key, 0, str(actor.get_instance_id()))
+ if get_child_count() > 0:
+ get_child(0).before_run(actor, blackboard)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"LimiterDecorator")
+ return classes
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ if not get_child_count() == 1:
+ return ["Requires exactly one child node"]
+ return []
diff --git a/addons/beehave/nodes/decorators/repeater.gd b/addons/beehave/nodes/decorators/repeater.gd
new file mode 100644
index 0000000..647f7e0
--- /dev/null
+++ b/addons/beehave/nodes/decorators/repeater.gd
@@ -0,0 +1,58 @@
+## The repeater will execute its child until it returns `SUCCESS` a certain amount of times.
+## When the number of maximum ticks is reached, it will return a `SUCCESS` status code.
+## If the child returns `FAILURE`, the repeater will return `FAILURE` immediately.
+@tool
+@icon("../../icons/repeater.svg")
+class_name RepeaterDecorator extends Decorator
+
+@export var repetitions: int = 1
+var current_count: int = 0
+
+
+func before_run(actor: Node, blackboard: Blackboard):
+ current_count = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var child = get_child(0)
+
+ if current_count < repetitions:
+ if running_child == null:
+ child.before_run(actor, blackboard)
+
+ var response = child.tick(actor, blackboard)
+
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
+
+ if child is ConditionLeaf:
+ blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = child
+ if child is ActionLeaf:
+ blackboard.set_value("running_action", child, str(actor.get_instance_id()))
+ return RUNNING
+
+ current_count += 1
+ child.after_run(actor, blackboard)
+
+ if running_child != null:
+ running_child = null
+
+ if response == FAILURE:
+ return FAILURE
+
+ if current_count >= repetitions:
+ return SUCCESS
+
+ return RUNNING
+ else:
+ return SUCCESS
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"LimiterDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/succeeder.gd b/addons/beehave/nodes/decorators/succeeder.gd
new file mode 100644
index 0000000..344b90f
--- /dev/null
+++ b/addons/beehave/nodes/decorators/succeeder.gd
@@ -0,0 +1,35 @@
+@tool
+@icon("../../icons/succeeder.svg")
+class_name AlwaysSucceedDecorator extends Decorator
+
+## A succeeder node will always return a `SUCCESS` status code.
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ else:
+ c.after_run(actor, blackboard)
+ return SUCCESS
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"AlwaysSucceedDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/time_limiter.gd b/addons/beehave/nodes/decorators/time_limiter.gd
new file mode 100644
index 0000000..d9638d7
--- /dev/null
+++ b/addons/beehave/nodes/decorators/time_limiter.gd
@@ -0,0 +1,60 @@
+@tool
+@icon("../../icons/limiter.svg")
+class_name TimeLimiterDecorator extends Decorator
+
+## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish
+## before interrupting it and return a `FAILURE` status code.
+## The timer resets the next time that a child is not `RUNNING`
+
+@export var wait_time := 0.0
+
+@onready var cache_key = "time_limiter_%s" % self.get_instance_id()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if not get_child_count() == 1:
+ return FAILURE
+
+ var child = self.get_child(0)
+ var time_left = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
+
+ if time_left < wait_time:
+ time_left += get_physics_process_delta_time()
+ blackboard.set_value(cache_key, time_left, str(actor.get_instance_id()))
+ var response = child.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
+
+ if child is ConditionLeaf:
+ blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = child
+ if child is ActionLeaf:
+ blackboard.set_value("running_action", child, str(actor.get_instance_id()))
+ else:
+ child.after_run(actor, blackboard)
+ return response
+ else:
+ interrupt(actor, blackboard)
+ child.after_run(actor, blackboard)
+ return FAILURE
+
+
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id()))
+ if get_child_count() > 0:
+ get_child(0).before_run(actor, blackboard)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"TimeLimiterDecorator")
+ return classes
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ if not get_child_count() == 1:
+ return ["Requires exactly one child node"]
+ return []
diff --git a/addons/beehave/nodes/decorators/until_fail.gd b/addons/beehave/nodes/decorators/until_fail.gd
new file mode 100644
index 0000000..8f5ebbe
--- /dev/null
+++ b/addons/beehave/nodes/decorators/until_fail.gd
@@ -0,0 +1,33 @@
+@tool
+@icon("../../icons/until_fail.svg")
+class_name UntilFailDecorator
+extends Decorator
+
+## The UntilFail Decorator will return `RUNNING` if its child returns
+## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns
+## `FAILURE`
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ if response == SUCCESS:
+ return RUNNING
+
+ return SUCCESS
diff --git a/addons/beehave/nodes/leaves/action.gd b/addons/beehave/nodes/leaves/action.gd
new file mode 100644
index 0000000..9074c07
--- /dev/null
+++ b/addons/beehave/nodes/leaves/action.gd
@@ -0,0 +1,14 @@
+@tool
+@icon("../../icons/action.svg")
+class_name ActionLeaf extends Leaf
+
+## Actions are leaf nodes that define a task to be performed by an actor.
+## Their execution can be long running, potentially being called across multiple
+## frame executions. In this case, the node should return `RUNNING` until the
+## action is completed.
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"ActionLeaf")
+ return classes
diff --git a/addons/beehave/nodes/leaves/blackboard_compare.gd b/addons/beehave/nodes/leaves/blackboard_compare.gd
new file mode 100644
index 0000000..38a870d
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_compare.gd
@@ -0,0 +1,65 @@
+@tool
+class_name BlackboardCompareCondition extends ConditionLeaf
+
+## Compares two values using the specified comparison operator.
+## Returns [code]FAILURE[/code] if any of the expression fails or the
+## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code].
+
+enum Operators {
+ EQUAL,
+ NOT_EQUAL,
+ GREATER,
+ LESS,
+ GREATER_EQUAL,
+ LESS_EQUAL,
+}
+
+## Expression represetning left operand.
+## This value can be any valid GDScript expression.
+## In order to use the existing blackboard keys for comparison,
+## use get_value("key_name") e.g. get_value("direction").length()
+@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = ""
+## Comparison operator.
+@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0
+## Expression represetning right operand.
+## This value can be any valid GDScript expression.
+## In order to use the existing blackboard keys for comparison,
+## use get_value("key_name") e.g. get_value("direction").length()
+@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = ""
+
+@onready var _left_expression: Expression = _parse_expression(left_operand)
+@onready var _right_expression: Expression = _parse_expression(right_operand)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var left: Variant = _left_expression.execute([], blackboard)
+
+ if _left_expression.has_execute_failed():
+ return FAILURE
+
+ var right: Variant = _right_expression.execute([], blackboard)
+
+ if _right_expression.has_execute_failed():
+ return FAILURE
+
+ var result: bool = false
+
+ match operator:
+ Operators.EQUAL:
+ result = left == right
+ Operators.NOT_EQUAL:
+ result = left != right
+ Operators.GREATER:
+ result = left > right
+ Operators.LESS:
+ result = left < right
+ Operators.GREATER_EQUAL:
+ result = left >= right
+ Operators.LESS_EQUAL:
+ result = left <= right
+
+ return SUCCESS if result else FAILURE
+
+
+func _get_expression_sources() -> Array[String]:
+ return [left_operand, right_operand]
diff --git a/addons/beehave/nodes/leaves/blackboard_erase.gd b/addons/beehave/nodes/leaves/blackboard_erase.gd
new file mode 100644
index 0000000..e5cc1d4
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_erase.gd
@@ -0,0 +1,25 @@
+@tool
+class_name BlackboardEraseAction extends ActionLeaf
+
+## Erases the specified key from the blackboard.
+## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+
+@onready var _key_expression: Expression = _parse_expression(key)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ blackboard.erase_value(key_value)
+
+ return SUCCESS
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key]
diff --git a/addons/beehave/nodes/leaves/blackboard_has.gd b/addons/beehave/nodes/leaves/blackboard_has.gd
new file mode 100644
index 0000000..2ee5e92
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_has.gd
@@ -0,0 +1,23 @@
+@tool
+class_name BlackboardHasCondition extends ConditionLeaf
+
+## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist.
+## Returns [code]SUCCESS[/code] if blackboard has the specified key.
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+
+@onready var _key_expression: Expression = _parse_expression(key)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ return SUCCESS if blackboard.has_value(key_value) else FAILURE
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key]
diff --git a/addons/beehave/nodes/leaves/blackboard_set.gd b/addons/beehave/nodes/leaves/blackboard_set.gd
new file mode 100644
index 0000000..4f0ed9f
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_set.gd
@@ -0,0 +1,33 @@
+@tool
+class_name BlackboardSetAction extends ActionLeaf
+
+## Sets the specified key to the specified value.
+## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+## Expression representing a blackboard value to assign to the specified key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = ""
+
+@onready var _key_expression: Expression = _parse_expression(key)
+@onready var _value_expression: Expression = _parse_expression(value)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ var value_value: Variant = _value_expression.execute([], blackboard)
+
+ if _value_expression.has_execute_failed():
+ return FAILURE
+
+ blackboard.set_value(key_value, value_value)
+
+ return SUCCESS
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key, value]
diff --git a/addons/beehave/nodes/leaves/condition.gd b/addons/beehave/nodes/leaves/condition.gd
new file mode 100644
index 0000000..f4610b4
--- /dev/null
+++ b/addons/beehave/nodes/leaves/condition.gd
@@ -0,0 +1,12 @@
+@tool
+@icon("../../icons/condition.svg")
+class_name ConditionLeaf extends Leaf
+
+## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
+## a single simple condition. They should never return `RUNNING`.
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"ConditionLeaf")
+ return classes
diff --git a/addons/beehave/nodes/leaves/leaf.gd b/addons/beehave/nodes/leaves/leaf.gd
new file mode 100644
index 0000000..4946c7d
--- /dev/null
+++ b/addons/beehave/nodes/leaves/leaf.gd
@@ -0,0 +1,48 @@
+@tool
+@icon("../../icons/category_leaf.svg")
+class_name Leaf extends BeehaveNode
+
+## Base class for all leaf nodes of the tree.
+
+const EXPRESSION_PLACEHOLDER: String = "Insert an expression..."
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+
+ var children: Array[Node] = get_children()
+
+ if children.any(func(x): return x is BeehaveNode):
+ warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.")
+
+ for source in _get_expression_sources():
+ var error_text: String = _parse_expression(source).get_error_text()
+ if not error_text.is_empty():
+ warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text])
+
+ return warnings
+
+
+func _parse_expression(source: String) -> Expression:
+ var result: Expression = Expression.new()
+ var error: int = result.parse(source)
+
+ if not Engine.is_editor_hint() and error != OK:
+ push_error(
+ (
+ "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`"
+ % [source, result.get_error_text()]
+ )
+ )
+
+ return result
+
+
+func _get_expression_sources() -> Array[String]: # virtual
+ return []
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Leaf")
+ return classes
diff --git a/addons/beehave/plugin.cfg b/addons/beehave/plugin.cfg
new file mode 100644
index 0000000..f06defe
--- /dev/null
+++ b/addons/beehave/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Beehave"
+description="🐝 Behavior Tree addon for Godot Engine"
+author="bitbrain"
+version="2.8.2-dev"
+script="plugin.gd"
diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd
new file mode 100644
index 0000000..cda3b0d
--- /dev/null
+++ b/addons/beehave/plugin.gd
@@ -0,0 +1,26 @@
+@tool
+extends EditorPlugin
+
+const BeehaveEditorDebugger := preload("debug/debugger.gd")
+var editor_debugger: BeehaveEditorDebugger
+var frames: RefCounted
+
+
+func _init():
+ name = "BeehavePlugin"
+ add_autoload_singleton("BeehaveGlobalMetrics", "metrics/beehave_global_metrics.gd")
+ add_autoload_singleton("BeehaveGlobalDebugger", "debug/global_debugger.gd")
+ print("Beehave initialized!")
+
+
+func _enter_tree() -> void:
+ editor_debugger = BeehaveEditorDebugger.new()
+ if Engine.get_version_info().minor >= 2:
+ frames = preload("debug/new_frames.gd").new()
+ else:
+ frames = preload("debug/old_frames.gd").new()
+ add_debugger_plugin(editor_debugger)
+
+
+func _exit_tree() -> void:
+ remove_debugger_plugin(editor_debugger)
diff --git a/addons/beehave/utils/utils.gd b/addons/beehave/utils/utils.gd
new file mode 100644
index 0000000..5f51ce7
--- /dev/null
+++ b/addons/beehave/utils/utils.gd
@@ -0,0 +1,21 @@
+@tool
+
+
+static func get_plugin() -> EditorPlugin:
+ var tree: SceneTree = Engine.get_main_loop()
+ return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin")
+
+
+static func get_editor_scale() -> float:
+ var plugin := get_plugin()
+ if plugin:
+ return plugin.get_editor_interface().get_editor_scale()
+ return 1.0
+
+
+static func get_frames() -> RefCounted:
+ var plugin := get_plugin()
+ if plugin:
+ return plugin.frames
+ push_error("Can't find Beehave Plugin")
+ return null
diff --git a/autoload/database/database.gd b/autoload/database/database.gd
new file mode 100644
index 0000000..9d48022
--- /dev/null
+++ b/autoload/database/database.gd
@@ -0,0 +1,33 @@
+extends Node
+
+#不同族群单位之间的初始好感度
+#下面意思为测试族群1对2具有初始负面好感
+#相反2对1具有正面好感
+var init_favour:Dictionary={
+ #测试族群1
+ "test_1":{
+ #对测试族群2的初始好感度为-10
+ "test_1":20,
+ "test_2":-50,
+ "player":-20,
+ },
+ "test_2":{
+
+ "test_2":100,
+ "test_1":50,
+ "player":-20,
+
+ }
+}
+#获取初始好感度
+func get_init_favour(self_type:String,other_type:String):
+ if not init_favour.has(self_type):
+ return 0
+ var favour_dic=init_favour[self_type]
+ if not favour_dic.has(other_type):
+ return 0
+ else:
+ return favour_dic[other_type]
+
+
+ pass
diff --git a/autoload/database/database.tscn b/autoload/database/database.tscn
new file mode 100644
index 0000000..ec32be8
--- /dev/null
+++ b/autoload/database/database.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://ds0gdslpfq47i"]
+
+[ext_resource type="Script" path="res://autoload/database/database.gd" id="1_5ljwu"]
+
+[node name="database" type="Node"]
+script = ExtResource("1_5ljwu")
diff --git a/autoload/global/global.gd b/autoload/global/global.gd
new file mode 100644
index 0000000..a7988e3
--- /dev/null
+++ b/autoload/global/global.gd
@@ -0,0 +1,47 @@
+extends Node
+#保存ID对应角色实例的字典
+var unit_instance_dic:Dictionary={
+
+}
+#可交互物品/建筑的类型:Array字典
+var item_type_dic_arr:Dictionary={
+
+}
+#设置对象实例
+func set_unit_instance(id:String,instance:Node):
+ if unit_instance_dic.has(id):
+ var before_instance=unit_instance_dic[id]
+ if is_instance_valid(before_instance):
+ before_instance.queue_free()
+ unit_instance_dic[id]=instance
+#获取对象实例
+func get_unit_instance(id:String):
+ if unit_instance_dic.has(id) and is_instance_valid(unit_instance_dic[id]):
+ return unit_instance_dic[id]
+ else:
+ return null
+#单位好感度字典
+var unit_favour_dic:Dictionary={
+
+}
+#获取与对应单位的好感度,如果不存在则根据双方族群自动创建初始好感
+func get_unit_favour(self_id:String,other_id:String):
+
+ if not unit_favour_dic.has(self_id):
+ var favour=Database.get_init_favour(get_unit_instance(self_id).unit_type,get_unit_instance(other_id).unit_type)
+ unit_favour_dic[self_id]={other_id:favour}
+ return favour
+ elif not unit_favour_dic[self_id].has(other_id):
+ var favour=Database.get_init_favour(get_unit_instance(self_id).unit_type,get_unit_instance(other_id).unit_type)
+ unit_favour_dic[self_id][other_id]=favour
+ return favour
+ else:
+ return unit_favour_dic[self_id][other_id]
+ pass
+
+#设置好感度
+func set_unit_favour(self_id:String,other_id:String,favour:float):
+ if not unit_favour_dic.has(self_id):
+ unit_favour_dic[self_id]={other_id:favour}
+ else:
+ unit_favour_dic[self_id][other_id]=favour
diff --git a/autoload/global/global.tscn b/autoload/global/global.tscn
new file mode 100644
index 0000000..3a66e4d
--- /dev/null
+++ b/autoload/global/global.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://bbovfa26lay8e"]
+
+[ext_resource type="Script" path="res://autoload/global/global.gd" id="1_7m8su"]
+
+[node name="global" type="Node"]
+script = ExtResource("1_7m8su")
diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..9d8b7fa
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icon.svg.import b/icon.svg.import
new file mode 100644
index 0000000..dc342ea
--- /dev/null
+++ b/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://csk8u15wepd1w"
+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
diff --git a/project.godot b/project.godot
new file mode 100644
index 0000000..2981d1b
--- /dev/null
+++ b/project.godot
@@ -0,0 +1,50 @@
+; 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="模拟异世界"
+run/main_scene="res://scene/test/test_main.tscn"
+config/features=PackedStringArray("4.3", "Forward Plus")
+boot_splash/bg_color=Color(0, 0, 0, 0)
+config/icon="res://icon.svg"
+
+[autoload]
+
+BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd"
+BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd"
+Database="*res://autoload/database/database.tscn"
+Global="*res://autoload/global/global.tscn"
+
+[display]
+
+window/size/initial_position_type=2
+window/size/initial_screen=1
+
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/beehave/plugin.cfg")
+
+[input]
+
+mouse_left={
+"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(297, 21),"global_position":Vector2(306, 67),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
+]
+}
+mouse_right={
+"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":2,"position":Vector2(150, 28),"global_position":Vector2(159, 74),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
+]
+}
+
+[rendering]
+
+environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 0)
diff --git a/res/animation/other_character_default.tres b/res/animation/other_character_default.tres
new file mode 100644
index 0000000..2092e28
--- /dev/null
+++ b/res/animation/other_character_default.tres
@@ -0,0 +1,413 @@
+[gd_resource type="SpriteFrames" load_steps=53 format=3 uid="uid://jbsg1umeffu1"]
+
+[ext_resource type="Texture2D" uid="uid://b8mr80al21rka" path="res://TEST/mystic_woods_free_2.2/sprites/characters/player.png" id="1_ciqw4"]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_15036"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 432, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_l0qjt"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 432, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jv6ku"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 432, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_p5ts7"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fdekw"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_uc0b0"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_v0bwb"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_sfgu1"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_pkr8f"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 144, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_dehdu"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 288, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fs2e1"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 288, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_1swd0"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 288, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_0nfde"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 288, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7lq3f"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_22e3l"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7r7es"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_qla58"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j4fw1"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_efry1"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 0, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_8w6ce"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7lbf3"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_kguei"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_dwllq"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_25nxc"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_0erop"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 192, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3tqjp"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 336, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_u5img"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 336, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_p1dr4"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 336, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3judn"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 336, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_wm7vq"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j8ewr"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ubgqn"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_algwd"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_18b3y"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_wxhnb"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 48, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_cut56"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_41mkh"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ixxhs"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_in33l"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_vwt1n"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_w86to"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 240, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_67mfl"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 384, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lan5a"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 384, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_kwvch"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 384, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_5nl1s"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 384, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_k2iw0"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(0, 96, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_vbv3o"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(48, 96, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_df8uv"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(96, 96, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ts0ik"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(144, 96, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_oi8qt"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(192, 96, 48, 48)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_725dq"]
+atlas = ExtResource("1_ciqw4")
+region = Rect2(240, 96, 48, 48)
+
+[resource]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_15036")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_l0qjt")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_jv6ku")
+}],
+"loop": false,
+"name": &"dead",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_p5ts7")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_fdekw")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_uc0b0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_v0bwb")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_sfgu1")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_pkr8f")
+}],
+"loop": true,
+"name": &"down",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_dehdu")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_fs2e1")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_1swd0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_0nfde")
+}],
+"loop": false,
+"name": &"down_attack",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_7lq3f")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_22e3l")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_7r7es")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_qla58")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_j4fw1")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_efry1")
+}],
+"loop": true,
+"name": &"down_idle",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_8w6ce")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_7lbf3")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_kguei")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_dwllq")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_25nxc")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_0erop")
+}],
+"loop": true,
+"name": &"left_right",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_3tqjp")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_u5img")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_p1dr4")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_3judn")
+}],
+"loop": false,
+"name": &"left_right_attack",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_wm7vq")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_j8ewr")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ubgqn")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_algwd")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_18b3y")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_wxhnb")
+}],
+"loop": true,
+"name": &"left_right_idle",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_cut56")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_41mkh")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ixxhs")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_in33l")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_vwt1n")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_w86to")
+}],
+"loop": true,
+"name": &"up",
+"speed": 10.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_67mfl")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_lan5a")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_kwvch")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_5nl1s")
+}],
+"loop": false,
+"name": &"up_attack",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_k2iw0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_vbv3o")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_df8uv")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ts0ik")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_oi8qt")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_725dq")
+}],
+"loop": true,
+"name": &"up_idle",
+"speed": 10.0
+}]
diff --git a/res/image/test/tile.png b/res/image/test/tile.png
new file mode 100644
index 0000000..f6dd89f
Binary files /dev/null and b/res/image/test/tile.png differ
diff --git a/res/image/test/tile.png.import b/res/image/test/tile.png.import
new file mode 100644
index 0000000..a9499b9
--- /dev/null
+++ b/res/image/test/tile.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dxtu12mw7fsq8"
+path="res://.godot/imported/tile.png-9aeae6768d5b314413b30957805abe9e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://res/image/test/tile.png"
+dest_files=["res://.godot/imported/tile.png-9aeae6768d5b314413b30957805abe9e.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
diff --git a/scene/behavour_tree/action_leaf.gd b/scene/behavour_tree/action_leaf.gd
new file mode 100644
index 0000000..bb7b6a6
--- /dev/null
+++ b/scene/behavour_tree/action_leaf.gd
@@ -0,0 +1 @@
+extends ActionLeaf
diff --git a/scene/behavour_tree/await_time.gd b/scene/behavour_tree/await_time.gd
new file mode 100644
index 0000000..40343b6
--- /dev/null
+++ b/scene/behavour_tree/await_time.gd
@@ -0,0 +1,3 @@
+extends SequenceComposite
+##等待
+@export var await_time:float
diff --git a/scene/behavour_tree/beehave_tree.gd b/scene/behavour_tree/beehave_tree.gd
new file mode 100644
index 0000000..eedc09e
--- /dev/null
+++ b/scene/behavour_tree/beehave_tree.gd
@@ -0,0 +1 @@
+extends BeehaveTree
diff --git a/scene/behavour_tree/beehave_tree.tscn b/scene/behavour_tree/beehave_tree.tscn
new file mode 100644
index 0000000..ae0fc6b
--- /dev/null
+++ b/scene/behavour_tree/beehave_tree.tscn
@@ -0,0 +1,33 @@
+[gd_scene load_steps=7 format=3 uid="uid://w2jggqirpb3q"]
+
+[ext_resource type="Script" path="res://scene/behavour_tree/beehave_tree.gd" id="1_r1nve"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/selector.gd" id="2_hodda"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="3_1mypc"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/leaves/condition.gd" id="4_58d1f"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/leaves/action.gd" id="5_6ttu2"]
+[ext_resource type="Script" path="res://scene/behavour_tree/action_leaf.gd" id="5_woxrp"]
+
+[node name="BeehaveTree" type="Node" node_paths=PackedStringArray("blackboard")]
+script = ExtResource("1_r1nve")
+blackboard = NodePath("")
+
+[node name="SelectorComposite" type="Node" parent="."]
+script = ExtResource("2_hodda")
+
+[node name="SequenceComposite" type="Node" parent="SelectorComposite"]
+script = ExtResource("3_1mypc")
+
+[node name="ConditionLeaf" type="Node" parent="SelectorComposite/SequenceComposite"]
+script = ExtResource("4_58d1f")
+
+[node name="ActionLeaf" type="Node" parent="SelectorComposite/SequenceComposite"]
+script = ExtResource("5_woxrp")
+
+[node name="SequenceComposite2" type="Node" parent="SelectorComposite"]
+script = ExtResource("3_1mypc")
+
+[node name="ConditionLeaf" type="Node" parent="SelectorComposite/SequenceComposite2"]
+script = ExtResource("4_58d1f")
+
+[node name="ActionLeaf" type="Node" parent="SelectorComposite/SequenceComposite2"]
+script = ExtResource("5_6ttu2")
diff --git a/scene/behavour_tree/condition_accuse.gd b/scene/behavour_tree/condition_accuse.gd
new file mode 100644
index 0000000..19b1bc8
--- /dev/null
+++ b/scene/behavour_tree/condition_accuse.gd
@@ -0,0 +1,22 @@
+extends ConditionLeaf
+
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:UnitOther=actor
+ var all_bodys=unit.sense_area.get_overlapping_bodies()
+ for i in all_bodys:
+ if i is Unit:
+ var favour=Global.get_unit_favour(unit.unit_id,i.unit_id)
+ var res=get_res(favour,unit.get_hungry())
+ if res:
+ black_board.set_value("target_unit_id",i.unit_id)
+ return SUCCESS
+ return FAILURE
+ pass
+
+#返回true口角判定成功
+func get_res(favour:float,hungry:float=0)->bool:
+ if favour>0 or favour<-50:
+ return false
+ else:
+ return randf()<=(-favour/100)-hungry/300
diff --git a/scene/character/dead_scene_sprite/dead_scene.gd b/scene/character/dead_scene_sprite/dead_scene.gd
new file mode 100644
index 0000000..53ac13d
--- /dev/null
+++ b/scene/character/dead_scene_sprite/dead_scene.gd
@@ -0,0 +1,18 @@
+extends AnimatedSprite2D
+@onready var animation_player: AnimationPlayer = $AnimationPlayer
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ pass # Replace with function body.
+
+
+
+func _on_animation_finished() -> void:
+ animation_player.play("mddulutehide")
+ pass # Replace with function body.
+
+
+func _on_animation_player_animation_finished(anim_name: StringName) -> void:
+ queue_free()
+ pass # Replace with function body.
diff --git a/scene/character/dead_scene_sprite/dead_scene.tscn b/scene/character/dead_scene_sprite/dead_scene.tscn
new file mode 100644
index 0000000..41bda40
--- /dev/null
+++ b/scene/character/dead_scene_sprite/dead_scene.tscn
@@ -0,0 +1,51 @@
+[gd_scene load_steps=5 format=3 uid="uid://dey5n3rj0355t"]
+
+[ext_resource type="Script" path="res://scene/character/dead_scene_sprite/dead_scene.gd" id="1_ugaxh"]
+
+[sub_resource type="Animation" id="Animation_w6wlf"]
+resource_name = "modulutehide"
+length = 0.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_mdkhy"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_a2cak"]
+_data = {
+"RESET": SubResource("Animation_mdkhy"),
+"modulutehide": SubResource("Animation_w6wlf")
+}
+
+[node name="DeadScene" type="AnimatedSprite2D"]
+script = ExtResource("1_ugaxh")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_a2cak")
+}
+
+[connection signal="animation_finished" from="." to="." method="_on_animation_finished"]
+[connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_animation_player_animation_finished"]
diff --git a/scene/class/food.gd b/scene/class/food.gd
new file mode 100644
index 0000000..ebb667b
--- /dev/null
+++ b/scene/class/food.gd
@@ -0,0 +1,17 @@
+extends Area2D
+class_name Food
+
+var hp:float=100
+var hungry:float=100
+
+
+
+func _ready() -> void:
+
+ pass
+
+
+#被吃掉,比如如果要建立一个全局,可以用这个函数删掉
+func eated():
+
+ self.queue_free()
diff --git a/scene/test/action_accuse.gd b/scene/test/action_accuse.gd
new file mode 100644
index 0000000..cfc22be
--- /dev/null
+++ b/scene/test/action_accuse.gd
@@ -0,0 +1,10 @@
+extends ActionLeaf
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:UnitOther=actor
+ if black_board.has_value("target_unit_id"):
+ unit.accuse(black_board.get_value("target_unit_id"))
+ black_board.erase_value("target_unit_id")
+ return SUCCESS
+ else:
+ return FAILURE
diff --git a/scene/test/action_accuse.tscn b/scene/test/action_accuse.tscn
new file mode 100644
index 0000000..83c215b
--- /dev/null
+++ b/scene/test/action_accuse.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://c3pouwk6mbovv"]
+
+[ext_resource type="Script" path="res://scene/test/action_accuse.gd" id="1_1jbpf"]
+
+[node name="action_accuse" type="Node"]
+script = ExtResource("1_1jbpf")
diff --git a/scene/test/action_attack.gd b/scene/test/action_attack.gd
new file mode 100644
index 0000000..8f15b95
--- /dev/null
+++ b/scene/test/action_attack.gd
@@ -0,0 +1,17 @@
+extends ActionLeaf
+
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if unit.is_attacking():
+ var target_unit:Unit=Global.get_unit_instance(black_board.get_value("target_unit_id"))
+ unit.set_target(target_unit.global_position)
+ return RUNNING
+ if unit.is_attack_finished():
+ black_board.erase_value("target_unit_id")
+ return SUCCESS
+ if black_board.has_value("target_unit_id") and not unit.is_attacking():
+ unit.attack()
+ var target_unit:Unit=Global.get_unit_instance(black_board.get_value("target_unit_id"))
+ unit.set_target(target_unit.global_position)
+ return RUNNING
diff --git a/scene/test/action_attack.tscn b/scene/test/action_attack.tscn
new file mode 100644
index 0000000..639b2ae
--- /dev/null
+++ b/scene/test/action_attack.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://bcr5oa32fvxwa"]
+
+[ext_resource type="Script" path="res://scene/test/action_attack.gd" id="1_ty0vr"]
+
+[node name="action_attack" type="Node"]
+script = ExtResource("1_ty0vr")
diff --git a/scene/test/action_await_time.gd b/scene/test/action_await_time.gd
new file mode 100644
index 0000000..b24ec29
--- /dev/null
+++ b/scene/test/action_await_time.gd
@@ -0,0 +1,16 @@
+extends ActionLeaf
+
+@onready var timer: Timer = $Timer
+var is_self_started:bool=false
+func tick(actor:Node,blackboard:Blackboard):
+ if not is_self_started and timer.is_stopped():
+ timer.start(get_parent().await_time)
+ is_self_started=true
+ return RUNNING
+ elif is_self_started and not timer.is_stopped():
+ return RUNNING
+ elif is_self_started and timer.is_stopped():
+ is_self_started=false
+ return SUCCESS
+ else:
+ return FAILED
diff --git a/scene/test/action_eat.gd b/scene/test/action_eat.gd
new file mode 100644
index 0000000..2b1dfd4
--- /dev/null
+++ b/scene/test/action_eat.gd
@@ -0,0 +1,12 @@
+extends ActionLeaf
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if black_board.has_value("target"):
+
+ var target=black_board.get_value("target")
+ if target is Food and unit.is_unit_instance_in_touch_area(target) :
+ unit.eat(target)
+ return SUCCESS
+ return FAILURE
+ return FAILURE
diff --git a/scene/test/action_eat.tscn b/scene/test/action_eat.tscn
new file mode 100644
index 0000000..3e78511
--- /dev/null
+++ b/scene/test/action_eat.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://k7sldgn2joe0"]
+
+[ext_resource type="Script" path="res://scene/test/action_eat.gd" id="1_ahrfw"]
+
+[node name="action_eat" type="Node"]
+script = ExtResource("1_ahrfw")
diff --git a/scene/test/action_move_to_target.gd b/scene/test/action_move_to_target.gd
new file mode 100644
index 0000000..0728da6
--- /dev/null
+++ b/scene/test/action_move_to_target.gd
@@ -0,0 +1,64 @@
+extends ActionLeaf
+
+#执行成功后是否抹除黑板目标数据
+@export var should_erase_target:bool=false
+
+#用于移动到特定单位的可交互范围内
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if black_board.has_value("target"):
+ var target=black_board.get_value("target")
+ if not is_instance_valid(target) or not target is Node2D:
+ black_board.erase_value("target")
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ unit.stop_move()
+ return FAILURE
+ #获取要移动到的目标
+ if unit.is_unit_instance_in_touch_area(target):
+ if should_erase_target:
+ black_board.erase_value("target")
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ unit.stop_move()
+ return SUCCESS
+ unit.set_target_pos(target.global_position)
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up")
+ 1:
+ unit.play_animation("down")
+ 2:
+ unit.play_animation("left_right")
+ 3:
+ unit.play_animation("left_right")
+ return RUNNING
+ else:
+ black_board.erase_value("target")
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ unit.stop_move()
+ return FAILURE
+
+
diff --git a/scene/test/action_move_to_target.tscn b/scene/test/action_move_to_target.tscn
new file mode 100644
index 0000000..c61e0ac
--- /dev/null
+++ b/scene/test/action_move_to_target.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://b65amfq474sv7"]
+
+[ext_resource type="Script" path="res://scene/test/action_move_to_target.gd" id="1_2p57o"]
+
+[node name="action_move_to_target" type="Node"]
+script = ExtResource("1_2p57o")
diff --git a/scene/test/action_move_to_unit.gd b/scene/test/action_move_to_unit.gd
new file mode 100644
index 0000000..bb33a19
--- /dev/null
+++ b/scene/test/action_move_to_unit.gd
@@ -0,0 +1,58 @@
+extends ActionLeaf
+#执行成功后是否抹除黑板目标数据
+@export var should_erase_target_id:bool=false
+
+#用于移动到特定单位的可交互范围内
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if black_board.has_value("target_unit_id"):
+ var target_id:String=black_board.get_value("target_unit_id")
+ if Global.get_unit_instance(target_id)==null:
+ #移除目标
+ black_board.erase_value("target_unit_id")
+ return FAILURE
+ #获取要移动到的目标实例
+ var target_instance:Unit=Global.get_unit_instance(target_id)
+ if not unit.is_unit_instance_in_touch_area(target_instance):
+ unit.set_target_pos(target_instance.global_position)
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up")
+ 1:
+ unit.play_animation("down")
+ 2:
+ unit.play_animation("left_right")
+ 3:
+ unit.play_animation("left_right")
+ return RUNNING
+ else:
+ unit.stop_move()
+ if should_erase_target_id:
+ black_board.erase_value("target_unit_id")
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ return SUCCESS
+ else:
+ black_board.erase_value("target_unit_id")
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ return FAILURE
+
+ pass
+
+
+ pass
diff --git a/scene/test/action_move_to_unit.tscn b/scene/test/action_move_to_unit.tscn
new file mode 100644
index 0000000..e7bbf96
--- /dev/null
+++ b/scene/test/action_move_to_unit.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://cm7nelq3tbye6"]
+
+[ext_resource type="Script" path="res://scene/test/action_move_to_unit.gd" id="1_pthy4"]
+
+[node name="action_move_to_unit" type="Node"]
+script = ExtResource("1_pthy4")
diff --git a/scene/test/action_set_rand_pos.gd b/scene/test/action_set_rand_pos.gd
new file mode 100644
index 0000000..a094bd8
--- /dev/null
+++ b/scene/test/action_set_rand_pos.gd
@@ -0,0 +1,19 @@
+extends ActionLeaf
+##随机进行的距离最大值
+@export var rand_length_max:float=100
+##随机进行的距离最小值
+@export var rand_length_min:float=50
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if unit.get_hungry()>50:
+ rand_length_max=float(unit.get_hungry()-50)/50*2000
+ else:
+ rand_length_max=100
+ var now_pos=actor.global_position
+ #随机距离
+ var rand_distance=randf_range(rand_length_min,rand_length_max)
+ #随机方向
+ var rand_dir:Vector2=Vector2(2*randf()-1,2*randf()-1).normalized()
+ var target_pos=now_pos+rand_dir*rand_distance
+ black_board.set_value("run_pos",target_pos)
+ return SUCCESS
diff --git a/scene/test/action_set_rand_pos.tscn b/scene/test/action_set_rand_pos.tscn
new file mode 100644
index 0000000..e7658b5
--- /dev/null
+++ b/scene/test/action_set_rand_pos.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://ldjrnwapxefe"]
+
+[ext_resource type="Script" path="res://scene/test/action_set_rand_pos.gd" id="1_5ly3l"]
+
+[node name="action_set_rand_pos" type="Node"]
+script = ExtResource("1_5ly3l")
diff --git a/scene/test/await_time.tscn b/scene/test/await_time.tscn
new file mode 100644
index 0000000..286a8b6
--- /dev/null
+++ b/scene/test/await_time.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=3 format=3 uid="uid://jkwn820drv43"]
+
+[ext_resource type="Script" path="res://scene/behavour_tree/await_time.gd" id="1_g7nf2"]
+[ext_resource type="Script" path="res://scene/test/action_await_time.gd" id="2_1ek7g"]
+
+[node name="await_time" type="Node"]
+script = ExtResource("1_g7nf2")
+
+[node name="action_await_time" type="Node" parent="."]
+script = ExtResource("2_1ek7g")
+
+[node name="Timer" type="Timer" parent="action_await_time"]
+one_shot = true
diff --git a/scene/test/character.gd b/scene/test/character.gd
new file mode 100644
index 0000000..14df247
--- /dev/null
+++ b/scene/test/character.gd
@@ -0,0 +1,293 @@
+extends CharacterBody2D
+class_name Unit
+#这里暂时使用中文键debug,方便调试,后会更改
+var action_emoji:Dictionary={
+ "生气":"🤯",
+ "愤怒":"🤬",
+ "指责":"👈🤯",
+ "伤害":"😡🔪",
+ "友好":"🥰",
+ "被指责":"😟",
+ "受伤":"😭💔",
+ "饥饿":"🍽︎",
+ "好吃":"😋"
+}
+@export var unit_data:Dictionary={
+ "unit_id":"default",
+ "unit_type":"default",
+ #动画资源
+ "sprite_frames":"res://res/animation/other_character_default.tres",
+ #动画偏移
+ "sprite_offset":Vector2(0,-80),
+ #动画缩放
+ "sprite_scale":Vector2(5,5),
+ #基础属性
+ "hp_base":100,
+ "atk_base":10,
+ "speed_base":1,
+
+
+ #攻击方式
+ "attack_type":"base",
+
+ #下面是临时生成的动态数据
+
+ #状态值
+ #饥饿,疲劳,怒气,紧张,恐慌,压力
+ "hungry":0,
+ "fatigue":0,
+ "rage":0,
+ "tensity":0,
+
+ "panic":0,
+ #压力
+ "pressure":0,
+
+ "hp_max":100,
+ "hp":100,
+
+}
+#允许的状态队列
+var state_value_array:Array=["hungry","fatigue","rage","tensity","panic","pressure","hp_max","hp"]
+#设置状态值
+func get_state_value(state_value_name:String):
+ if state_value_name in state_value_array:
+ return unit_data[state_value_name]
+ else:
+ return null
+#状态值改变时发出信号
+signal state_value_changed(state_value_name:String,value)
+#设置状态值
+func set_state_value(state_value_name:String,value)->bool:
+ if state_value_name in state_value_array:
+ unit_data[state_value_name]=value
+ state_value_changed.emit(state_value_name,value)
+ return true
+ else:
+ return false
+#获取单位ID
+func get_unit_id():
+ return unit_data["unit_id"]
+#获取单位类型
+func get_unit_type():
+ return unit_data["unit_type"]
+
+#快捷获取
+func get_hungry():
+ return get_state_value("hungry")
+func set_hungry(value):
+ return set_state_value("hungry",value)
+
+
+
+
+
+##单位的独特ID
+@export var unit_id:String="default"
+##单位所属族群
+@export var unit_type:String="default"
+##每秒触发的timer,用于计算饥饿值积累等
+var second_timer:Timer
+@export var animation:AnimatedSprite2D
+const unit_speed = 300.0
+const JUMP_VELOCITY = -400.0
+@export var agent: NavigationAgent2D
+@export var state_machine: StateMachine
+#旋转中心轴
+@export var rotate: Node2D
+
+##与其他单位交互的范围,目标进入此范围内才进行交互
+@export var touch_area:Area2D
+##感知范围
+@export var sense_area:Area2D
+
+@export var attack_area:Area2D
+
+
+
+
+
+
+#var hungry:float=0:
+ #set(val):
+ #hungry=val
+ #if %hungry!=null:
+ #%hungry.text="饥饿值:"+str(val)
+func _ready() -> void:
+ Global.set_unit_instance(unit_id,self)
+ agent.max_speed=unit_speed
+ agent.velocity_computed.connect(safe_speed)
+ if animation!=null:
+ animation.frame_changed.connect(frame_changed)
+ second_timer=Timer.new()
+ second_timer.autostart=true
+ second_timer.one_shot=false
+ second_timer.wait_time=1
+ second_timer.timeout.connect(second_timer_time_out)
+ add_child(second_timer)
+
+ var new_sprite_animation:AnimatedSprite2D=AnimatedSprite2D.new()
+ new_sprite_animation.sprite_frames=load(unit_data["sprite_frames"])
+
+ add_child(new_sprite_animation)
+ new_sprite_animation.position=unit_data["sprite_offset"]
+ new_sprite_animation.scale=unit_data["sprite_scale"]
+ animation=new_sprite_animation
+
+ #state_machine.launch()
+func set_target_pos(target:Vector2):
+ agent.target_position=target
+
+func _physics_process(delta: float) -> void:
+ if agent!=null and not is_move_finished():
+ var current_pos:Vector2=global_position
+ var next_pos:Vector2=agent.get_next_path_position()
+ var new_velocity=current_pos.direction_to(next_pos)*unit_speed
+ if agent.avoidance_enabled:
+ agent.set_velocity(new_velocity)
+ rotate.rotation=velocity.angle()
+ else:
+ safe_speed(new_velocity)
+ rotate.rotation=velocity.angle()
+ if is_move_finished():
+ velocity=Vector2.ZERO
+ else:
+ cache_velicity=velocity
+ if animation!=null:
+ if velocity.x>0:
+ animation.flip_h=false
+ if velocity.x<0:
+ animation.flip_h=true
+
+ move_and_slide()
+
+func safe_speed(safe_velocity):
+ velocity=safe_velocity
+
+func stop_move():
+ set_target_pos(self.global_position)
+
+func is_move_finished():
+ #return agent.is_target_reached()
+ return agent.is_navigation_finished()
+
+func sent_message(type:String,value):
+ state_machine.send_message(type,value)
+
+
+#判断unit实例在不在交互范围内
+func is_unit_instance_in_touch_area(instance:Node):
+ if touch_area==null:
+ return false
+ else:
+ if instance is PhysicsBody2D:
+ return instance in touch_area.get_overlapping_bodies()
+ if instance is Area2D:
+ return instance in touch_area.get_overlapping_areas()
+#指责(口角)
+func accuse(unit_id:String):
+ show_action("指责")
+ var instance=Global.get_unit_instance(unit_id)
+ if instance is UnitOther:
+ instance.accused(self.unit_id)
+ pass
+#被指责,调用
+func accused(by_unit_id:String):
+ show_action("被指责")
+ Global.set_unit_favour(unit_id,by_unit_id,Global.get_unit_favour(unit_id,by_unit_id)-10)
+ pass
+@export var attack_frames:int=2
+
+
+#攻击
+func attack():
+ show_action("伤害")
+ match get_dir():
+ 0:
+ play_animation("up_attack")
+ 1:
+ play_animation("down_attack")
+ 2:
+ play_animation("left_right_attack")
+ 3:
+ play_animation("left_right_attack")
+ pass
+
+func frame_changed():
+ if animation.animation in [&"up_attack",&"down_attack",&"left_right_attack"] and animation.frame==attack_frames:
+ use_attack_damage()
+func is_attack_finished():
+ if animation.animation in [&"up_attack",&"down_attack",&"left_right_attack"] and not animation.is_playing():
+ return true
+ else:
+ return false
+func is_attacking():
+ return animation.animation in [&"up_attack",&"down_attack",&"left_right_attack"] and animation.is_playing()
+#在固定帧使用攻击
+func use_attack_damage():
+
+ for i in attack_area.get_overlapping_bodies():
+
+ if i is Unit and i!=self:
+ i.attacked(unit_id)
+
+ pass
+func attacked(by_unit_id:String):
+ show_action("受伤")
+ Global.set_unit_favour(unit_id,by_unit_id,Global.get_unit_favour(unit_id,by_unit_id)-20)
+
+ pass
+func set_target(global_pos:Vector2):
+ cache_velicity=global_pos-self.global_position
+ rotate.look_at(global_pos)
+ if cache_velicity.x>0:
+ animation.flip_h=false
+ if cache_velicity.x<0:
+ animation.flip_h=true
+
+ pass
+func show_action(type:String):
+ print(type)
+ if action_emoji.has(type):
+ %action_show.text=action_emoji[type]
+ if %action_animation.is_playing():
+ %action_animation.stop()
+ %action_animation.play("show")
+
+ pass
+func play_animation(animation_name:String):
+ if animation !=null:
+ animation.play(animation_name)
+
+ pass
+var cache_velicity:Vector2=Vector2(1,0)
+#0上,1下,2左,3右
+func get_dir()->int:
+ if abs(cache_velicity.y)>abs(cache_velicity.x):
+
+ if cache_velicity.y>0:
+ return 1
+
+ else:
+ return 0
+
+ else:
+ if cache_velicity.x>0:
+ return 3
+
+ else:
+ return 2
+ pass
+ pass
+
+func second_timer_time_out():
+
+ set_hungry(clamp(get_hungry()+10,0,100))
+
+ pass
+func eat(food:Food):
+ print("吃")
+ show_action("好吃")
+ set_hungry(clamp(get_hungry()-food.hungry,0,100))
+ food.eated()
+ pass
diff --git a/scene/test/character.tscn b/scene/test/character.tscn
new file mode 100644
index 0000000..4f194f2
--- /dev/null
+++ b/scene/test/character.tscn
@@ -0,0 +1,136 @@
+[gd_scene load_steps=15 format=3 uid="uid://cf2g2urxaukxb"]
+
+[ext_resource type="Script" path="res://scene/test/self_character.gd" id="1_d0trv"]
+[ext_resource type="Script" path="res://scene/test/state_machine.gd" id="2_v5m4x"]
+[ext_resource type="Script" path="res://scene/test/idle.gd" id="3_gcdcj"]
+[ext_resource type="Script" path="res://scene/test/run.gd" id="4_c7f2w"]
+[ext_resource type="Texture2D" uid="uid://csk8u15wepd1w" path="res://icon.svg" id="5_lkgch"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_740ny"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_7uxj5"]
+size = Vector2(41, 34)
+
+[sub_resource type="Animation" id="Animation_tov45"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("action_show:visible")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+
+[sub_resource type="Animation" id="Animation_8eva7"]
+resource_name = "show"
+length = 0.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("action_show:visible")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.1, 0.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 1,
+"values": [false, true, false]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_a6wya"]
+_data = {
+"RESET": SubResource("Animation_tov45"),
+"show": SubResource("Animation_8eva7")
+}
+
+[sub_resource type="Gradient" id="Gradient_k76nm"]
+colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_akajp"]
+gradient = SubResource("Gradient_k76nm")
+
+[sub_resource type="Gradient" id="Gradient_vdy2i"]
+colors = PackedColorArray(0, 1, 0, 1, 0, 1, 0, 1)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_jp0r7"]
+gradient = SubResource("Gradient_vdy2i")
+
+[node name="CharacterBody2D" type="CharacterBody2D" node_paths=PackedStringArray("agent", "state_machine", "rotate")]
+script = ExtResource("1_d0trv")
+agent = NodePath("agent")
+state_machine = NodePath("state_machine")
+rotate = NodePath("rotate")
+
+[node name="agent" type="NavigationAgent2D" parent="."]
+path_postprocessing = 1
+avoidance_enabled = true
+radius = 20.0
+debug_enabled = true
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position_smoothing_enabled = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_740ny")
+
+[node name="state_machine" type="Node" parent="."]
+script = ExtResource("2_v5m4x")
+init_state = NodePath("idle")
+owner_player = NodePath("..")
+
+[node name="idle" type="Node" parent="state_machine"]
+script = ExtResource("3_gcdcj")
+
+[node name="run" type="Node" parent="state_machine"]
+script = ExtResource("4_c7f2w")
+
+[node name="Node3" type="Node" parent="state_machine"]
+
+[node name="rotate" type="Node2D" parent="."]
+
+[node name="attack_area" type="Area2D" parent="rotate"]
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="rotate/attack_area"]
+position = Vector2(20.5, 0)
+shape = SubResource("RectangleShape2D_7uxj5")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+visible = false
+texture = ExtResource("5_lkgch")
+
+[node name="action_show" type="Label" parent="."]
+unique_name_in_owner = true
+visible = false
+z_index = 1
+offset_left = -40.0
+offset_top = -75.0
+offset_right = 39.0
+offset_bottom = -6.0
+theme_override_font_sizes/font_size = 50
+text = "🤬"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="action_animation" type="AnimationPlayer" parent="."]
+unique_name_in_owner = true
+libraries = {
+"": SubResource("AnimationLibrary_a6wya")
+}
+
+[node name="hp" type="TextureProgressBar" parent="."]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.580392)
+offset_left = -127.0
+offset_top = -260.0
+offset_right = 129.0
+offset_bottom = -220.0
+nine_patch_stretch = true
+texture_under = SubResource("GradientTexture1D_akajp")
+texture_progress = SubResource("GradientTexture1D_jp0r7")
+
+[connection signal="state_value_changed" from="." to="." method="_on_state_value_changed"]
diff --git a/scene/test/condition_accuse.tscn b/scene/test/condition_accuse.tscn
new file mode 100644
index 0000000..25840e3
--- /dev/null
+++ b/scene/test/condition_accuse.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://hgsb64hg78m5"]
+
+[ext_resource type="Script" path="res://scene/behavour_tree/condition_accuse.gd" id="1_nvtrg"]
+
+[node name="condition_accuse" type="Node"]
+script = ExtResource("1_nvtrg")
diff --git a/scene/test/condition_attack.gd b/scene/test/condition_attack.gd
new file mode 100644
index 0000000..945a649
--- /dev/null
+++ b/scene/test/condition_attack.gd
@@ -0,0 +1,20 @@
+extends ConditionLeaf
+func tick(actor:Node,black_board:Blackboard):
+ var unit:UnitOther=actor
+ var all_bodys=unit.sense_area.get_overlapping_bodies()
+ for i in all_bodys:
+ if i is Unit:
+ var favour=Global.get_unit_favour(unit.unit_id,i.unit_id)
+ var res=get_res(favour,unit.get_hungry())
+ if res:
+ black_board.set_value("target_unit_id",i.unit_id)
+ return SUCCESS
+ return FAILURE
+ pass
+
+#返回true攻击判定成功
+func get_res(favour:float,hungry:float=0)->bool:
+ if favour>-50:
+ return false
+ else:
+ return randf()<=(-favour/100)-hungry/300
diff --git a/scene/test/condition_attack.tscn b/scene/test/condition_attack.tscn
new file mode 100644
index 0000000..1b30475
--- /dev/null
+++ b/scene/test/condition_attack.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://qrl0pc72e3q6"]
+
+[ext_resource type="Script" path="res://scene/test/condition_attack.gd" id="1_f0v72"]
+
+[node name="condition_attack" type="Node"]
+script = ExtResource("1_f0v72")
diff --git a/scene/test/condition_hungry.gd b/scene/test/condition_hungry.gd
new file mode 100644
index 0000000..63e0236
--- /dev/null
+++ b/scene/test/condition_hungry.gd
@@ -0,0 +1,17 @@
+extends ConditionLeaf
+
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:UnitOther=actor
+ if unit.get_hungry()<=50:
+ return FAILURE
+ if randf()>float(unit.get_hungry()-50)/50:
+ return FAILURE
+ unit.show_action("饥饿")
+ var all_bodys=unit.sense_area.get_overlapping_areas()
+ for i in all_bodys:
+ if i is Food:
+ black_board.set_value("target",i)
+ return SUCCESS
+ return FAILURE
+ pass
diff --git a/scene/test/condition_hungry.tscn b/scene/test/condition_hungry.tscn
new file mode 100644
index 0000000..0e95547
--- /dev/null
+++ b/scene/test/condition_hungry.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://dukqa6ha8aeic"]
+
+[ext_resource type="Script" path="res://scene/test/condition_hungry.gd" id="1_7gs6t"]
+
+[node name="condition_hungry" type="Node"]
+script = ExtResource("1_7gs6t")
diff --git a/scene/test/food.tscn b/scene/test/food.tscn
new file mode 100644
index 0000000..2310944
--- /dev/null
+++ b/scene/test/food.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=3 format=3 uid="uid://b4mawovxv402a"]
+
+[ext_resource type="Script" path="res://scene/class/food.gd" id="1_3mwrm"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_q10t5"]
+radius = 55.5428
+
+[node name="food" type="Area2D"]
+script = ExtResource("1_3mwrm")
+
+[node name="Label" type="Label" parent="."]
+offset_left = -71.0
+offset_top = -107.0
+offset_right = 66.0
+offset_bottom = 30.0
+theme_override_font_sizes/font_size = 100
+text = "🍱"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2(-3, -40)
+shape = SubResource("CircleShape2D_q10t5")
diff --git a/scene/test/idle.gd b/scene/test/idle.gd
new file mode 100644
index 0000000..a478a19
--- /dev/null
+++ b/scene/test/idle.gd
@@ -0,0 +1,29 @@
+extends State
+
+
+func enter_state(n):
+ pass
+func update_state(delta):
+
+ pass
+func update_state_phy(delta):
+ match get_player().get_dir():
+ 0:
+ get_player().play_animation("up_idle")
+ 1:
+ get_player().play_animation("down_idle")
+ 2:
+ get_player().play_animation("left_right_idle")
+ 3:
+ get_player().play_animation("left_right_idle")
+ pass
+func exit_state():
+ pass
+
+func process_message(type:String,n):
+ match type:
+ "move":
+ get_player().set_target_pos(n)
+ change_to_state("run")
+
+ pass
diff --git a/scene/test/other_character.gd b/scene/test/other_character.gd
new file mode 100644
index 0000000..2c893b3
--- /dev/null
+++ b/scene/test/other_character.gd
@@ -0,0 +1,27 @@
+extends Unit
+#除了玩家外的单位
+class_name UnitOther
+
+
+
+func _on_sense_area_body_entered(body: Node2D) -> void:
+ if body is Unit:
+ #自动初始化好感度
+ Global.get_unit_favour(unit_id,body.unit_id)
+ pass # Replace with function body.
+
+func _ready() -> void:
+ super._ready()
+ %hp.max_value=unit_data["hp_max"]
+ %hp.value=unit_data["hp"]
+
+
+func _on_state_value_changed(state_value_name: String, value: Variant) -> void:
+ match state_value_name:
+ "hungry":
+ %hungry.text="饥饿值:"+str(value)
+ "hp":
+ %hp.value=value
+ "hp_max":
+ %hp.max_value=value
+ pass # Replace with function body.
diff --git a/scene/test/other_character.tscn b/scene/test/other_character.tscn
new file mode 100644
index 0000000..7799e01
--- /dev/null
+++ b/scene/test/other_character.tscn
@@ -0,0 +1,214 @@
+[gd_scene load_steps=28 format=3 uid="uid://dl1axae8yeeeh"]
+
+[ext_resource type="Script" path="res://scene/test/other_character.gd" id="1_6f8fy"]
+[ext_resource type="Texture2D" uid="uid://csk8u15wepd1w" path="res://icon.svg" id="2_tbr4x"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="2_twyt5"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/selector.gd" id="3_0pv7j"]
+[ext_resource type="PackedScene" uid="uid://ldjrnwapxefe" path="res://scene/test/action_set_rand_pos.tscn" id="3_a6loo"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="3_gehdk"]
+[ext_resource type="PackedScene" uid="uid://jkwn820drv43" path="res://scene/test/await_time.tscn" id="4_hl4be"]
+[ext_resource type="PackedScene" uid="uid://dg0gnl5n62gq6" path="res://scene/test/run_await.tscn" id="4_hydke"]
+[ext_resource type="PackedScene" uid="uid://hgsb64hg78m5" path="res://scene/test/condition_accuse.tscn" id="5_tomxt"]
+[ext_resource type="PackedScene" uid="uid://dukqa6ha8aeic" path="res://scene/test/condition_hungry.tscn" id="6_qkap5"]
+[ext_resource type="PackedScene" uid="uid://cm7nelq3tbye6" path="res://scene/test/action_move_to_unit.tscn" id="6_wkm0p"]
+[ext_resource type="PackedScene" uid="uid://b65amfq474sv7" path="res://scene/test/action_move_to_target.tscn" id="7_phvf1"]
+[ext_resource type="PackedScene" uid="uid://c3pouwk6mbovv" path="res://scene/test/action_accuse.tscn" id="7_wxwf6"]
+[ext_resource type="PackedScene" uid="uid://k7sldgn2joe0" path="res://scene/test/action_eat.tscn" id="8_xfqbv"]
+[ext_resource type="PackedScene" uid="uid://qrl0pc72e3q6" path="res://scene/test/condition_attack.tscn" id="10_85ndl"]
+[ext_resource type="PackedScene" uid="uid://bcr5oa32fvxwa" path="res://scene/test/action_attack.tscn" id="11_7tlfn"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_s46hi"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_lphpm"]
+size = Vector2(121, 136)
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_fp36j"]
+radius = 104.12
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_0d77h"]
+radius = 472.662
+
+[sub_resource type="Animation" id="Animation_tov45"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("action_show:visible")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+
+[sub_resource type="Animation" id="Animation_8eva7"]
+resource_name = "show"
+length = 0.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("action_show:visible")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.1, 0.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 1,
+"values": [false, true, false]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_a6wya"]
+_data = {
+"RESET": SubResource("Animation_tov45"),
+"show": SubResource("Animation_8eva7")
+}
+
+[sub_resource type="Gradient" id="Gradient_uhodq"]
+colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_ioncu"]
+gradient = SubResource("Gradient_uhodq")
+
+[sub_resource type="Gradient" id="Gradient_qd3pc"]
+colors = PackedColorArray(0, 1, 0, 1, 0, 1, 0, 1)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_6t72h"]
+gradient = SubResource("Gradient_qd3pc")
+
+[node name="other_character" type="CharacterBody2D" node_paths=PackedStringArray("agent", "rotate", "touch_area", "sense_area", "attack_area")]
+script = ExtResource("1_6f8fy")
+agent = NodePath("agent")
+rotate = NodePath("rotate")
+touch_area = NodePath("touch_area")
+sense_area = NodePath("sense_area")
+attack_area = NodePath("rotate/attack_area")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+visible = false
+texture = ExtResource("2_tbr4x")
+
+[node name="agent" type="NavigationAgent2D" parent="."]
+path_postprocessing = 1
+avoidance_enabled = true
+radius = 20.0
+debug_enabled = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_s46hi")
+
+[node name="rotate" type="Node2D" parent="."]
+
+[node name="attack_area" type="Area2D" parent="rotate"]
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="rotate/attack_area"]
+position = Vector2(60.5, 3)
+shape = SubResource("RectangleShape2D_lphpm")
+
+[node name="BeehaveTree" type="Node" parent="." node_paths=PackedStringArray("blackboard", "actor")]
+script = ExtResource("2_twyt5")
+blackboard = NodePath("@Node@37976")
+actor = NodePath("..")
+
+[node name="SelectorComposite" type="Node" parent="BeehaveTree"]
+script = ExtResource("3_0pv7j")
+
+[node name="eat" type="Node" parent="BeehaveTree/SelectorComposite"]
+script = ExtResource("3_gehdk")
+
+[node name="condition_hungry" parent="BeehaveTree/SelectorComposite/eat" instance=ExtResource("6_qkap5")]
+
+[node name="action_move_to_target" parent="BeehaveTree/SelectorComposite/eat" instance=ExtResource("7_phvf1")]
+
+[node name="action_eat" parent="BeehaveTree/SelectorComposite/eat" instance=ExtResource("8_xfqbv")]
+
+[node name="accuse" type="Node" parent="BeehaveTree/SelectorComposite"]
+script = ExtResource("3_gehdk")
+
+[node name="condition_accuse" parent="BeehaveTree/SelectorComposite/accuse" instance=ExtResource("5_tomxt")]
+
+[node name="action_move_to_unit" parent="BeehaveTree/SelectorComposite/accuse" instance=ExtResource("6_wkm0p")]
+
+[node name="action_accuse" parent="BeehaveTree/SelectorComposite/accuse" instance=ExtResource("7_wxwf6")]
+
+[node name="await_time" parent="BeehaveTree/SelectorComposite/accuse" instance=ExtResource("4_hl4be")]
+await_time = 2.0
+
+[node name="attack" type="Node" parent="BeehaveTree/SelectorComposite"]
+script = ExtResource("3_gehdk")
+
+[node name="condition_attack" parent="BeehaveTree/SelectorComposite/attack" instance=ExtResource("10_85ndl")]
+
+[node name="action_move_to_unit" parent="BeehaveTree/SelectorComposite/attack" instance=ExtResource("6_wkm0p")]
+
+[node name="action_attack" parent="BeehaveTree/SelectorComposite/attack" instance=ExtResource("11_7tlfn")]
+
+[node name="await_time" parent="BeehaveTree/SelectorComposite/attack" instance=ExtResource("4_hl4be")]
+await_time = 1.0
+
+[node name="rand_walk" type="Node" parent="BeehaveTree/SelectorComposite"]
+script = ExtResource("3_gehdk")
+
+[node name="await_time" parent="BeehaveTree/SelectorComposite/rand_walk" instance=ExtResource("4_hl4be")]
+await_time = 2.0
+
+[node name="action_set_rand_pos" parent="BeehaveTree/SelectorComposite/rand_walk" instance=ExtResource("3_a6loo")]
+
+[node name="run_await" parent="BeehaveTree/SelectorComposite/rand_walk" instance=ExtResource("4_hydke")]
+
+[node name="touch_area" type="Area2D" parent="."]
+unique_name_in_owner = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="touch_area"]
+shape = SubResource("CircleShape2D_fp36j")
+
+[node name="sense_area" type="Area2D" parent="."]
+unique_name_in_owner = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="sense_area"]
+shape = SubResource("CircleShape2D_0d77h")
+
+[node name="action_show" type="Label" parent="."]
+unique_name_in_owner = true
+visible = false
+z_index = 1
+offset_left = -40.0
+offset_top = -75.0
+offset_right = 39.0
+offset_bottom = -6.0
+theme_override_font_sizes/font_size = 50
+text = "🤬"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="action_animation" type="AnimationPlayer" parent="."]
+unique_name_in_owner = true
+libraries = {
+"": SubResource("AnimationLibrary_a6wya")
+}
+
+[node name="hungry" type="Label" parent="."]
+unique_name_in_owner = true
+offset_left = -56.0
+offset_top = -179.0
+offset_right = 48.0
+offset_bottom = -96.0
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_font_sizes/font_size = 20
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="hp" type="TextureProgressBar" parent="."]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.607843)
+offset_left = -128.0
+offset_top = -192.0
+offset_right = 128.0
+offset_bottom = -152.0
+nine_patch_stretch = true
+texture_under = SubResource("GradientTexture1D_ioncu")
+texture_progress = SubResource("GradientTexture1D_6t72h")
+
+[connection signal="state_value_changed" from="." to="." method="_on_state_value_changed"]
+[connection signal="body_entered" from="sense_area" to="." method="_on_sense_area_body_entered"]
diff --git a/scene/test/run.gd b/scene/test/run.gd
new file mode 100644
index 0000000..607e1d4
--- /dev/null
+++ b/scene/test/run.gd
@@ -0,0 +1,32 @@
+extends State
+func enter_state(n):
+ pass
+func update_state(delta):
+ pass
+func update_state_phy(delta):
+ if get_player().is_move_finished():
+
+ change_to_state("idle")
+ print("切换为idle")
+ return
+
+ match get_player().get_dir():
+ 0:
+ get_player().play_animation("up")
+ 1:
+ get_player().play_animation("down")
+ 2:
+ get_player().play_animation("left_right")
+ 3:
+ get_player().play_animation("left_right")
+ pass
+func exit_state():
+ pass
+
+func process_message(type:String,n):
+ match type:
+ "move":
+ get_player().set_target_pos(n)
+ change_to_state("run")
+
+ pass
diff --git a/scene/test/run_action.gd b/scene/test/run_action.gd
new file mode 100644
index 0000000..f1e14ad
--- /dev/null
+++ b/scene/test/run_action.gd
@@ -0,0 +1,79 @@
+extends ActionLeaf
+#当前是否已经开始运动
+var is_started_self:bool=false
+@export var time_out_time:float=2
+var is_time_out:bool=false
+@onready var timer: Timer = $Timer
+
+func tick(actor:Node,black_board:Blackboard):
+ var unit:Unit=actor
+ if not is_started_self:
+ if not black_board.has_value("run_pos"):
+ return FAILURE
+ var run_pos:Vector2=black_board.get_value("run_pos")
+ actor.set_target_pos(run_pos)
+ is_started_self=true
+ timer.start(time_out_time)
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up")
+ 1:
+ unit.play_animation("down")
+ 2:
+ unit.play_animation("left_right")
+ 3:
+ unit.play_animation("left_right")
+ return RUNNING
+
+ else:
+ if is_time_out:
+ is_started_self=false
+ black_board.erase_value("run_pos")
+ is_time_out=false
+ timer.stop()
+ unit.stop_move()
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ return FAILURE
+
+ if actor.is_move_finished():
+ is_started_self=false
+ black_board.erase_value("run_pos")
+ is_time_out=false
+ timer.stop()
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up_idle")
+ 1:
+ unit.play_animation("down_idle")
+ 2:
+ unit.play_animation("left_right_idle")
+ 3:
+ unit.play_animation("left_right_idle")
+ return SUCCESS
+ else:
+ match unit.get_dir():
+ 0:
+ unit.play_animation("up")
+ 1:
+ unit.play_animation("down")
+ 2:
+ unit.play_animation("left_right")
+ 3:
+ unit.play_animation("left_right")
+ return RUNNING
+
+
+ pass
+
+
+func _on_timer_timeout() -> void:
+ is_time_out=true
+ pass # Replace with function body.
diff --git a/scene/test/run_await.tscn b/scene/test/run_await.tscn
new file mode 100644
index 0000000..a14b7dd
--- /dev/null
+++ b/scene/test/run_await.tscn
@@ -0,0 +1,20 @@
+[gd_scene load_steps=4 format=3 uid="uid://dg0gnl5n62gq6"]
+
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="1_qv4d0"]
+[ext_resource type="Script" path="res://scene/test/run_condition.gd" id="2_e4qcd"]
+[ext_resource type="Script" path="res://scene/test/run_action.gd" id="3_w3x7t"]
+
+[node name="run_await" type="Node"]
+script = ExtResource("1_qv4d0")
+
+[node name="ConditionLeaf" type="Node" parent="."]
+script = ExtResource("2_e4qcd")
+
+[node name="ActionLeaf" type="Node" parent="."]
+script = ExtResource("3_w3x7t")
+
+[node name="Timer" type="Timer" parent="ActionLeaf"]
+wait_time = 10.0
+one_shot = true
+
+[connection signal="timeout" from="ActionLeaf/Timer" to="ActionLeaf" method="_on_timer_timeout"]
diff --git a/scene/test/run_condition.gd b/scene/test/run_condition.gd
new file mode 100644
index 0000000..8e00491
--- /dev/null
+++ b/scene/test/run_condition.gd
@@ -0,0 +1,9 @@
+extends ConditionLeaf
+
+func tick(actor:Node,black_board:Blackboard):
+ #如果有设置目标地点
+ if black_board.has_value("run_pos"):
+
+ return SUCCESS
+ else:
+ return FAILURE
diff --git a/scene/test/self_character.gd b/scene/test/self_character.gd
new file mode 100644
index 0000000..90b8516
--- /dev/null
+++ b/scene/test/self_character.gd
@@ -0,0 +1,15 @@
+extends Unit
+
+func _ready() -> void:
+
+ super._ready()
+ state_machine.launch()
+
+
+func _on_state_value_changed(state_value_name: String, value: Variant) -> void:
+ match state_value_name:
+ "hp":
+ %hp.value=value
+ "hp_max":
+ %hp.max_value=value
+ pass # Replace with function body.
diff --git a/scene/test/state_machine.gd b/scene/test/state_machine.gd
new file mode 100644
index 0000000..dc29904
--- /dev/null
+++ b/scene/test/state_machine.gd
@@ -0,0 +1 @@
+extends StateMachine
diff --git a/scene/test/test_main.gd b/scene/test/test_main.gd
new file mode 100644
index 0000000..87094c8
--- /dev/null
+++ b/scene/test/test_main.gd
@@ -0,0 +1,14 @@
+extends Node2D
+@export var player: CharacterBody2D
+
+func _physics_process(delta: float) -> void:
+ %mouse_finder.position=get_global_mouse_position()
+
+
+func _on_control_gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed("mouse_right"):
+ if player:
+ player.sent_message("move",player.get_global_mouse_position())
+ pass
+
+ pass # Replace with function body.
diff --git a/scene/test/test_main.tscn b/scene/test/test_main.tscn
new file mode 100644
index 0000000..4935546
--- /dev/null
+++ b/scene/test/test_main.tscn
@@ -0,0 +1,137 @@
+[gd_scene load_steps=10 format=4 uid="uid://1wl1fl3qtxc"]
+
+[ext_resource type="Script" path="res://scene/test/test_main.gd" id="1_aimp4"]
+[ext_resource type="Texture2D" uid="uid://dxtu12mw7fsq8" path="res://res/image/test/tile.png" id="1_jp0cd"]
+[ext_resource type="PackedScene" uid="uid://cf2g2urxaukxb" path="res://scene/test/character.tscn" id="2_nvm7o"]
+[ext_resource type="PackedScene" uid="uid://b4mawovxv402a" path="res://scene/test/food.tscn" id="3_fgsvv"]
+[ext_resource type="PackedScene" uid="uid://dl1axae8yeeeh" path="res://scene/test/other_character.tscn" id="4_eyxcn"]
+
+[sub_resource type="NavigationPolygon" id="NavigationPolygon_tjudi"]
+vertices = PackedVector2Array(32, 32, -32, 32, -32, -32, 32, -32)
+polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3)])
+outlines = Array[PackedVector2Array]([PackedVector2Array(-32, -32, 32, -32, 32, 32, -32, 32)])
+agent_radius = 0.0
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_pe5ig"]
+texture = ExtResource("1_jp0cd")
+texture_region_size = Vector2i(64, 64)
+0:0/0 = 0
+0:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_tjudi")
+0:1/0 = 0
+0:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-32, -32, 32, -32, 32, 32, -32, 32)
+
+[sub_resource type="TileSet" id="TileSet_jmwcd"]
+tile_size = Vector2i(64, 64)
+physics_layer_0/collision_layer = 1
+navigation_layer_0/layers = 1
+sources/0 = SubResource("TileSetAtlasSource_pe5ig")
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_shajf"]
+
+[node name="main" type="Node2D" node_paths=PackedStringArray("player")]
+script = ExtResource("1_aimp4")
+player = NodePath("CB_add_pos/CharacterBody2D")
+
+[node name="Node2D" type="Node2D" parent="."]
+
+[node name="navigation" type="TileMapLayer" parent="Node2D"]
+unique_name_in_owner = true
+tile_map_data = PackedByteArray("AAD0//r/AAAAAAAAAAD0//v/AAAAAAEAAAD0//z/AAAAAAEAAAD0//3/AAAAAAAAAAD0//7/AAAAAAAAAAD0////AAAAAAAAAAD0/wAAAAAAAAAAAAD0/wEAAAAAAAAAAAD0/wIAAAAAAAAAAAD0/wMAAAAAAAEAAAD0/wQAAAAAAAEAAAD0/wUAAAAAAAAAAAD1/wYAAAAAAAEAAAD2/wYAAAAAAAEAAAD3/wcAAAAAAAAAAAD4/wcAAAAAAAEAAAD5/wcAAAAAAAAAAAD6/wgAAAAAAAEAAAD7/wgAAAAAAAAAAAD8/wgAAAAAAAAAAAD9/wgAAAAAAAAAAAD+/wgAAAAAAAAAAAD+/wkAAAAAAAEAAAD//wkAAAAAAAAAAAAAAAkAAAAAAAAAAAABAAkAAAAAAAAAAAACAAkAAAAAAAAAAAADAAkAAAAAAAAAAAAEAAkAAAAAAAAAAAAFAAkAAAAAAAEAAAAGAAkAAAAAAAEAAAAHAAkAAAAAAAAAAAAIAAkAAAAAAAAAAAAJAAkAAAAAAAAAAAAKAAkAAAAAAAAAAAALAAkAAAAAAAAAAAAMAAkAAAAAAAAAAAAMAAgAAAAAAAAAAAANAAgAAAAAAAAAAAANAAcAAAAAAAAAAAANAAYAAAAAAAAAAAANAAUAAAAAAAAAAAANAAQAAAAAAAAAAAANAAMAAAAAAAAAAAAMAAMAAAAAAAAAAAAMAAIAAAAAAAAAAAAMAAEAAAAAAAAAAAAMAAAAAAAAAAAAAAAMAP//AAAAAAAAAAALAP//AAAAAAAAAAALAP7/AAAAAAAAAAAKAP7/AAAAAAAAAAAKAP3/AAAAAAAAAAAJAP3/AAAAAAAAAAAIAPz/AAAAAAEAAAAHAPz/AAAAAAEAAAAHAPv/AAAAAAAAAAAGAPv/AAAAAAAAAAAFAPv/AAAAAAEAAAAEAPv/AAAAAAEAAAADAPv/AAAAAAEAAAADAPr/AAAAAAAAAAACAPr/AAAAAAAAAAABAPr/AAAAAAAAAAAAAPr/AAAAAAAAAAD///r/AAAAAAAAAAD+//r/AAAAAAAAAAD+//v/AAAAAAAAAAD9//v/AAAAAAAAAAD9//z/AAAAAAAAAAD9//3/AAAAAAAAAAD9//7/AAAAAAAAAAD+//7/AAAAAAAAAAD+////AAAAAAAAAAD+/wAAAAAAAAAAAAD//wAAAAAAAAAAAAD//wEAAAAAAAAAAAD//wIAAAAAAAAAAAD//wMAAAAAAAAAAAD//wQAAAAAAAEAAAAAAAQAAAAAAAAAAAABAAQAAAAAAAAAAAABAAMAAAAAAAAAAAACAAIAAAAAAAAAAADu//f/AAAAAAAAAADu//j/AAAAAAAAAADu//n/AAAAAAAAAADu//r/AAAAAAAAAADu//v/AAAAAAAAAADu//z/AAAAAAAAAADu//3/AAAAAAAAAADu//7/AAAAAAAAAADu////AAAAAAAAAADu/wAAAAAAAAAAAADu/wEAAAAAAAAAAADu/wIAAAAAAAAAAADu/wMAAAAAAAAAAADu/wQAAAAAAAAAAADu/wUAAAAAAAAAAADu/wYAAAAAAAAAAADu/wcAAAAAAAAAAADu/wgAAAAAAAAAAADu/wkAAAAAAAAAAADu/woAAAAAAAAAAADu/wsAAAAAAAAAAADv//f/AAAAAAAAAADv//j/AAAAAAAAAADv//n/AAAAAAAAAADv//r/AAAAAAAAAADv//v/AAAAAAAAAADv//z/AAAAAAAAAADv//3/AAAAAAAAAADv//7/AAAAAAAAAADv////AAAAAAAAAADv/wAAAAAAAAAAAADv/wEAAAAAAAAAAADv/wIAAAAAAAAAAADv/wMAAAAAAAAAAADv/wQAAAAAAAAAAADv/wUAAAAAAAAAAADv/wYAAAAAAAAAAADv/wcAAAAAAAAAAADv/wgAAAAAAAAAAADv/wkAAAAAAAAAAADv/woAAAAAAAAAAADv/wsAAAAAAAAAAADw//f/AAAAAAAAAADw//j/AAAAAAAAAADw//n/AAAAAAAAAADw//r/AAAAAAAAAADw//v/AAAAAAAAAADw//z/AAAAAAAAAADw//3/AAAAAAAAAADw//7/AAAAAAAAAADw////AAAAAAAAAADw/wAAAAAAAAAAAADw/wEAAAAAAAAAAADw/wIAAAAAAAAAAADw/wMAAAAAAAAAAADw/wQAAAAAAAAAAADw/wUAAAAAAAAAAADw/wYAAAAAAAAAAADw/wcAAAAAAAAAAADw/wgAAAAAAAAAAADw/wkAAAAAAAAAAADw/woAAAAAAAAAAADw/wsAAAAAAAAAAADx//f/AAAAAAAAAADx//j/AAAAAAAAAADx//n/AAAAAAAAAADx//r/AAAAAAAAAADx//v/AAAAAAAAAADx//z/AAAAAAAAAADx//3/AAAAAAAAAADx//7/AAAAAAAAAADx////AAAAAAAAAADx/wAAAAAAAAAAAADx/wEAAAAAAAAAAADx/wIAAAAAAAAAAADx/wMAAAAAAAAAAADx/wQAAAAAAAAAAADx/wUAAAAAAAAAAADx/wYAAAAAAAAAAADx/wcAAAAAAAAAAADx/wgAAAAAAAAAAADx/wkAAAAAAAAAAADx/woAAAAAAAAAAADx/wsAAAAAAAAAAADy//f/AAAAAAAAAADy//j/AAAAAAAAAADy//n/AAAAAAAAAADy//r/AAAAAAAAAADy//v/AAAAAAEAAADy//z/AAAAAAEAAADy//3/AAAAAAEAAADy//7/AAAAAAEAAADy////AAAAAAEAAADy/wAAAAAAAAEAAADy/wEAAAAAAAAAAADy/wIAAAAAAAAAAADy/wMAAAAAAAAAAADy/wQAAAAAAAAAAADy/wUAAAAAAAAAAADy/wYAAAAAAAAAAADy/wcAAAAAAAAAAADy/wgAAAAAAAAAAADy/wkAAAAAAAAAAADy/woAAAAAAAAAAADy/wsAAAAAAAAAAADz//f/AAAAAAAAAADz//j/AAAAAAAAAADz//n/AAAAAAAAAADz//r/AAAAAAAAAADz//v/AAAAAAEAAADz//z/AAAAAAEAAADz//3/AAAAAAAAAADz//7/AAAAAAAAAADz////AAAAAAAAAADz/wAAAAAAAAEAAADz/wEAAAAAAAEAAADz/wIAAAAAAAEAAADz/wMAAAAAAAEAAADz/wQAAAAAAAAAAADz/wUAAAAAAAAAAADz/wYAAAAAAAAAAADz/wcAAAAAAAAAAADz/wgAAAAAAAAAAADz/wkAAAAAAAAAAADz/woAAAAAAAAAAADz/wsAAAAAAAAAAAD0//f/AAAAAAAAAAD0//j/AAAAAAAAAAD0//n/AAAAAAAAAAD0/wYAAAAAAAAAAAD0/wcAAAAAAAAAAAD0/wgAAAAAAAAAAAD0/wkAAAAAAAAAAAD0/woAAAAAAAAAAAD0/wsAAAAAAAAAAAD1//f/AAAAAAAAAAD1//j/AAAAAAAAAAD1//n/AAAAAAAAAAD1//r/AAAAAAAAAAD1//v/AAAAAAEAAAD1//z/AAAAAAEAAAD1//3/AAAAAAAAAAD1//7/AAAAAAAAAAD1////AAAAAAAAAAD1/wAAAAAAAAAAAAD1/wEAAAAAAAAAAAD1/wIAAAAAAAAAAAD1/wMAAAAAAAAAAAD1/wQAAAAAAAEAAAD1/wUAAAAAAAEAAAD1/wcAAAAAAAAAAAD1/wgAAAAAAAAAAAD1/wkAAAAAAAAAAAD1/woAAAAAAAAAAAD1/wsAAAAAAAAAAAD2//f/AAAAAAAAAAD2//j/AAAAAAAAAAD2//n/AAAAAAAAAAD2//r/AAAAAAAAAAD2//v/AAAAAAEAAAD2//z/AAAAAAEAAAD2//3/AAAAAAAAAAD2//7/AAAAAAAAAAD2////AAAAAAAAAAD2/wAAAAAAAAAAAAD2/wEAAAAAAAAAAAD2/wIAAAAAAAAAAAD2/wMAAAAAAAAAAAD2/wQAAAAAAAAAAAD2/wUAAAAAAAAAAAD2/wcAAAAAAAAAAAD2/wgAAAAAAAAAAAD2/wkAAAAAAAAAAAD2/woAAAAAAAAAAAD2/wsAAAAAAAAAAAD3//f/AAAAAAAAAAD3//j/AAAAAAAAAAD3//n/AAAAAAAAAAD3//r/AAAAAAAAAAD3//v/AAAAAAEAAAD3//z/AAAAAAEAAAD3//3/AAAAAAAAAAD3//7/AAAAAAAAAAD3////AAAAAAAAAAD3/wAAAAAAAAAAAAD3/wEAAAAAAAAAAAD3/wIAAAAAAAAAAAD3/wMAAAAAAAAAAAD3/wQAAAAAAAAAAAD3/wUAAAAAAAAAAAD3/wYAAAAAAAEAAAD3/wgAAAAAAAAAAAD3/wkAAAAAAAAAAAD3/woAAAAAAAAAAAD3/wsAAAAAAAAAAAD4//f/AAAAAAAAAAD4//j/AAAAAAAAAAD4//n/AAAAAAAAAAD4//r/AAAAAAAAAAD4//v/AAAAAAEAAAD4//z/AAAAAAEAAAD4//3/AAAAAAAAAAD4//7/AAAAAAAAAAD4////AAAAAAAAAAD4/wAAAAAAAAAAAAD4/wEAAAAAAAAAAAD4/wIAAAAAAAAAAAD4/wMAAAAAAAAAAAD4/wQAAAAAAAAAAAD4/wUAAAAAAAAAAAD4/wYAAAAAAAEAAAD4/wgAAAAAAAEAAAD4/wkAAAAAAAAAAAD4/woAAAAAAAAAAAD4/wsAAAAAAAAAAAD5//f/AAAAAAAAAAD5//j/AAAAAAAAAAD5//n/AAAAAAAAAAD5//r/AAAAAAAAAAD5//v/AAAAAAEAAAD5//z/AAAAAAEAAAD5//3/AAAAAAAAAAD5//7/AAAAAAAAAAD5////AAAAAAAAAAD5/wAAAAAAAAAAAAD5/wEAAAAAAAAAAAD5/wIAAAAAAAAAAAD5/wMAAAAAAAAAAAD5/wQAAAAAAAAAAAD5/wUAAAAAAAAAAAD5/wYAAAAAAAAAAAD5/wgAAAAAAAEAAAD5/wkAAAAAAAAAAAD5/woAAAAAAAAAAAD5/wsAAAAAAAAAAAD6//f/AAAAAAAAAAD6//j/AAAAAAAAAAD6//n/AAAAAAAAAAD6//r/AAAAAAAAAAD6//v/AAAAAAEAAAD6//z/AAAAAAEAAAD6//3/AAAAAAAAAAD6//7/AAAAAAAAAAD6////AAAAAAAAAAD6/wAAAAAAAAAAAAD6/wEAAAAAAAAAAAD6/wIAAAAAAAAAAAD6/wMAAAAAAAAAAAD6/wQAAAAAAAAAAAD6/wUAAAAAAAAAAAD6/wYAAAAAAAAAAAD6/wcAAAAAAAAAAAD6/wkAAAAAAAEAAAD6/woAAAAAAAAAAAD6/wsAAAAAAAAAAAD7//f/AAAAAAAAAAD7//j/AAAAAAAAAAD7//n/AAAAAAAAAAD7//r/AAAAAAAAAAD7//v/AAAAAAEAAAD7//z/AAAAAAEAAAD7//3/AAAAAAAAAAD7//7/AAAAAAAAAAD7////AAAAAAAAAAD7/wAAAAAAAAAAAAD7/wEAAAAAAAAAAAD7/wIAAAAAAAAAAAD7/wMAAAAAAAAAAAD7/wQAAAAAAAAAAAD7/wUAAAAAAAAAAAD7/wYAAAAAAAAAAAD7/wcAAAAAAAAAAAD7/wkAAAAAAAEAAAD7/woAAAAAAAAAAAD7/wsAAAAAAAAAAAD8//f/AAAAAAAAAAD8//j/AAAAAAAAAAD8//n/AAAAAAAAAAD8//r/AAAAAAAAAAD8//v/AAAAAAEAAAD8//z/AAAAAAEAAAD8//3/AAAAAAAAAAD8//7/AAAAAAAAAAD8////AAAAAAAAAAD8/wAAAAAAAAAAAAD8/wEAAAAAAAAAAAD8/wIAAAAAAAAAAAD8/wMAAAAAAAAAAAD8/wQAAAAAAAAAAAD8/wUAAAAAAAAAAAD8/wYAAAAAAAAAAAD8/wcAAAAAAAAAAAD8/wkAAAAAAAEAAAD8/woAAAAAAAAAAAD8/wsAAAAAAAAAAAD9//f/AAAAAAAAAAD9//j/AAAAAAAAAAD9//n/AAAAAAAAAAD9//r/AAAAAAAAAAD9////AAAAAAAAAAD9/wAAAAAAAAAAAAD9/wEAAAAAAAAAAAD9/wIAAAAAAAAAAAD9/wMAAAAAAAAAAAD9/wQAAAAAAAAAAAD9/wUAAAAAAAAAAAD9/wYAAAAAAAAAAAD9/wcAAAAAAAAAAAD9/wkAAAAAAAEAAAD9/woAAAAAAAAAAAD9/wsAAAAAAAAAAAD+//f/AAAAAAAAAAD+//j/AAAAAAAAAAD+//n/AAAAAAAAAAD+//z/AAAAAAAAAAD+//3/AAAAAAAAAAD+/wEAAAAAAAAAAAD+/wIAAAAAAAAAAAD+/wMAAAAAAAAAAAD+/wQAAAAAAAAAAAD+/wUAAAAAAAAAAAD+/wYAAAAAAAAAAAD+/wcAAAAAAAAAAAD+/woAAAAAAAEAAAD+/wsAAAAAAAAAAAD///f/AAAAAAAAAAD///j/AAAAAAAAAAD///n/AAAAAAAAAAD///v/AAAAAAAAAAD///z/AAAAAAAAAAD///3/AAAAAAAAAAD///7/AAAAAAAAAAD/////AAAAAAAAAAD//wUAAAAAAAEAAAD//wYAAAAAAAAAAAD//wcAAAAAAAAAAAD//wgAAAAAAAAAAAD//woAAAAAAAEAAAD//wsAAAAAAAAAAAAAAPf/AAAAAAAAAAAAAPj/AAAAAAAAAAAAAPn/AAAAAAAAAAAAAPv/AAAAAAAAAAAAAPz/AAAAAAAAAAAAAP3/AAAAAAAAAAAAAP7/AAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAUAAAAAAAEAAAAAAAYAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAoAAAAAAAEAAAAAAAsAAAAAAAAAAAABAPf/AAAAAAAAAAABAPj/AAAAAAAAAAABAPn/AAAAAAAAAAABAPv/AAAAAAAAAAABAPz/AAAAAAAAAAABAP3/AAAAAAAAAAABAP7/AAAAAAAAAAABAP//AAAAAAAAAAABAAAAAAAAAAAAAAABAAEAAAAAAAAAAAABAAIAAAAAAAAAAAABAAUAAAAAAAEAAAABAAYAAAAAAAAAAAABAAcAAAAAAAAAAAABAAgAAAAAAAAAAAABAAoAAAAAAAEAAAABAAsAAAAAAAAAAAACAPf/AAAAAAAAAAACAPj/AAAAAAAAAAACAPn/AAAAAAAAAAACAPv/AAAAAAEAAAACAPz/AAAAAAEAAAACAP3/AAAAAAEAAAACAP7/AAAAAAEAAAACAP//AAAAAAAAAAACAAAAAAAAAAAAAAACAAEAAAAAAAAAAAACAAMAAAAAAAAAAAACAAQAAAAAAAAAAAACAAUAAAAAAAEAAAACAAYAAAAAAAAAAAACAAcAAAAAAAAAAAACAAgAAAAAAAAAAAACAAoAAAAAAAEAAAACAAsAAAAAAAAAAAADAPf/AAAAAAAAAAADAPj/AAAAAAAAAAADAPn/AAAAAAAAAAADAPz/AAAAAAAAAAADAP3/AAAAAAAAAAADAP7/AAAAAAEAAAADAP//AAAAAAEAAAADAAAAAAAAAAAAAAADAAEAAAAAAAAAAAADAAIAAAAAAAAAAAADAAMAAAAAAAAAAAADAAQAAAAAAAEAAAADAAUAAAAAAAEAAAADAAYAAAAAAAAAAAADAAcAAAAAAAAAAAADAAgAAAAAAAAAAAADAAoAAAAAAAEAAAADAAsAAAAAAAAAAAAEAPf/AAAAAAAAAAAEAPj/AAAAAAAAAAAEAPn/AAAAAAAAAAAEAPr/AAAAAAAAAAAEAPz/AAAAAAAAAAAEAP3/AAAAAAAAAAAEAP7/AAAAAAAAAAAEAP//AAAAAAEAAAAEAAAAAAAAAAEAAAAEAAEAAAAAAAEAAAAEAAIAAAAAAAEAAAAEAAMAAAAAAAEAAAAEAAQAAAAAAAEAAAAEAAUAAAAAAAEAAAAEAAYAAAAAAAAAAAAEAAcAAAAAAAAAAAAEAAgAAAAAAAAAAAAEAAoAAAAAAAEAAAAEAAsAAAAAAAAAAAAFAPf/AAAAAAAAAAAFAPj/AAAAAAAAAAAFAPn/AAAAAAAAAAAFAPr/AAAAAAAAAAAFAPz/AAAAAAEAAAAFAP3/AAAAAAAAAAAFAP7/AAAAAAAAAAAFAP//AAAAAAAAAAAFAAAAAAAAAAEAAAAFAAEAAAAAAAEAAAAFAAIAAAAAAAAAAAAFAAMAAAAAAAAAAAAFAAQAAAAAAAAAAAAFAAUAAAAAAAAAAAAFAAYAAAAAAAAAAAAFAAcAAAAAAAAAAAAFAAgAAAAAAAAAAAAFAAoAAAAAAAEAAAAFAAsAAAAAAAAAAAAGAPf/AAAAAAAAAAAGAPj/AAAAAAAAAAAGAPn/AAAAAAAAAAAGAPr/AAAAAAAAAAAGAPz/AAAAAAEAAAAGAP3/AAAAAAAAAAAGAP7/AAAAAAAAAAAGAP//AAAAAAAAAAAGAAAAAAAAAAAAAAAGAAEAAAAAAAAAAAAGAAIAAAAAAAAAAAAGAAMAAAAAAAAAAAAGAAQAAAAAAAAAAAAGAAUAAAAAAAAAAAAGAAYAAAAAAAAAAAAGAAcAAAAAAAAAAAAGAAgAAAAAAAEAAAAGAAoAAAAAAAAAAAAGAAsAAAAAAAAAAAAHAPf/AAAAAAAAAAAHAPj/AAAAAAAAAAAHAPn/AAAAAAAAAAAHAPr/AAAAAAAAAAAHAP3/AAAAAAAAAAAHAP7/AAAAAAAAAAAHAP//AAAAAAAAAAAHAAAAAAAAAAAAAAAHAAEAAAAAAAAAAAAHAAIAAAAAAAAAAAAHAAMAAAAAAAAAAAAHAAQAAAAAAAAAAAAHAAUAAAAAAAAAAAAHAAYAAAAAAAAAAAAHAAcAAAAAAAEAAAAHAAgAAAAAAAEAAAAHAAoAAAAAAAAAAAAHAAsAAAAAAAAAAAAIAPf/AAAAAAAAAAAIAPj/AAAAAAAAAAAIAPn/AAAAAAAAAAAIAPr/AAAAAAAAAAAIAPv/AAAAAAAAAAAIAP3/AAAAAAEAAAAIAP7/AAAAAAEAAAAIAP//AAAAAAAAAAAIAAAAAAAAAAAAAAAIAAEAAAAAAAAAAAAIAAIAAAAAAAAAAAAIAAMAAAAAAAAAAAAIAAQAAAAAAAAAAAAIAAUAAAAAAAAAAAAIAAYAAAAAAAEAAAAIAAcAAAAAAAEAAAAIAAgAAAAAAAAAAAAIAAoAAAAAAAAAAAAIAAsAAAAAAAAAAAAJAPf/AAAAAAAAAAAJAPj/AAAAAAAAAAAJAPn/AAAAAAAAAAAJAPr/AAAAAAAAAAAJAPv/AAAAAAAAAAAJAPz/AAAAAAAAAAAJAP7/AAAAAAEAAAAJAP//AAAAAAEAAAAJAAAAAAAAAAEAAAAJAAEAAAAAAAEAAAAJAAIAAAAAAAEAAAAJAAMAAAAAAAEAAAAJAAQAAAAAAAEAAAAJAAUAAAAAAAEAAAAJAAYAAAAAAAEAAAAJAAcAAAAAAAAAAAAJAAgAAAAAAAAAAAAJAAoAAAAAAAAAAAAJAAsAAAAAAAAAAAAKAPf/AAAAAAAAAAAKAPj/AAAAAAAAAAAKAPn/AAAAAAAAAAAKAPr/AAAAAAAAAAAKAPv/AAAAAAAAAAAKAPz/AAAAAAAAAAAKAP//AAAAAAAAAAAKAAAAAAAAAAAAAAAKAAEAAAAAAAAAAAAKAAIAAAAAAAAAAAAKAAMAAAAAAAAAAAAKAAQAAAAAAAAAAAAKAAUAAAAAAAAAAAAKAAYAAAAAAAAAAAAKAAcAAAAAAAAAAAAKAAgAAAAAAAAAAAAKAAoAAAAAAAAAAAAKAAsAAAAAAAAAAAALAPf/AAAAAAAAAAALAPj/AAAAAAAAAAALAPn/AAAAAAAAAAALAPr/AAAAAAAAAAALAPv/AAAAAAAAAAALAPz/AAAAAAAAAAALAP3/AAAAAAAAAAALAAAAAAAAAAAAAAALAAEAAAAAAAAAAAALAAIAAAAAAAAAAAALAAMAAAAAAAAAAAALAAQAAAAAAAAAAAALAAUAAAAAAAAAAAALAAYAAAAAAAAAAAALAAcAAAAAAAAAAAALAAgAAAAAAAAAAAALAAoAAAAAAAAAAAALAAsAAAAAAAAAAAAMAPf/AAAAAAAAAAAMAPj/AAAAAAAAAAAMAPn/AAAAAAAAAAAMAPr/AAAAAAAAAAAMAPv/AAAAAAAAAAAMAPz/AAAAAAAAAAAMAP3/AAAAAAAAAAAMAP7/AAAAAAAAAAAMAAQAAAAAAAAAAAAMAAUAAAAAAAAAAAAMAAYAAAAAAAAAAAAMAAcAAAAAAAAAAAAMAAoAAAAAAAAAAAAMAAsAAAAAAAAAAAANAPf/AAAAAAAAAAANAPj/AAAAAAAAAAANAPn/AAAAAAAAAAANAPr/AAAAAAAAAAANAPv/AAAAAAAAAAANAPz/AAAAAAAAAAANAP3/AAAAAAAAAAANAP7/AAAAAAAAAAANAP//AAAAAAAAAAANAAAAAAAAAAAAAAANAAEAAAAAAAAAAAANAAIAAAAAAAAAAAANAAkAAAAAAAAAAAANAAoAAAAAAAAAAAANAAsAAAAAAAAAAAAOAPf/AAAAAAAAAAAOAPj/AAAAAAAAAAAOAPn/AAAAAAAAAAAOAPr/AAAAAAAAAAAOAPv/AAAAAAAAAAAOAPz/AAAAAAEAAAAOAP3/AAAAAAAAAAAOAP7/AAAAAAAAAAAOAP//AAAAAAAAAAAOAAAAAAAAAAAAAAAOAAEAAAAAAAAAAAAOAAIAAAAAAAAAAAAOAAMAAAAAAAAAAAAOAAQAAAAAAAAAAAAOAAUAAAAAAAAAAAAOAAYAAAAAAAAAAAAOAAcAAAAAAAAAAAAOAAgAAAAAAAAAAAAOAAkAAAAAAAAAAAAOAAoAAAAAAAAAAAAOAAsAAAAAAAAAAAAPAPf/AAAAAAAAAAAPAPj/AAAAAAAAAAAPAPn/AAAAAAEAAAAPAPr/AAAAAAEAAAAPAPv/AAAAAAEAAAAPAPz/AAAAAAEAAAAPAP3/AAAAAAEAAAAPAP7/AAAAAAEAAAAPAP//AAAAAAAAAAAPAAAAAAAAAAAAAAAPAAEAAAAAAAAAAAAPAAIAAAAAAAAAAAAPAAMAAAAAAAAAAAAPAAQAAAAAAAAAAAAPAAUAAAAAAAAAAAAPAAYAAAAAAAAAAAAPAAcAAAAAAAAAAAAPAAgAAAAAAAAAAAAPAAkAAAAAAAAAAAAPAAoAAAAAAAAAAAAPAAsAAAAAAAAAAAAQAPf/AAAAAAAAAAAQAPj/AAAAAAAAAAAQAPn/AAAAAAEAAAAQAPr/AAAAAAAAAAAQAPv/AAAAAAAAAAAQAPz/AAAAAAAAAAAQAP3/AAAAAAAAAAAQAP7/AAAAAAEAAAAQAP//AAAAAAAAAAAQAAAAAAAAAAAAAAAQAAEAAAAAAAAAAAAQAAIAAAAAAAAAAAAQAAMAAAAAAAAAAAAQAAQAAAAAAAAAAAAQAAUAAAAAAAAAAAAQAAYAAAAAAAAAAAAQAAcAAAAAAAAAAAAQAAgAAAAAAAAAAAAQAAkAAAAAAAAAAAAQAAoAAAAAAAAAAAAQAAsAAAAAAAAAAAARAPf/AAAAAAAAAAARAPj/AAAAAAAAAAARAPn/AAAAAAEAAAARAPr/AAAAAAAAAAARAPv/AAAAAAAAAAARAPz/AAAAAAAAAAARAP3/AAAAAAAAAAARAP7/AAAAAAEAAAARAP//AAAAAAAAAAARAAAAAAAAAAAAAAARAAEAAAAAAAAAAAARAAIAAAAAAAAAAAARAAMAAAAAAAAAAAARAAQAAAAAAAAAAAARAAUAAAAAAAAAAAARAAYAAAAAAAAAAAARAAcAAAAAAAAAAAARAAgAAAAAAAAAAAARAAkAAAAAAAAAAAARAAoAAAAAAAAAAAARAAsAAAAAAAAAAAASAPf/AAAAAAAAAAASAPj/AAAAAAAAAAASAPn/AAAAAAEAAAASAPr/AAAAAAEAAAASAPv/AAAAAAEAAAASAPz/AAAAAAAAAAASAP3/AAAAAAEAAAASAP7/AAAAAAEAAAASAP//AAAAAAAAAAASAAAAAAAAAAAAAAASAAEAAAAAAAAAAAASAAIAAAAAAAAAAAASAAMAAAAAAAAAAAASAAQAAAAAAAAAAAASAAUAAAAAAAAAAAASAAYAAAAAAAAAAAASAAcAAAAAAAAAAAASAAgAAAAAAAAAAAASAAkAAAAAAAAAAAASAAoAAAAAAAAAAAASAAsAAAAAAAAAAAATAPf/AAAAAAAAAAATAPj/AAAAAAAAAAATAPn/AAAAAAEAAAATAPr/AAAAAAEAAAATAPv/AAAAAAEAAAATAPz/AAAAAAEAAAATAP3/AAAAAAEAAAATAP7/AAAAAAEAAAATAP//AAAAAAAAAAATAAAAAAAAAAAAAAATAAEAAAAAAAAAAAATAAIAAAAAAAAAAAATAAMAAAAAAAAAAAATAAQAAAAAAAAAAAATAAUAAAAAAAAAAAATAAYAAAAAAAAAAAATAAcAAAAAAAAAAAATAAgAAAAAAAAAAAATAAkAAAAAAAAAAAATAAoAAAAAAAAAAAATAAsAAAAAAAAAAAAUAPf/AAAAAAAAAAAUAPj/AAAAAAAAAAAUAPn/AAAAAAAAAAAUAPr/AAAAAAAAAAAUAPv/AAAAAAAAAAAUAPz/AAAAAAAAAAAUAP3/AAAAAAAAAAAUAP7/AAAAAAAAAAAUAP//AAAAAAAAAAAUAAAAAAAAAAAAAAAUAAEAAAAAAAAAAAAUAAIAAAAAAAAAAAAUAAMAAAAAAAAAAAAUAAQAAAAAAAAAAAAUAAUAAAAAAAAAAAAUAAYAAAAAAAAAAAAUAAcAAAAAAAAAAAAUAAgAAAAAAAAAAAAUAAkAAAAAAAAAAAAUAAoAAAAAAAAAAAAUAAsAAAAAAAAAAAAVAPf/AAAAAAAAAAAVAPj/AAAAAAAAAAAVAPn/AAAAAAAAAAAVAPr/AAAAAAAAAAAVAPv/AAAAAAAAAAAVAPz/AAAAAAAAAAAVAP3/AAAAAAAAAAAVAP7/AAAAAAAAAAAVAP//AAAAAAAAAAAVAAAAAAAAAAAAAAAVAAEAAAAAAAAAAAAVAAIAAAAAAAAAAAAVAAMAAAAAAAAAAAAVAAQAAAAAAAAAAAAVAAUAAAAAAAAAAAAVAAYAAAAAAAAAAAAVAAcAAAAAAAAAAAAVAAgAAAAAAAAAAAAVAAkAAAAAAAAAAAAVAAoAAAAAAAAAAAAVAAsAAAAAAAAAAAA=")
+tile_set = SubResource("TileSet_jmwcd")
+
+[node name="back" type="TileMapLayer" parent="Node2D"]
+
+[node name="build" type="TileMapLayer" parent="Node2D"]
+
+[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="mouse_finder" type="Area2D" parent="."]
+unique_name_in_owner = true
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="mouse_finder"]
+shape = SubResource("CircleShape2D_shajf")
+
+[node name="CB_add_pos" type="Node2D" parent="."]
+unique_name_in_owner = true
+y_sort_enabled = true
+
+[node name="CharacterBody2D" parent="CB_add_pos" instance=ExtResource("2_nvm7o")]
+unit_id = "player"
+unit_type = "player"
+
+[node name="other_character" parent="CB_add_pos" instance=ExtResource("4_eyxcn")]
+position = Vector2(1048, 119)
+unit_id = "test_1_1"
+unit_type = "test_1"
+
+[node name="other_character2" parent="CB_add_pos" instance=ExtResource("4_eyxcn")]
+position = Vector2(548, -436)
+unit_id = "test_1_2"
+unit_type = "test_1"
+
+[node name="other_character5" parent="CB_add_pos" instance=ExtResource("4_eyxcn")]
+position = Vector2(-871, 442)
+unit_id = "test_1_3"
+unit_type = "test_1"
+
+[node name="other_character3" parent="CB_add_pos" instance=ExtResource("4_eyxcn")]
+position = Vector2(-649, 649)
+unit_id = "test_2_1"
+unit_type = "test_2"
+
+[node name="other_character4" parent="CB_add_pos" instance=ExtResource("4_eyxcn")]
+position = Vector2(-429, -13)
+unit_id = "test_2_2"
+unit_type = "test_2"
+
+[node name="food" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-917, -398)
+
+[node name="food2" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-986, -147)
+
+[node name="food3" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-491, -411)
+
+[node name="food4" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-66, -408)
+
+[node name="food5" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(343, -416)
+
+[node name="food6" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(433, 275)
+
+[node name="food7" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(161, 131)
+
+[node name="food8" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-176, 192)
+
+[node name="food9" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-580, 86)
+
+[node name="food10" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(-360, 343)
+
+[node name="food11" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(154, 522)
+
+[node name="food12" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(615, 638)
+
+[node name="food13" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(1091, 597)
+
+[node name="food14" parent="CB_add_pos" instance=ExtResource("3_fgsvv")]
+position = Vector2(464, 0)
+
+[connection signal="gui_input" from="CanvasLayer/Control" to="." method="_on_control_gui_input"]
diff --git a/tool/drawer_container/drawer_container.gd b/tool/drawer_container/drawer_container.gd
new file mode 100644
index 0000000..99609d9
--- /dev/null
+++ b/tool/drawer_container/drawer_container.gd
@@ -0,0 +1,246 @@
+@tool
+extends Container
+##抽屉容器节点,可以用于将子节点侧滑,开启clip content可以做到完全隐藏子节点
+class_name DrawerContainer
+##按钮大小
+@export var custom_button_size:Vector2=Vector2.ZERO:
+ set(val):
+ custom_button_size=val
+ side_button_size_changed()
+ fit_children()
+ fit_side_button()
+var button_size:Vector2=Vector2(100,200):
+ set(val):
+ button_size=val
+ fit_children()
+ fit_side_button()
+##按钮场景,根节点必须为basebutton或继承自basebutton,留空则使用默认按钮
+@export var button_tscn:PackedScene
+##布局模式
+@export var button_model:BUTTON_MODEL=BUTTON_MODEL.TOP
+enum BUTTON_MODEL{
+ ##按钮在上方,向下收纳
+ TOP=0,
+ ##按钮在下方,向上收纳
+ END=1,
+ ##按钮在左方,向右收纳
+ LEFT=2,
+ ##按钮在右方,向左收纳
+ RIGHT=3,
+}
+##动画时间
+@export var animation_time:float=0.5
+##是否自动隐藏
+@export var is_auto_hide:bool=false
+#隐藏计时器
+@onready var hide_timer:Timer=Timer.new()
+##是否自动收缩
+@export var is_auto_shrink:bool=false
+##是否禁用按钮
+@export var disable_button:bool=false:
+ set(val):
+ disable_button=val
+ fit_side_button()
+ fit_side_button()
+ if side_button!=null:
+ if val :
+ side_button.hide()
+ else:
+ side_button.show()
+##动画过渡模式
+@export var animation_model:Tween.TransitionType=Tween.TransitionType.TRANS_BACK
+##当前持有的动画
+var now_keep_tween:Tween
+##当前是否打开
+@export var is_open:bool=true
+##持有的抽屉控制按钮的实例(内部节点)
+var side_button:BaseButton
+
+##获取当前持有的侧边按钮
+func get_side_button()->BaseButton:
+ return side_button
+
+##当前动画状态,0为完全打开,1为完全关闭
+@export var rag:float=0:
+ set(val):
+ rag=val
+ fit_side_button()
+ fit_children()
+ minimum_size_changed.emit()
+func _ready() -> void:
+ add_child(hide_timer,false,Node.INTERNAL_MODE_FRONT)
+ hide_timer.one_shot=true
+ hide_timer.timeout.connect(hide_time_out)
+ if is_open:
+ rag=0
+ else:
+ rag=1
+ if button_tscn!=null:
+ var new_btn=button_tscn.instantiate()
+ if new_btn is BaseButton:
+ side_button=new_btn
+ add_child(new_btn,false,Node.INTERNAL_MODE_FRONT)
+ else:
+ new_btn=Button.new()
+ side_button=new_btn
+ add_child(new_btn,false,Node.INTERNAL_MODE_FRONT)
+ else:
+ var new_btn=Button.new()
+ side_button=new_btn
+ add_child(new_btn,false,Node.INTERNAL_MODE_FRONT)
+ side_button.pressed.connect(change_open)
+ side_button.minimum_size_changed.connect(side_button_size_changed)
+ side_button_size_changed()
+ if disable_button:
+ side_button.hide()
+ resized.connect(resize)
+ resize()
+ _re_ready()
+ pass # Replace with function body.
+##因为占用了ready函数,所以留了一个新的虚函数给使用节点重写
+func _re_ready()->void:
+
+
+
+ pass
+##对侧边栏按钮进行适配
+func fit_side_button()->void:
+ var rect:Rect2
+ if side_button!=null:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ rect=Rect2(0,(size.y-button_size.y)*rag,size.x,button_size.y)
+ BUTTON_MODEL.END:
+ rect=Rect2(0,(size.y-button_size.y)*(1-rag),size.x,button_size.y)
+ BUTTON_MODEL.LEFT:
+ rect=Rect2((size.x-button_size.x)*rag,0,button_size.x,size.y)
+ BUTTON_MODEL.RIGHT:
+ rect=Rect2((size.x-button_size.x)*(1-rag),0,button_size.x,size.y)
+
+ _:
+ rect=Rect2()
+ fit_child_in_rect(side_button,rect)
+##对子节点进行适配
+func fit_children()->void:
+ var rect:Rect2
+ if !disable_button:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ rect=Rect2(0,(size.y-button_size.y)*rag+button_size.y,size.x,size.y-button_size.y)
+ BUTTON_MODEL.END:
+ rect=Rect2(0,(size.y-button_size.y)*(-rag),size.x,size.y-button_size.y)
+ BUTTON_MODEL.LEFT:
+ rect=Rect2((size.x-button_size.x)*rag+button_size.x,0,size.x-button_size.x,size.y)
+ BUTTON_MODEL.RIGHT:
+ rect=Rect2((size.x-button_size.x)*(-rag),0,size.x-button_size.x,size.y)
+ _:
+ rect=Rect2()
+ else:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ rect=Rect2(0,(size.y)*rag+button_size.y,size.x,size.y)
+ BUTTON_MODEL.END:
+ rect=Rect2(0,(size.y)*(-rag),size.x,size.y)
+ BUTTON_MODEL.LEFT:
+ rect=Rect2((size.x)*rag,0,size.x,size.y)
+ BUTTON_MODEL.RIGHT:
+ rect=Rect2((size.x)*(-rag),0,size.x,size.y)
+ _:
+ rect=Rect2()
+
+
+ pass
+ for i in get_children():
+ if i is Control:
+ fit_child_in_rect(i,rect)
+
+##更改窗口的开闭状态,使用动画
+func change_open(opened:bool=false):
+ if opened==is_open:
+ return
+ if now_keep_tween!=null:
+ now_keep_tween.kill()
+ if !opened:
+ if is_auto_hide:
+ hide_timer.start(animation_time)
+ pass
+ now_keep_tween=create_tween()
+ now_keep_tween.set_trans(animation_model)
+ now_keep_tween.tween_property(self,"rag",1,animation_time)
+ else:
+ now_keep_tween=create_tween()
+ now_keep_tween.set_trans(animation_model)
+ now_keep_tween.tween_property(self,"rag",0,animation_time)
+ if is_auto_hide:
+ show()
+ hide_timer.stop()
+ is_open=opened
+
+
+
+ pass
+##当此节点大小改变时触发
+func resize():
+ fit_side_button()
+ fit_children()
+##重写的虚函数,用于获取当前节点的最小大小
+func _get_minimum_size() -> Vector2:
+ var res:Vector2=Vector2.ZERO
+
+ for i in get_children():
+ if i is Control:
+ var i_size:Vector2=i.get_minimum_size()
+ res=Vector2(max(res.x,i_size.x),max(res.y,i_size.y))
+ if not is_auto_shrink:
+ if !disable_button:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ res=Vector2(max(res.x,button_size.x),res.y+button_size.y)
+ BUTTON_MODEL.END:
+ res=Vector2(max(res.x,button_size.x),res.y+button_size.y)
+ BUTTON_MODEL.LEFT:
+ res=Vector2(res.x+button_size.x,max(res.y,button_size.y))
+ BUTTON_MODEL.RIGHT:
+ res=Vector2(res.x+button_size.x,max(res.y,button_size.y))
+ _:
+ res=Vector2.ZERO
+ else:
+ if !disable_button:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ res=Vector2(max(res.x,button_size.x),res.y*rag+button_size.y)
+ BUTTON_MODEL.END:
+ res=Vector2(max(res.x,button_size.x),res.y*rag+button_size.y)
+ BUTTON_MODEL.LEFT:
+ res=Vector2(res.x*rag+button_size.x,max(res.y,button_size.y))
+ BUTTON_MODEL.RIGHT:
+ res=Vector2(res.x*rag+button_size.x,max(res.y,button_size.y))
+ _:
+ res=Vector2.ZERO
+ else:
+ match button_model:
+ BUTTON_MODEL.TOP:
+ res=Vector2(res.x,res.y*(1-rag))
+ BUTTON_MODEL.END:
+ res=Vector2(res.x*rag,res.y*(1-rag))
+ BUTTON_MODEL.LEFT:
+ res=Vector2(res.x*(1-rag),res.y)
+ BUTTON_MODEL.RIGHT:
+ res=Vector2(res.x*rag,res.y*(1-rag))
+ _:
+ res=Vector2.ZERO
+ pass
+
+ return res
+##当侧边栏按钮大小更改时调用
+func side_button_size_changed()->void:
+ if side_button:
+ var new_size=side_button.get_minimum_size()
+ button_size=Vector2(max(custom_button_size.x,new_size.x),max(custom_button_size.y,new_size.y))
+
+func _process(delta: float) -> void:
+ if Engine.is_editor_hint():
+ fit_children()
+ pass
+func hide_time_out():
+ hide()
diff --git a/tool/state_machine/state.gd b/tool/state_machine/state.gd
new file mode 100644
index 0000000..517612b
--- /dev/null
+++ b/tool/state_machine/state.gd
@@ -0,0 +1,45 @@
+extends Node
+##状态机根节点类
+class_name StateMachine
+##状态机进入时候的状态
+@export var init_state:NodePath="stable_1"##进入时的状态
+@export var owner_player:NodePath="stable_1"##拥有此状态机的节点路径
+@onready var state_list=get_children() ##所拥有的状态节点的数组
+@onready var state_now:State=get_node(init_state) ##当前的状态节点
+@onready var player =get_node(owner_player) ##通过角色路径获取到的节点
+func _ready():
+ set_process(false)
+##启动状态机
+func launch():
+ state_now=get_node(init_state)
+ state_now.enter_state(1)
+ set_process(true)
+func _process(delta):
+ if state_now!=null:
+ state_now.update_state(delta)
+func _physics_process(delta):
+ if state_now!=null:
+ state_now.update_state_phy(delta)
+##判断n节点是否在状态机节点所拥有的状态中
+func is_in_list(n)->bool:
+ if n in state_list:
+ return true
+ return false
+##将状态节点切换为n,并调用对应的init stable方法和exit stable方法,传入s
+func change_state(n:String,s):
+ var change_to=get_node(n)
+ if change_to&&is_in_list(change_to)&&state_now!=change_to:
+ state_now.exit_state()
+ state_now=change_to
+ state_now.enter_state(s)
+
+##获取当前状态的状态名
+func get_state_name()->String:
+ return self.state_now.state_name
+
+func get_state(state_name:String):
+ return self.get_node(state_name)
+func send_message(type:String,n=0):
+ if state_now!=null:
+ state_now.process_message(type,n)
+ pass
diff --git a/tool/state_machine/state_root.gd b/tool/state_machine/state_root.gd
new file mode 100644
index 0000000..5a5ffde
--- /dev/null
+++ b/tool/state_machine/state_root.gd
@@ -0,0 +1,32 @@
+extends Node
+class_name State
+var timer
+@onready var root=get_node("..") as StateMachine
+@export var need_timer:bool=false
+func _ready():
+ if need_timer:
+ timer=Timer.new()
+ timer.set_one_shot(true)
+ self.add_child(timer)
+ timer.timeout.connect(Callable(self,"time_out"))
+
+func enter_state(n):
+ pass
+func update_state(delta):
+ pass
+func update_state_phy(delta):
+ pass
+func exit_state():
+ pass
+func change_to_state(state_name:String,s=0):
+ root.change_state(state_name,s)
+ pass
+
+func time_out():
+ pass
+func get_player():
+ return root.player
+func process_message(type:String,n):
+
+
+ pass