diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/addons/SpritesheetGenerator/Checker.png b/addons/SpritesheetGenerator/Checker.png
new file mode 100644
index 0000000..f147f72
Binary files /dev/null and b/addons/SpritesheetGenerator/Checker.png differ
diff --git a/addons/simple-state/icons/state.png.import b/addons/SpritesheetGenerator/Checker.png.import
similarity index 67%
rename from addons/simple-state/icons/state.png.import
rename to addons/SpritesheetGenerator/Checker.png.import
index 8fb2c43..7bcd8ec 100644
--- a/addons/simple-state/icons/state.png.import
+++ b/addons/SpritesheetGenerator/Checker.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://msu07hn5ewo5"
-path="res://.godot/imported/state.png-fa3dd722682a28f890f138dcc6e162af.ctex"
+uid="uid://bnkl8rujlgv0h"
+path="res://.godot/imported/Checker.png-95b82ca4c05ab143e1e16c56d598421b.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/simple-state/icons/state.png"
-dest_files=["res://.godot/imported/state.png-fa3dd722682a28f890f138dcc6e162af.ctex"]
+source_file="res://addons/SpritesheetGenerator/Checker.png"
+dest_files=["res://.godot/imported/Checker.png-95b82ca4c05ab143e1e16c56d598421b.ctex"]
[params]
diff --git a/addons/SpritesheetGenerator/SpriteSheetGenerator.gd b/addons/SpritesheetGenerator/SpriteSheetGenerator.gd
new file mode 100644
index 0000000..cbb9bbe
--- /dev/null
+++ b/addons/SpritesheetGenerator/SpriteSheetGenerator.gd
@@ -0,0 +1,13 @@
+@tool
+extends EditorPlugin
+
+func _enter_tree() -> void:
+ add_tool_menu_item("Open Spritesheet Generator", run_generator)
+ get_editor_interface().get_command_palette().add_command("Open Spritesheet Generator", "addons/open_spritesheet_generator", run_generator)
+
+func _exit_tree() -> void:
+ remove_tool_menu_item("Open Spritesheet Generator")
+ get_editor_interface().get_command_palette().remove_command("addons/open_spritesheet_generator")
+
+func run_generator():
+ get_editor_interface().play_custom_scene("res://addons/SpritesheetGenerator/SpritesheetGenerator.tscn")
diff --git a/addons/SpritesheetGenerator/SpritesheetFrame.tscn b/addons/SpritesheetGenerator/SpritesheetFrame.tscn
new file mode 100644
index 0000000..790cf62
--- /dev/null
+++ b/addons/SpritesheetGenerator/SpritesheetFrame.tscn
@@ -0,0 +1,77 @@
+[gd_scene load_steps=3 format=3 uid="uid://cd5wndu01c1sn"]
+
+[sub_resource type="StyleBoxFlat" id="2"]
+resource_local_to_scene = true
+bg_color = Color(0, 0.501961, 0.501961, 1)
+
+[sub_resource type="GDScript" id="3"]
+resource_name = "Prefab"
+script/source = "extends PanelContainer
+
+var odd: Vector2
+
+func set_frame_margin(margin: Vector2):
+ $MarginContainer.add_theme_constant_override(&\"margin_left\", margin.x)
+ $MarginContainer.add_theme_constant_override(&\"margin_top\", margin.y)
+
+ margin += odd
+
+ $MarginContainer.add_theme_constant_override(&\"margin_right\", margin.x)
+ $MarginContainer.add_theme_constant_override(&\"margin_bottom\", margin.y)
+
+func set_texture(texture: Texture2D):
+ %TextureRect.texture = texture
+ odd = Vector2(int(get_texture_size().x) % 2, int(get_texture_size().y) % 2)
+
+func get_texture_size() -> Vector2:
+ return %TextureRect.texture.get_size()
+
+func get_position2() -> Vector2:
+ return position + %TextureRect.position
+
+func get_texture_data() -> Image:
+ return %TextureRect.texture.get_image()
+
+func set_display_background(display: bool):
+ get_theme_stylebox(&\"panel\").draw_center = display
+
+func set_background_color(color: Color):
+ get_theme_stylebox(&\"panel\").bg_color = color
+
+func _get_drag_data(p: Vector2):
+ var preview = TextureRect.new()
+ preview.texture = %TextureRect.texture
+ preview.ignore_texture_size = true
+ preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ preview.size = Vector2(64, 64)
+ set_drag_preview(preview)
+ return {type = \"SpritesheetFrame\", node = self}
+
+func _can_drop_data(p: Vector2, data) -> bool:
+ return data is Dictionary and data.get(\"type\", \"\") == \"SpritesheetFrame\"
+
+func _drop_data(p: Vector2, data) -> void:
+ var index = get_index()
+ get_parent().move_child(self, data.node.get_index())
+ get_parent().move_child(data.node, index)
+ get_tree().current_scene.refresh_grid()
+
+func _gui_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton:
+ if event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
+ get_tree().current_scene.remove_frame.call_deferred(self)
+"
+
+[node name="FramePrefab" type="PanelContainer" groups=["frame"]]
+theme_override_styles/panel = SubResource("2")
+script = SubResource("3")
+
+[node name="MarginContainer" type="MarginContainer" parent="."]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="TextureRect" type="TextureRect" parent="MarginContainer"]
+unique_name_in_owner = true
+texture_filter = 1
+layout_mode = 2
+stretch_mode = 4
diff --git a/addons/SpritesheetGenerator/SpritesheetGenerator.tscn b/addons/SpritesheetGenerator/SpritesheetGenerator.tscn
new file mode 100644
index 0000000..c064e03
--- /dev/null
+++ b/addons/SpritesheetGenerator/SpritesheetGenerator.tscn
@@ -0,0 +1,714 @@
+[gd_scene load_steps=5 format=3 uid="uid://bf3b0i8scthbm"]
+
+[ext_resource type="Texture2D" uid="uid://bnkl8rujlgv0h" path="res://addons/SpritesheetGenerator/Checker.png" id="1_hs1uu"]
+
+[sub_resource type="GDScript" id="1"]
+resource_name = "Generator"
+script/source = "extends Control
+
+const SUPPORTED_FORMATS: PackedStringArray = [\"bmp\", \"dds\", \"exr\", \"hdr\", \"jpg\", \"jpeg\", \"png\", \"tga\", \"svg\", \"svgz\", \"webp\"]
+
+@onready var grid := %GridContainer
+
+var file_list: Array
+var image_list: Array
+var texture_list: Array
+
+var images_to_process: Array
+var images_to_texturize: Array
+var first_time := true
+var image_count: int
+var output_path: String
+
+var auto := true
+var margin := Vector2.ONE
+
+var pan_origin: Vector2
+var pan_start: Vector2
+
+signal images_processed
+
+func _enter_tree() -> void:
+ $SplitDialog.hide()
+ $StashDialog.hide()
+
+func _ready():
+ $Status.text = $Status.text % \", \".join(SUPPORTED_FORMATS)
+
+ get_viewport().files_dropped.connect(load_files)
+ grid.minimum_size_changed.connect(refresh_background)
+ set_process(false)
+
+func refresh_background():
+ %Background.custom_minimum_size = grid.get_minimum_size()
+
+func load_files(files: PackedStringArray):
+ file_list.clear()
+ image_list.clear()
+
+ %CustomName.text = \"\"
+ %Reload.disabled = false
+ %SavePNG.disabled = false
+
+ if files.size() == 1 and not FileAccess.file_exists(files[0]):
+ var dir := DirAccess.open(files[0])
+ if not dir:
+ show_error(\"Can't open directory.\")
+ return
+
+ for file in dir.get_files():
+ if file.get_extension() in SUPPORTED_FORMATS:
+ file_list.append(str(dir.get_current_dir().path_join(file)))
+ else:
+ var wrong_count: int
+ for file in files:
+ if file.get_extension() in SUPPORTED_FORMATS:
+ file_list.append(file)
+ else:
+ wrong_count += 1
+
+ if wrong_count > 0:
+ show_error(\"Skipped %s file(s) with unsupported extension.\" % wrong_count)
+
+ if file_list.is_empty():
+ show_error(\"No valid files or directories to process.\")
+ return
+
+ load_images()
+
+func load_images():
+ texture_list.clear()
+
+ for image in grid.get_children():
+ image.free()
+
+ for image in %StashImages.get_children():
+ image.free()
+ update_stash()
+
+ var size_map: Dictionary
+
+ if not file_list.is_empty():
+ image_list = file_list.map(func(file: String):
+ var image := Image.load_from_file(file)
+ if image:
+ image.set_meta(&\"path\", file)
+ return image)
+
+ for image in image_list:
+ if not image:
+ continue
+
+ if not image.get_size() in size_map:
+ size_map[image.get_size()] = []
+ size_map[image.get_size()].append(image)
+
+ var output_name: String
+ var most_common_size: Vector2i
+ var most_common_count: int
+
+ for size in size_map:
+ if size_map[size].size() > most_common_count:
+ most_common_size = size
+ most_common_count = size_map[size].size()
+
+ for image in size_map[most_common_size]:
+ if output_path.is_empty():
+ var path: String = image.get_meta(&\"path\", \"\")
+ output_path = path.get_base_dir()
+ output_name = path.get_base_dir().get_file()
+
+ images_to_process.append(image)
+ size_map.clear()
+
+ if not output_name.is_empty() and %CustomName.text.is_empty():
+ %CustomName.text = output_name
+ update_save_button()
+
+ if images_to_process.size() < file_list.size():
+ show_error(\"Rejected %s image(s) due to size mismatch.\" % (file_list.size() - images_to_process.size()))
+
+ if images_to_process.size() == 1:
+ if file_list.size() > 1:
+ images_to_process.clear()
+ show_error(\"Only one dropped image was valid.\")
+ else:
+ %SplitPreview.texture = ImageTexture.create_from_image(images_to_process[0])
+ $SplitDialog.reset_size()
+ $SplitDialog.popup_centered()
+
+ return
+
+ $Status.show()
+ %CenterContainer.hide()
+
+ image_count = images_to_process.size()
+ %Columns.max_value = image_count
+
+ threshold = %Threshold.value
+ min_x = 9999999
+ min_y = 9999999
+ max_x = -9999999
+ max_y = -9999999
+
+ set_process(true)
+
+ await images_processed
+
+ for texture in texture_list:
+ add_frame(texture)
+
+ toggle_auto(auto)
+ refresh_margin()
+
+ $Status.hide()
+ %CenterContainer.show()
+
+var threshold: float
+var min_x: int
+var min_y: int
+var max_x: int
+var max_y: int
+
+func _process(delta: float) -> void:
+ if not images_to_process.is_empty():
+ var image: Image = images_to_process.pop_front()
+ $Status.text = str(\"Preprocessing image \", image_count - images_to_process.size(), \"/\", image_count)
+
+ for x in image.get_width():
+ for y in image.get_height():
+ if image.get_pixel(x, y).a >= threshold:
+ min_x = mini(min_x, x)
+ min_y = mini(min_y, y)
+ max_x = maxi(max_x, x)
+ max_y = maxi(max_y, y)
+
+ images_to_texturize.append(image)
+ elif not images_to_texturize.is_empty():
+ var rect := Rect2i(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1)
+ var image: Image = images_to_texturize.pop_front()
+ $Status.text = str(\"Creating texture \", image_count - images_to_texturize.size(), \"/\", image_count)
+
+ var true_image := Image.create(rect.size.x, rect.size.y, false, image.get_format())
+ true_image.blit_rect(image, rect, Vector2())
+
+ var texture := ImageTexture.create_from_image(true_image)
+ texture_list.append(texture)
+
+ if images_to_texturize.is_empty():
+ set_process(false)
+ images_processed.emit()
+ if first_time:
+ recenter()
+ first_time = false
+
+func toggle_grid(show: bool) -> void:
+ get_tree().call_group(&\"frame\", &\"set_display_background\", show)
+
+func toggle_auto(button_pressed: bool) -> void:
+ %Columns.editable = not button_pressed
+ auto = button_pressed
+
+ if button_pressed:
+ var best: int
+ var best_score = -9999999
+
+ for i in range(1, image_count + 1):
+ var cols = i
+ var rows = ceili(image_count / float(i))
+
+ var score = image_count - cols * rows - maxi(cols, rows) - rows
+ if score > best_score:
+ best = i
+ best_score = score
+
+ grid.columns = best
+ else:
+ grid.columns = %Columns.value
+ refresh_grid()
+
+func hmargin_changed(value: float) -> void:
+ margin.x = value
+ refresh_margin()
+
+func vmargin_changed(value: float) -> void:
+ margin.y = value
+ refresh_margin()
+
+func refresh_margin():
+ get_tree().call_group(&\"frame\", &\"set_frame_margin\", margin)
+
+func columns_changed(value: float) -> void:
+ grid.columns = value
+ refresh_grid()
+
+func refresh_grid():
+ var coord: Vector2
+ var dark = false
+
+ for rect in grid.get_children():
+ rect.set_background_color(Color(0, 0, 0, 0.2 if dark else 0.1))
+ dark = not dark
+ coord.x += 1
+
+ if coord.x == grid.columns:
+ coord.x = 0
+ coord.y += 1
+ dark = int(coord.y) % 2 == 1
+
+func save_png() -> void:
+ var image_size: Vector2 = grid.get_child(0).get_minimum_size()
+
+ var image := Image.create(image_size.x * grid.columns, image_size.y * (ceil(grid.get_child_count() / float(grid.columns))), false, Image.FORMAT_RGBA8)
+
+ for rect in grid.get_children():
+ image.blit_rect(rect.get_texture_data(), Rect2(Vector2(), image_size), rect.get_position2())
+
+ image.save_png(output_path.path_join(%CustomName.text) + \".png\")
+
+func show_error(text: String):
+ if not %Error.visible:
+ %Error.show()
+ else:
+ %Error.text += \"\\n\"
+ %Error.text += text
+ %Timer.start()
+
+func error_hidden() -> void:
+ %Error.text = \"\"
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventMouseButton:
+ var cc: Control = %CenterContainer
+
+ if event.button_index == MOUSE_BUTTON_MIDDLE:
+ if event.pressed:
+ pan_origin = get_local_mouse_position()
+ pan_start = cc.position
+ else:
+ pan_origin = Vector2()
+
+ if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
+ var lm = cc.get_local_mouse_position()
+ cc.scale -= Vector2.ONE * 0.05
+ if cc.scale.x <= 0:
+ cc.scale = Vector2.ONE * 0.05
+
+ cc.position -= (lm - cc.get_local_mouse_position()) * cc.scale
+ elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
+ var lm = cc.get_local_mouse_position()
+ cc.scale += Vector2.ONE * 0.05
+ cc.position -= (lm - cc.get_local_mouse_position()) * cc.scale
+
+ if event is InputEventMouseMotion:
+ if pan_origin != Vector2():
+ %CenterContainer.position = pan_start + (get_local_mouse_position() - pan_origin)
+
+func recenter() -> void:
+ %CenterContainer.position = get_viewport().size / 2 - Vector2i(%CenterContainer.size) / 2
+ %CenterContainer.scale = Vector2.ONE
+
+func update_split_preview():
+ %SplitPreview.queue_redraw()
+
+func draw_split_preview() -> void:
+ var preview: TextureRect = %SplitPreview
+ var frame_count := Vector2(%SplitX.value, %SplitY.value)
+ var frame_size := preview.size / frame_count
+
+ for x in range(1, frame_count.x):
+ for y in int(frame_count.y):
+ preview.draw_line(frame_size * Vector2(x, y), frame_size * Vector2(x, y + 1), Color.WHITE)
+ preview.draw_line(frame_size * Vector2(x, y) + Vector2.RIGHT, frame_size * Vector2(x, y + 1) + Vector2.RIGHT, Color.BLACK)
+
+ for y in range(1, frame_count.y):
+ for x in int(frame_count.x):
+ preview.draw_line(frame_size * Vector2(x, y), frame_size * Vector2(x + 1, y), Color.WHITE)
+ preview.draw_line(frame_size * Vector2(x, y) + Vector2.DOWN, frame_size * Vector2(x + 1, y) + Vector2.DOWN, Color.BLACK)
+
+func split_spritesheet() -> void:
+ file_list.clear()
+ image_list.clear()
+
+ var image: Image = images_to_process[0]
+ var sub_image_size := image.get_size() / Vector2i(%SplitX.value, %SplitY.value)
+
+ for y in %SplitY.value:
+ for x in %SplitX.value:
+ image_list.append(image.get_region(Rect2i(Vector2i(x, y) * sub_image_size, sub_image_size)))
+
+ images_to_process.clear()
+ load_images()
+
+func remove_frame(frame):
+ var image: Image = frame.get_texture_data()
+ var texture := ImageTexture.create_from_image(image)
+
+ var button := TextureButton.new()
+ button.texture_normal = texture
+ button.custom_minimum_size = Vector2(128, 128)
+ button.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED
+ button.ignore_texture_size = true
+ button.pressed.connect(re_add_image.bind(button), CONNECT_DEFERRED)
+ %StashImages.add_child(button)
+
+ var ref := ReferenceRect.new()
+ button.add_child(ref)
+ ref.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ ref.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ ref.editor_only = false
+
+ frame.free()
+ refresh_grid()
+ update_stash()
+
+func update_stash():
+ %Stash.disabled = %StashImages.get_child_count() == 0
+
+func re_add_image(tb: TextureButton):
+ add_frame(tb.texture_normal)
+ tb.free()
+ refresh_grid()
+ update_stash()
+
+ if %Stash.disabled:
+ $StashDialog.hide()
+
+func add_frame(texture: Texture2D):
+ var rect := preload(\"res://addons/SpritesheetGenerator/SpritesheetFrame.tscn\").instantiate()
+ rect.set_texture(texture)
+ rect.set_display_background(%DisplayGrid.button_pressed)
+ rect.set_frame_margin(margin)
+ grid.add_child(rect)
+
+func update_save_button() -> void:
+ %SavePNG.disabled = %CustomName.text.is_empty()
+"
+
+[sub_resource type="StyleBoxFlat" id="5"]
+content_margin_left = 20.0
+content_margin_top = 20.0
+content_margin_right = 20.0
+content_margin_bottom = 20.0
+bg_color = Color(0, 0, 0, 0.25098)
+
+[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kjgn5"]
+texture = ExtResource("1_hs1uu")
+axis_stretch_horizontal = 1
+axis_stretch_vertical = 1
+
+[node name="Main" type="HBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = SubResource("1")
+
+[node name="MarginContainer" type="PanelContainer" parent="."]
+layout_mode = 2
+theme_override_styles/panel = SubResource("5")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="Label5" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Alpha Threshold"
+horizontal_alignment = 1
+
+[node name="Threshold" type="SpinBox" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+max_value = 1.0
+step = 0.005
+value = 0.9
+
+[node name="Reload" type="Button" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+text = "Reload"
+
+[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Columns"
+horizontal_alignment = 1
+
+[node name="Columns" type="SpinBox" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+min_value = 1.0
+value = 1.0
+editable = false
+
+[node name="Grid" type="CheckButton" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+button_pressed = true
+text = "Auto"
+
+[node name="Label3" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Horizontal Margin"
+horizontal_alignment = 1
+
+[node name="MarginH" type="SpinBox" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+value = 1.0
+suffix = "px"
+
+[node name="Label4" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Vertical Margin"
+horizontal_alignment = 1
+
+[node name="MarginV" type="SpinBox" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+max_value = 128.0
+value = 1.0
+suffix = "px"
+
+[node name="Stash" type="Button" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+text = "Image Stash"
+
+[node name="HSeparator2" type="HSeparator" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Button" type="Button" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Recenter View
+"
+
+[node name="DisplayGrid" type="CheckBox" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+button_pressed = true
+text = "Show Grid"
+
+[node name="HSeparator3" type="HSeparator" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="CustomName" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Image Name"
+
+[node name="SavePNG" type="Button" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+text = "Save PNG"
+
+[node name="Status" type="Label" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+text = "Drop folder or image files here to start.
+
+Images should be of the same size. If their sizes don't match, the generator will try to use the dominating size.
+
+The images will be automatically cropped based on the Alpha Threshold value. Greater value means more exact crop.
+
+Supported formats: %s
+
+If you drop a single image, the generator will instead edit it as spritesheet."
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="View" type="CanvasLayer" parent="."]
+layer = -1
+
+[node name="CenterContainer" type="CenterContainer" parent="View"]
+unique_name_in_owner = true
+visible = false
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+
+[node name="Background" type="ColorRect" parent="View/CenterContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 1
+color = Color(0, 0.501961, 0.501961, 1)
+
+[node name="GridContainer" type="GridContainer" parent="View/CenterContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/h_separation = 0
+theme_override_constants/v_separation = 0
+columns = 3
+
+[node name="VBoxContainer" type="GridContainer" parent="View"]
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -40.0
+offset_top = -40.0
+grow_horizontal = 0
+grow_vertical = 0
+mouse_filter = 2
+columns = 3
+
+[node name="Label" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "LMB"
+horizontal_alignment = 1
+
+[node name="VSeparator" type="VSeparator" parent="View/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label2" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "rearrange images"
+
+[node name="Label3" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "RMB"
+horizontal_alignment = 1
+
+[node name="VSeparator2" type="VSeparator" parent="View/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label4" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "delete images"
+
+[node name="Label5" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "MMB"
+horizontal_alignment = 1
+
+[node name="VSeparator3" type="VSeparator" parent="View/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label6" type="Label" parent="View/VBoxContainer"]
+layout_mode = 2
+text = "pan view"
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="Error" type="Label" parent="CanvasLayer"]
+unique_name_in_owner = true
+modulate = Color(1, 0, 0, 1)
+anchors_preset = 12
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -14.0
+grow_horizontal = 2
+grow_vertical = 0
+size_flags_vertical = 0
+horizontal_alignment = 1
+
+[node name="Timer" type="Timer" parent="CanvasLayer"]
+unique_name_in_owner = true
+wait_time = 5.0
+one_shot = true
+
+[node name="SplitDialog" type="ConfirmationDialog" parent="."]
+title = "Edit Spritesheet"
+position = Vector2i(-500, 0)
+size = Vector2i(272, 343)
+visible = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="SplitDialog"]
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 264.0
+offset_bottom = 294.0
+
+[node name="Label" type="Label" parent="SplitDialog/VBoxContainer"]
+layout_mode = 2
+text = "Split Frames"
+horizontal_alignment = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="SplitDialog/VBoxContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="SplitX" type="SpinBox" parent="SplitDialog/VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+min_value = 1.0
+max_value = 1000.0
+value = 1.0
+select_all_on_focus = true
+
+[node name="Label" type="Label" parent="SplitDialog/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "x"
+
+[node name="SplitY" type="SpinBox" parent="SplitDialog/VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+min_value = 1.0
+max_value = 1000.0
+value = 1.0
+select_all_on_focus = true
+
+[node name="CenterContainer" type="CenterContainer" parent="SplitDialog/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="PanelContainer" type="PanelContainer" parent="SplitDialog/VBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxTexture_kjgn5")
+
+[node name="SplitPreview" type="TextureRect" parent="SplitDialog/VBoxContainer/CenterContainer/PanelContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="StashDialog" type="AcceptDialog" parent="."]
+title = "Image Stash"
+position = Vector2i(-500, 500)
+size = Vector2i(309, 100)
+visible = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="StashDialog"]
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 301.0
+offset_bottom = 51.0
+
+[node name="Label" type="Label" parent="StashDialog/VBoxContainer"]
+layout_mode = 2
+text = "Click frame to re-add it to spritesheet."
+horizontal_alignment = 1
+
+[node name="StashImages" type="HFlowContainer" parent="StashDialog/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[connection signal="pressed" from="MarginContainer/VBoxContainer/Reload" to="." method="load_images"]
+[connection signal="value_changed" from="MarginContainer/VBoxContainer/Columns" to="." method="columns_changed"]
+[connection signal="toggled" from="MarginContainer/VBoxContainer/Grid" to="." method="toggle_auto"]
+[connection signal="value_changed" from="MarginContainer/VBoxContainer/MarginH" to="." method="hmargin_changed"]
+[connection signal="value_changed" from="MarginContainer/VBoxContainer/MarginV" to="." method="vmargin_changed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/Stash" to="StashDialog" method="popup_centered_ratio" binds= [0.5]]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/Button" to="." method="recenter"]
+[connection signal="toggled" from="MarginContainer/VBoxContainer/DisplayGrid" to="." method="toggle_grid"]
+[connection signal="text_changed" from="MarginContainer/VBoxContainer/HBoxContainer/CustomName" to="." method="update_save_button" unbinds=1]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/SavePNG" to="." method="save_png"]
+[connection signal="hidden" from="CanvasLayer/Error" to="." method="error_hidden"]
+[connection signal="timeout" from="CanvasLayer/Timer" to="CanvasLayer/Error" method="hide"]
+[connection signal="confirmed" from="SplitDialog" to="." method="split_spritesheet"]
+[connection signal="value_changed" from="SplitDialog/VBoxContainer/HBoxContainer/SplitX" to="." method="update_split_preview" unbinds=1]
+[connection signal="value_changed" from="SplitDialog/VBoxContainer/HBoxContainer/SplitY" to="." method="update_split_preview" unbinds=1]
+[connection signal="draw" from="SplitDialog/VBoxContainer/CenterContainer/PanelContainer/SplitPreview" to="." method="draw_split_preview"]
diff --git a/addons/SpritesheetGenerator/plugin.cfg b/addons/SpritesheetGenerator/plugin.cfg
new file mode 100644
index 0000000..08a28bf
--- /dev/null
+++ b/addons/SpritesheetGenerator/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Spritesheet Generator"
+description="Generates cropped spritesheets from multiple images."
+author="KoBeWi"
+version="1.2"
+script="SpriteSheetGenerator.gd"
diff --git a/addons/fontawesome/All.gd b/addons/fontawesome/All.gd
new file mode 100644
index 0000000..4ef5a8c
--- /dev/null
+++ b/addons/fontawesome/All.gd
@@ -0,0 +1,2029 @@
+## AUTOGENERATED BY dump_eet.html, DO NOT MODIFY!!!
+const all: Dictionary = {
+ "regular": {
+ "trash-can": "\uf2ed",
+ "message": "\uf27a",
+ "file-lines": "\uf15c",
+ "calendar-days": "\uf073",
+ "hand-point-right": "\uf0a4",
+ "face-smile-beam": "\uf5b8",
+ "face-grin-stars": "\uf587",
+ "address-book": "\uf2b9",
+ "comments": "\uf086",
+ "paste": "\uf0ea",
+ "face-grin-tongue-squint": "\uf58a",
+ "face-flushed": "\uf579",
+ "square-caret-right": "\uf152",
+ "square-minus": "\uf146",
+ "compass": "\uf14e",
+ "square-caret-down": "\uf150",
+ "face-kiss-beam": "\uf597",
+ "lightbulb": "\uf0eb",
+ "flag": "\uf024",
+ "square-check": "\uf14a",
+ "circle-dot": "\uf192",
+ "face-dizzy": "\uf567",
+ "futbol": "\uf1e3",
+ "pen-to-square": "\uf044",
+ "hourglass-half": "\uf252",
+ "eye-slash": "\uf070",
+ "hand": "\uf256",
+ "hand-spock": "\uf259",
+ "face-kiss": "\uf596",
+ "face-grin-tongue": "\uf589",
+ "chess-bishop": "\uf43a",
+ "face-grin-wink": "\uf58c",
+ "face-grin-wide": "\uf581",
+ "face-frown-open": "\uf57a",
+ "hand-point-up": "\uf0a6",
+ "bookmark": "\uf02e",
+ "hand-point-down": "\uf0a7",
+ "folder": "\uf07b",
+ "user": "\uf007",
+ "square-caret-left": "\uf191",
+ "star": "\uf005",
+ "chess-knight": "\uf441",
+ "face-laugh-squint": "\uf59b",
+ "face-laugh": "\uf599",
+ "folder-open": "\uf07c",
+ "clipboard": "\uf328",
+ "chess-queen": "\uf445",
+ "hand-back-fist": "\uf255",
+ "square-caret-up": "\uf151",
+ "chart-bar": "\uf080",
+ "window-restore": "\uf2d2",
+ "square-plus": "\uf0fe",
+ "image": "\uf03e",
+ "folder-closed": "\ue185",
+ "lemon": "\uf094",
+ "handshake": "\uf2b5",
+ "gem": "\uf3a5",
+ "circle-play": "\uf144",
+ "circle-check": "\uf058",
+ "circle-stop": "\uf28d",
+ "id-badge": "\uf2c1",
+ "face-laugh-beam": "\uf59a",
+ "registered": "\uf25d",
+ "address-card": "\uf2bb",
+ "face-tired": "\uf5c8",
+ "font-awesome": "\uf2b4",
+ "face-smile-wink": "\uf4da",
+ "file-word": "\uf1c2",
+ "file-powerpoint": "\uf1c4",
+ "envelope-open": "\uf2b6",
+ "file-zipper": "\uf1c6",
+ "square": "\uf0c8",
+ "snowflake": "\uf2dc",
+ "newspaper": "\uf1ea",
+ "face-kiss-wink-heart": "\uf598",
+ "star-half-stroke": "\uf5c0",
+ "file-excel": "\uf1c3",
+ "face-grin-beam": "\uf582",
+ "object-ungroup": "\uf248",
+ "circle-right": "\uf35a",
+ "face-rolling-eyes": "\uf5a5",
+ "object-group": "\uf247",
+ "heart": "\uf004",
+ "face-surprise": "\uf5c2",
+ "circle-pause": "\uf28b",
+ "circle": "\uf111",
+ "circle-up": "\uf35b",
+ "file-audio": "\uf1c7",
+ "file-image": "\uf1c5",
+ "circle-question": "\uf059",
+ "face-meh-blank": "\uf5a4",
+ "eye": "\uf06e",
+ "face-sad-cry": "\uf5b3",
+ "file-code": "\uf1c9",
+ "window-maximize": "\uf2d0",
+ "face-frown": "\uf119",
+ "floppy-disk": "\uf0c7",
+ "comment-dots": "\uf4ad",
+ "face-grin-squint": "\uf585",
+ "hand-pointer": "\uf25a",
+ "hand-scissors": "\uf257",
+ "face-grin-tears": "\uf588",
+ "calendar-xmark": "\uf273",
+ "file-video": "\uf1c8",
+ "file-pdf": "\uf1c1",
+ "comment": "\uf075",
+ "envelope": "\uf0e0",
+ "hourglass": "\uf254",
+ "calendar-check": "\uf274",
+ "hard-drive": "\uf0a0",
+ "face-grin-squint-tears": "\uf586",
+ "rectangle-list": "\uf022",
+ "calendar-plus": "\uf271",
+ "circle-left": "\uf359",
+ "money-bill-1": "\uf3d1",
+ "clock": "\uf017",
+ "keyboard": "\uf11c",
+ "closed-captioning": "\uf20a",
+ "images": "\uf302",
+ "face-grin": "\uf580",
+ "face-meh": "\uf11a",
+ "id-card": "\uf2c2",
+ "sun": "\uf185",
+ "face-laugh-wink": "\uf59c",
+ "circle-down": "\uf358",
+ "thumbs-down": "\uf165",
+ "chess-pawn": "\uf443",
+ "credit-card": "\uf09d",
+ "bell": "\uf0f3",
+ "file": "\uf15b",
+ "hospital": "\uf0f8",
+ "chess-rook": "\uf447",
+ "star-half": "\uf089",
+ "chess-king": "\uf43f",
+ "circle-user": "\uf2bd",
+ "copy": "\uf0c5",
+ "share-from-square": "\uf14d",
+ "copyright": "\uf1f9",
+ "map": "\uf279",
+ "bell-slash": "\uf1f6",
+ "hand-lizard": "\uf258",
+ "face-smile": "\uf118",
+ "hand-peace": "\uf25b",
+ "face-grin-hearts": "\uf584",
+ "building": "\uf1ad",
+ "face-grin-beam-sweat": "\uf583",
+ "moon": "\uf186",
+ "calendar": "\uf133",
+ "face-grin-tongue-wink": "\uf58b",
+ "clone": "\uf24d",
+ "face-angry": "\uf556",
+ "rectangle-xmark": "\uf410",
+ "paper-plane": "\uf1d8",
+ "life-ring": "\uf1cd",
+ "face-grimace": "\uf57f",
+ "calendar-minus": "\uf272",
+ "circle-xmark": "\uf057",
+ "thumbs-up": "\uf164",
+ "window-minimize": "\uf2d1",
+ "square-full": "\uf45c",
+ "note-sticky": "\uf249",
+ "face-sad-tear": "\uf5b4",
+ "hand-point-left": "\uf0a5",
+ },
+ "brands": {
+ "monero": "\uf3d0",
+ "hooli": "\uf427",
+ "yelp": "\uf1e9",
+ "cc-visa": "\uf1f0",
+ "lastfm": "\uf202",
+ "shopware": "\uf5b5",
+ "creative-commons-nc": "\uf4e8",
+ "aws": "\uf375",
+ "redhat": "\uf7bc",
+ "yoast": "\uf2b1",
+ "cloudflare": "\ue07d",
+ "ups": "\uf7e0",
+ "wpexplorer": "\uf2de",
+ "dyalog": "\uf399",
+ "bity": "\uf37a",
+ "stackpath": "\uf842",
+ "buysellads": "\uf20d",
+ "first-order": "\uf2b0",
+ "modx": "\uf285",
+ "guilded": "\ue07e",
+ "vnv": "\uf40b",
+ "square-js": "\uf3b9",
+ "microsoft": "\uf3ca",
+ "qq": "\uf1d6",
+ "orcid": "\uf8d2",
+ "java": "\uf4e4",
+ "invision": "\uf7b0",
+ "creative-commons-pd-alt": "\uf4ed",
+ "centercode": "\uf380",
+ "glide-g": "\uf2a6",
+ "drupal": "\uf1a9",
+ "hire-a-helper": "\uf3b0",
+ "creative-commons-by": "\uf4e7",
+ "unity": "\ue049",
+ "whmcs": "\uf40d",
+ "rocketchat": "\uf3e8",
+ "vk": "\uf189",
+ "untappd": "\uf405",
+ "mailchimp": "\uf59e",
+ "css3-alt": "\uf38b",
+ "square-reddit": "\uf1a2",
+ "vimeo-v": "\uf27d",
+ "contao": "\uf26d",
+ "square-font-awesome": "\ue5ad",
+ "deskpro": "\uf38f",
+ "sistrix": "\uf3ee",
+ "square-instagram": "\ue055",
+ "battle-net": "\uf835",
+ "the-red-yeti": "\uf69d",
+ "square-hacker-news": "\uf3af",
+ "edge": "\uf282",
+ "napster": "\uf3d2",
+ "square-snapchat": "\uf2ad",
+ "google-plus-g": "\uf0d5",
+ "artstation": "\uf77a",
+ "markdown": "\uf60f",
+ "sourcetree": "\uf7d3",
+ "google-plus": "\uf2b3",
+ "diaspora": "\uf791",
+ "foursquare": "\uf180",
+ "stack-overflow": "\uf16c",
+ "github-alt": "\uf113",
+ "phoenix-squadron": "\uf511",
+ "pagelines": "\uf18c",
+ "algolia": "\uf36c",
+ "red-river": "\uf3e3",
+ "creative-commons-sa": "\uf4ef",
+ "safari": "\uf267",
+ "google": "\uf1a0",
+ "square-font-awesome-stroke": "\uf35c",
+ "atlassian": "\uf77b",
+ "linkedin-in": "\uf0e1",
+ "digital-ocean": "\uf391",
+ "nimblr": "\uf5a8",
+ "chromecast": "\uf838",
+ "evernote": "\uf839",
+ "hacker-news": "\uf1d4",
+ "creative-commons-sampling": "\uf4f0",
+ "adversal": "\uf36a",
+ "creative-commons": "\uf25e",
+ "watchman-monitoring": "\ue087",
+ "fonticons": "\uf280",
+ "weixin": "\uf1d7",
+ "shirtsinbulk": "\uf214",
+ "codepen": "\uf1cb",
+ "git-alt": "\uf841",
+ "lyft": "\uf3c3",
+ "rev": "\uf5b2",
+ "windows": "\uf17a",
+ "wizards-of-the-coast": "\uf730",
+ "square-viadeo": "\uf2aa",
+ "meetup": "\uf2e0",
+ "centos": "\uf789",
+ "adn": "\uf170",
+ "cloudsmith": "\uf384",
+ "pied-piper-alt": "\uf1a8",
+ "square-dribbble": "\uf397",
+ "codiepie": "\uf284",
+ "node": "\uf419",
+ "mix": "\uf3cb",
+ "steam": "\uf1b6",
+ "cc-apple-pay": "\uf416",
+ "scribd": "\uf28a",
+ "openid": "\uf19b",
+ "instalod": "\ue081",
+ "expeditedssl": "\uf23e",
+ "sellcast": "\uf2da",
+ "square-twitter": "\uf081",
+ "r-project": "\uf4f7",
+ "delicious": "\uf1a5",
+ "freebsd": "\uf3a4",
+ "vuejs": "\uf41f",
+ "accusoft": "\uf369",
+ "ioxhost": "\uf208",
+ "fonticons-fi": "\uf3a2",
+ "app-store": "\uf36f",
+ "cc-mastercard": "\uf1f1",
+ "itunes-note": "\uf3b5",
+ "golang": "\ue40f",
+ "kickstarter": "\uf3bb",
+ "grav": "\uf2d6",
+ "weibo": "\uf18a",
+ "uncharted": "\ue084",
+ "firstdraft": "\uf3a1",
+ "square-youtube": "\uf431",
+ "wikipedia-w": "\uf266",
+ "wpressr": "\uf3e4",
+ "angellist": "\uf209",
+ "galactic-republic": "\uf50c",
+ "nfc-directional": "\ue530",
+ "skype": "\uf17e",
+ "joget": "\uf3b7",
+ "fedora": "\uf798",
+ "stripe-s": "\uf42a",
+ "meta": "\ue49b",
+ "laravel": "\uf3bd",
+ "hotjar": "\uf3b1",
+ "bluetooth-b": "\uf294",
+ "sticker-mule": "\uf3f7",
+ "creative-commons-zero": "\uf4f3",
+ "hips": "\uf452",
+ "behance": "\uf1b4",
+ "reddit": "\uf1a1",
+ "discord": "\uf392",
+ "chrome": "\uf268",
+ "app-store-ios": "\uf370",
+ "cc-discover": "\uf1f2",
+ "wpbeginner": "\uf297",
+ "confluence": "\uf78d",
+ "mdb": "\uf8ca",
+ "dochub": "\uf394",
+ "accessible-icon": "\uf368",
+ "ebay": "\uf4f4",
+ "amazon": "\uf270",
+ "unsplash": "\ue07c",
+ "yarn": "\uf7e3",
+ "square-steam": "\uf1b7",
+ "500px": "\uf26e",
+ "square-vimeo": "\uf194",
+ "asymmetrik": "\uf372",
+ "font-awesome": "\uf2b4",
+ "gratipay": "\uf184",
+ "apple": "\uf179",
+ "hive": "\ue07f",
+ "gitkraken": "\uf3a6",
+ "keybase": "\uf4f5",
+ "apple-pay": "\uf415",
+ "padlet": "\ue4a0",
+ "amazon-pay": "\uf42c",
+ "square-github": "\uf092",
+ "stumbleupon": "\uf1a4",
+ "fedex": "\uf797",
+ "phoenix-framework": "\uf3dc",
+ "shopify": "\ue057",
+ "neos": "\uf612",
+ "hackerrank": "\uf5f7",
+ "researchgate": "\uf4f8",
+ "swift": "\uf8e1",
+ "angular": "\uf420",
+ "speakap": "\uf3f3",
+ "angrycreative": "\uf36e",
+ "y-combinator": "\uf23b",
+ "empire": "\uf1d1",
+ "envira": "\uf299",
+ "square-gitlab": "\ue5ae",
+ "studiovinari": "\uf3f8",
+ "pied-piper": "\uf2ae",
+ "wordpress": "\uf19a",
+ "product-hunt": "\uf288",
+ "firefox": "\uf269",
+ "linode": "\uf2b8",
+ "goodreads": "\uf3a8",
+ "square-odnoklassniki": "\uf264",
+ "jsfiddle": "\uf1cc",
+ "sith": "\uf512",
+ "themeisle": "\uf2b2",
+ "page4": "\uf3d7",
+ "hashnode": "\ue499",
+ "react": "\uf41b",
+ "cc-paypal": "\uf1f4",
+ "squarespace": "\uf5be",
+ "cc-stripe": "\uf1f5",
+ "creative-commons-share": "\uf4f2",
+ "bitcoin": "\uf379",
+ "keycdn": "\uf3ba",
+ "opera": "\uf26a",
+ "itch-io": "\uf83a",
+ "umbraco": "\uf8e8",
+ "galactic-senate": "\uf50d",
+ "ubuntu": "\uf7df",
+ "draft2digital": "\uf396",
+ "stripe": "\uf429",
+ "houzz": "\uf27c",
+ "gg": "\uf260",
+ "dhl": "\uf790",
+ "square-pinterest": "\uf0d3",
+ "xing": "\uf168",
+ "blackberry": "\uf37b",
+ "creative-commons-pd": "\uf4ec",
+ "playstation": "\uf3df",
+ "quinscape": "\uf459",
+ "less": "\uf41d",
+ "blogger-b": "\uf37d",
+ "opencart": "\uf23d",
+ "vine": "\uf1ca",
+ "paypal": "\uf1ed",
+ "gitlab": "\uf296",
+ "typo3": "\uf42b",
+ "reddit-alien": "\uf281",
+ "yahoo": "\uf19e",
+ "dailymotion": "\ue052",
+ "affiliatetheme": "\uf36b",
+ "pied-piper-pp": "\uf1a7",
+ "bootstrap": "\uf836",
+ "odnoklassniki": "\uf263",
+ "nfc-symbol": "\ue531",
+ "ethereum": "\uf42e",
+ "speaker-deck": "\uf83c",
+ "creative-commons-nc-eu": "\uf4e9",
+ "patreon": "\uf3d9",
+ "avianex": "\uf374",
+ "ello": "\uf5f1",
+ "gofore": "\uf3a7",
+ "bimobject": "\uf378",
+ "facebook-f": "\uf39e",
+ "square-google-plus": "\uf0d4",
+ "mandalorian": "\uf50f",
+ "first-order-alt": "\uf50a",
+ "osi": "\uf41a",
+ "google-wallet": "\uf1ee",
+ "d-and-d-beyond": "\uf6ca",
+ "periscope": "\uf3da",
+ "fulcrum": "\uf50b",
+ "cloudscale": "\uf383",
+ "forumbee": "\uf211",
+ "mizuni": "\uf3cc",
+ "schlix": "\uf3ea",
+ "square-xing": "\uf169",
+ "bandcamp": "\uf2d5",
+ "wpforms": "\uf298",
+ "cloudversify": "\uf385",
+ "usps": "\uf7e1",
+ "megaport": "\uf5a3",
+ "magento": "\uf3c4",
+ "spotify": "\uf1bc",
+ "optin-monster": "\uf23c",
+ "fly": "\uf417",
+ "aviato": "\uf421",
+ "itunes": "\uf3b4",
+ "cuttlefish": "\uf38c",
+ "blogger": "\uf37c",
+ "flickr": "\uf16e",
+ "viber": "\uf409",
+ "soundcloud": "\uf1be",
+ "digg": "\uf1a6",
+ "tencent-weibo": "\uf1d5",
+ "symfony": "\uf83d",
+ "maxcdn": "\uf136",
+ "etsy": "\uf2d7",
+ "facebook-messenger": "\uf39f",
+ "audible": "\uf373",
+ "think-peaks": "\uf731",
+ "bilibili": "\ue3d9",
+ "erlang": "\uf39d",
+ "cotton-bureau": "\uf89e",
+ "dashcube": "\uf210",
+ "42-group": "\ue080",
+ "stack-exchange": "\uf18d",
+ "elementor": "\uf430",
+ "square-pied-piper": "\ue01e",
+ "creative-commons-nd": "\uf4eb",
+ "palfed": "\uf3d8",
+ "superpowers": "\uf2dd",
+ "resolving": "\uf3e7",
+ "xbox": "\uf412",
+ "searchengin": "\uf3eb",
+ "tiktok": "\ue07b",
+ "square-facebook": "\uf082",
+ "renren": "\uf18b",
+ "linux": "\uf17c",
+ "glide": "\uf2a5",
+ "linkedin": "\uf08c",
+ "hubspot": "\uf3b2",
+ "deploydog": "\uf38e",
+ "twitch": "\uf1e8",
+ "ravelry": "\uf2d9",
+ "mixer": "\ue056",
+ "square-lastfm": "\uf203",
+ "vimeo": "\uf40a",
+ "mendeley": "\uf7b3",
+ "uniregistry": "\uf404",
+ "figma": "\uf799",
+ "creative-commons-remix": "\uf4ee",
+ "cc-amazon-pay": "\uf42d",
+ "dropbox": "\uf16b",
+ "instagram": "\uf16d",
+ "cmplid": "\ue360",
+ "facebook": "\uf09a",
+ "gripfire": "\uf3ac",
+ "jedi-order": "\uf50e",
+ "uikit": "\uf403",
+ "fort-awesome-alt": "\uf3a3",
+ "phabricator": "\uf3db",
+ "ussunnah": "\uf407",
+ "earlybirds": "\uf39a",
+ "trade-federation": "\uf513",
+ "autoprefixer": "\uf41c",
+ "whatsapp": "\uf232",
+ "slideshare": "\uf1e7",
+ "google-play": "\uf3ab",
+ "viadeo": "\uf2a9",
+ "line": "\uf3c0",
+ "google-drive": "\uf3aa",
+ "servicestack": "\uf3ec",
+ "simplybuilt": "\uf215",
+ "bitbucket": "\uf171",
+ "imdb": "\uf2d8",
+ "deezer": "\ue077",
+ "raspberry-pi": "\uf7bb",
+ "jira": "\uf7b1",
+ "docker": "\uf395",
+ "screenpal": "\ue570",
+ "bluetooth": "\uf293",
+ "gitter": "\uf426",
+ "d-and-d": "\uf38d",
+ "microblog": "\ue01a",
+ "cc-diners-club": "\uf24c",
+ "gg-circle": "\uf261",
+ "pied-piper-hat": "\uf4e5",
+ "kickstarter-k": "\uf3bc",
+ "yandex": "\uf413",
+ "readme": "\uf4d5",
+ "html5": "\uf13b",
+ "sellsy": "\uf213",
+ "sass": "\uf41e",
+ "wirsindhandwerk": "\ue2d0",
+ "buromobelexperte": "\uf37f",
+ "salesforce": "\uf83b",
+ "octopus-deploy": "\ue082",
+ "medapps": "\uf3c6",
+ "ns8": "\uf3d5",
+ "pinterest-p": "\uf231",
+ "apper": "\uf371",
+ "fort-awesome": "\uf286",
+ "waze": "\uf83f",
+ "cc-jcb": "\uf24b",
+ "snapchat": "\uf2ab",
+ "fantasy-flight-games": "\uf6dc",
+ "rust": "\ue07a",
+ "wix": "\uf5cf",
+ "square-behance": "\uf1b5",
+ "supple": "\uf3f9",
+ "rebel": "\uf1d0",
+ "css3": "\uf13c",
+ "staylinked": "\uf3f5",
+ "kaggle": "\uf5fa",
+ "space-awesome": "\ue5ac",
+ "deviantart": "\uf1bd",
+ "cpanel": "\uf388",
+ "goodreads-g": "\uf3a9",
+ "square-git": "\uf1d2",
+ "square-tumblr": "\uf174",
+ "trello": "\uf181",
+ "creative-commons-nc-jp": "\uf4ea",
+ "get-pocket": "\uf265",
+ "perbyte": "\ue083",
+ "grunt": "\uf3ad",
+ "weebly": "\uf5cc",
+ "connectdevelop": "\uf20e",
+ "leanpub": "\uf212",
+ "black-tie": "\uf27e",
+ "themeco": "\uf5c6",
+ "python": "\uf3e2",
+ "android": "\uf17b",
+ "bots": "\ue340",
+ "free-code-camp": "\uf2c5",
+ "hornbill": "\uf592",
+ "js": "\uf3b8",
+ "ideal": "\ue013",
+ "git": "\uf1d3",
+ "dev": "\uf6cc",
+ "sketch": "\uf7c6",
+ "yandex-international": "\uf414",
+ "cc-amex": "\uf1f3",
+ "uber": "\uf402",
+ "github": "\uf09b",
+ "php": "\uf457",
+ "alipay": "\uf642",
+ "youtube": "\uf167",
+ "skyatlas": "\uf216",
+ "firefox-browser": "\ue007",
+ "replyd": "\uf3e6",
+ "suse": "\uf7d6",
+ "jenkins": "\uf3b6",
+ "twitter": "\uf099",
+ "rockrms": "\uf3e9",
+ "pinterest": "\uf0d2",
+ "buffer": "\uf837",
+ "npm": "\uf3d4",
+ "yammer": "\uf840",
+ "btc": "\uf15a",
+ "dribbble": "\uf17d",
+ "stumbleupon-circle": "\uf1a3",
+ "internet-explorer": "\uf26b",
+ "stubber": "\ue5c7",
+ "telegram": "\uf2c6",
+ "old-republic": "\uf510",
+ "odysee": "\ue5c6",
+ "square-whatsapp": "\uf40c",
+ "node-js": "\uf3d3",
+ "edge-legacy": "\ue078",
+ "slack": "\uf198",
+ "medrt": "\uf3c8",
+ "usb": "\uf287",
+ "tumblr": "\uf173",
+ "vaadin": "\uf408",
+ "quora": "\uf2c4",
+ "reacteurope": "\uf75d",
+ "medium": "\uf23a",
+ "amilia": "\uf36d",
+ "mixcloud": "\uf289",
+ "flipboard": "\uf44d",
+ "viacoin": "\uf237",
+ "critical-role": "\uf6c9",
+ "sitrox": "\ue44a",
+ "discourse": "\uf393",
+ "joomla": "\uf1aa",
+ "mastodon": "\uf4f6",
+ "airbnb": "\uf834",
+ "wolf-pack-battalion": "\uf514",
+ "buy-n-large": "\uf8a6",
+ "gulp": "\uf3ae",
+ "creative-commons-sampling-plus": "\uf4f1",
+ "strava": "\uf428",
+ "ember": "\uf423",
+ "canadian-maple-leaf": "\uf785",
+ "teamspeak": "\uf4f9",
+ "pushed": "\uf3e1",
+ "wordpress-simple": "\uf411",
+ "nutritionix": "\uf3d6",
+ "wodu": "\ue088",
+ "google-pay": "\ue079",
+ "intercom": "\uf7af",
+ "zhihu": "\uf63f",
+ "korvue": "\uf42f",
+ "pix": "\ue43a",
+ "steam-symbol": "\uf3f6",
+ },
+ "solid": {
+ "0": "\u0030",
+ "1": "\u0031",
+ "2": "\u0032",
+ "3": "\u0033",
+ "4": "\u0034",
+ "5": "\u0035",
+ "6": "\u0036",
+ "7": "\u0037",
+ "8": "\u0038",
+ "9": "\u0039",
+ "fill-drip": "\uf576",
+ "arrows-to-circle": "\ue4bd",
+ "circle-chevron-right": "\uf138",
+ "at": "\u0040",
+ "trash-can": "\uf2ed",
+ "text-height": "\uf034",
+ "user-xmark": "\uf235",
+ "stethoscope": "\uf0f1",
+ "message": "\uf27a",
+ "info": "\uf129",
+ "down-left-and-up-right-to-center": "\uf422",
+ "explosion": "\ue4e9",
+ "file-lines": "\uf15c",
+ "wave-square": "\uf83e",
+ "ring": "\uf70b",
+ "building-un": "\ue4d9",
+ "dice-three": "\uf527",
+ "calendar-days": "\uf073",
+ "anchor-circle-check": "\ue4aa",
+ "building-circle-arrow-right": "\ue4d1",
+ "volleyball": "\uf45f",
+ "arrows-up-to-line": "\ue4c2",
+ "sort-down": "\uf0dd",
+ "circle-minus": "\uf056",
+ "door-open": "\uf52b",
+ "right-from-bracket": "\uf2f5",
+ "atom": "\uf5d2",
+ "soap": "\ue06e",
+ "icons": "\uf86d",
+ "microphone-lines-slash": "\uf539",
+ "bridge-circle-check": "\ue4c9",
+ "pump-medical": "\ue06a",
+ "fingerprint": "\uf577",
+ "hand-point-right": "\uf0a4",
+ "magnifying-glass-location": "\uf689",
+ "forward-step": "\uf051",
+ "face-smile-beam": "\uf5b8",
+ "flag-checkered": "\uf11e",
+ "football": "\uf44e",
+ "school-circle-exclamation": "\ue56c",
+ "crop": "\uf125",
+ "angles-down": "\uf103",
+ "users-rectangle": "\ue594",
+ "people-roof": "\ue537",
+ "people-line": "\ue534",
+ "beer-mug-empty": "\uf0fc",
+ "diagram-predecessor": "\ue477",
+ "arrow-up-long": "\uf176",
+ "fire-flame-simple": "\uf46a",
+ "person": "\uf183",
+ "laptop": "\uf109",
+ "file-csv": "\uf6dd",
+ "menorah": "\uf676",
+ "truck-plane": "\ue58f",
+ "record-vinyl": "\uf8d9",
+ "face-grin-stars": "\uf587",
+ "bong": "\uf55c",
+ "spaghetti-monster-flying": "\uf67b",
+ "arrow-down-up-across-line": "\ue4af",
+ "spoon": "\uf2e5",
+ "jar-wheat": "\ue517",
+ "envelopes-bulk": "\uf674",
+ "file-circle-exclamation": "\ue4eb",
+ "circle-h": "\uf47e",
+ "pager": "\uf815",
+ "address-book": "\uf2b9",
+ "strikethrough": "\uf0cc",
+ "k": "\u004b",
+ "landmark-flag": "\ue51c",
+ "pencil": "\uf303",
+ "backward": "\uf04a",
+ "caret-right": "\uf0da",
+ "comments": "\uf086",
+ "paste": "\uf0ea",
+ "code-pull-request": "\ue13c",
+ "clipboard-list": "\uf46d",
+ "truck-ramp-box": "\uf4de",
+ "user-check": "\uf4fc",
+ "vial-virus": "\ue597",
+ "sheet-plastic": "\ue571",
+ "blog": "\uf781",
+ "user-ninja": "\uf504",
+ "person-arrow-up-from-line": "\ue539",
+ "scroll-torah": "\uf6a0",
+ "broom-ball": "\uf458",
+ "toggle-off": "\uf204",
+ "box-archive": "\uf187",
+ "person-drowning": "\ue545",
+ "arrow-down-9-1": "\uf886",
+ "face-grin-tongue-squint": "\uf58a",
+ "spray-can": "\uf5bd",
+ "truck-monster": "\uf63b",
+ "w": "\u0057",
+ "earth-africa": "\uf57c",
+ "rainbow": "\uf75b",
+ "circle-notch": "\uf1ce",
+ "tablet-screen-button": "\uf3fa",
+ "paw": "\uf1b0",
+ "cloud": "\uf0c2",
+ "trowel-bricks": "\ue58a",
+ "face-flushed": "\uf579",
+ "hospital-user": "\uf80d",
+ "tent-arrow-left-right": "\ue57f",
+ "gavel": "\uf0e3",
+ "binoculars": "\uf1e5",
+ "microphone-slash": "\uf131",
+ "box-tissue": "\ue05b",
+ "motorcycle": "\uf21c",
+ "bell-concierge": "\uf562",
+ "pen-ruler": "\uf5ae",
+ "people-arrows": "\ue068",
+ "mars-and-venus-burst": "\ue523",
+ "square-caret-right": "\uf152",
+ "scissors": "\uf0c4",
+ "sun-plant-wilt": "\ue57a",
+ "toilets-portable": "\ue584",
+ "hockey-puck": "\uf453",
+ "table": "\uf0ce",
+ "magnifying-glass-arrow-right": "\ue521",
+ "tachograph-digital": "\uf566",
+ "users-slash": "\ue073",
+ "clover": "\ue139",
+ "reply": "\uf3e5",
+ "star-and-crescent": "\uf699",
+ "house-fire": "\ue50c",
+ "square-minus": "\uf146",
+ "helicopter": "\uf533",
+ "compass": "\uf14e",
+ "square-caret-down": "\uf150",
+ "file-circle-question": "\ue4ef",
+ "laptop-code": "\uf5fc",
+ "swatchbook": "\uf5c3",
+ "prescription-bottle": "\uf485",
+ "bars": "\uf0c9",
+ "people-group": "\ue533",
+ "hourglass-end": "\uf253",
+ "heart-crack": "\uf7a9",
+ "square-up-right": "\uf360",
+ "face-kiss-beam": "\uf597",
+ "film": "\uf008",
+ "ruler-horizontal": "\uf547",
+ "people-robbery": "\ue536",
+ "lightbulb": "\uf0eb",
+ "caret-left": "\uf0d9",
+ "circle-exclamation": "\uf06a",
+ "school-circle-xmark": "\ue56d",
+ "arrow-right-from-bracket": "\uf08b",
+ "circle-chevron-down": "\uf13a",
+ "unlock-keyhole": "\uf13e",
+ "cloud-showers-heavy": "\uf740",
+ "headphones-simple": "\uf58f",
+ "sitemap": "\uf0e8",
+ "circle-dollar-to-slot": "\uf4b9",
+ "memory": "\uf538",
+ "road-spikes": "\ue568",
+ "fire-burner": "\ue4f1",
+ "flag": "\uf024",
+ "hanukiah": "\uf6e6",
+ "feather": "\uf52d",
+ "volume-low": "\uf027",
+ "comment-slash": "\uf4b3",
+ "cloud-sun-rain": "\uf743",
+ "compress": "\uf066",
+ "wheat-awn": "\ue2cd",
+ "ankh": "\uf644",
+ "hands-holding-child": "\ue4fa",
+ "asterisk": "\u002a",
+ "square-check": "\uf14a",
+ "peseta-sign": "\ue221",
+ "heading": "\uf1dc",
+ "ghost": "\uf6e2",
+ "list": "\uf03a",
+ "square-phone-flip": "\uf87b",
+ "cart-plus": "\uf217",
+ "gamepad": "\uf11b",
+ "circle-dot": "\uf192",
+ "face-dizzy": "\uf567",
+ "egg": "\uf7fb",
+ "house-medical-circle-xmark": "\ue513",
+ "campground": "\uf6bb",
+ "folder-plus": "\uf65e",
+ "futbol": "\uf1e3",
+ "paintbrush": "\uf1fc",
+ "lock": "\uf023",
+ "gas-pump": "\uf52f",
+ "hot-tub-person": "\uf593",
+ "map-location": "\uf59f",
+ "house-flood-water": "\ue50e",
+ "tree": "\uf1bb",
+ "bridge-lock": "\ue4cc",
+ "sack-dollar": "\uf81d",
+ "pen-to-square": "\uf044",
+ "car-side": "\uf5e4",
+ "share-nodes": "\uf1e0",
+ "heart-circle-minus": "\ue4ff",
+ "hourglass-half": "\uf252",
+ "microscope": "\uf610",
+ "sink": "\ue06d",
+ "bag-shopping": "\uf290",
+ "arrow-down-z-a": "\uf881",
+ "mitten": "\uf7b5",
+ "person-rays": "\ue54d",
+ "users": "\uf0c0",
+ "eye-slash": "\uf070",
+ "flask-vial": "\ue4f3",
+ "hand": "\uf256",
+ "om": "\uf679",
+ "worm": "\ue599",
+ "house-circle-xmark": "\ue50b",
+ "plug": "\uf1e6",
+ "chevron-up": "\uf077",
+ "hand-spock": "\uf259",
+ "stopwatch": "\uf2f2",
+ "face-kiss": "\uf596",
+ "bridge-circle-xmark": "\ue4cb",
+ "face-grin-tongue": "\uf589",
+ "chess-bishop": "\uf43a",
+ "face-grin-wink": "\uf58c",
+ "ear-deaf": "\uf2a4",
+ "road-circle-check": "\ue564",
+ "dice-five": "\uf523",
+ "square-rss": "\uf143",
+ "land-mine-on": "\ue51b",
+ "i-cursor": "\uf246",
+ "stamp": "\uf5bf",
+ "stairs": "\ue289",
+ "i": "\u0049",
+ "hryvnia-sign": "\uf6f2",
+ "pills": "\uf484",
+ "face-grin-wide": "\uf581",
+ "tooth": "\uf5c9",
+ "v": "\u0056",
+ "bangladeshi-taka-sign": "\ue2e6",
+ "bicycle": "\uf206",
+ "staff-snake": "\ue579",
+ "head-side-cough-slash": "\ue062",
+ "truck-medical": "\uf0f9",
+ "wheat-awn-circle-exclamation": "\ue598",
+ "snowman": "\uf7d0",
+ "mortar-pestle": "\uf5a7",
+ "road-barrier": "\ue562",
+ "school": "\uf549",
+ "igloo": "\uf7ae",
+ "joint": "\uf595",
+ "angle-right": "\uf105",
+ "horse": "\uf6f0",
+ "q": "\u0051",
+ "g": "\u0047",
+ "notes-medical": "\uf481",
+ "temperature-half": "\uf2c9",
+ "dong-sign": "\ue169",
+ "capsules": "\uf46b",
+ "poo-storm": "\uf75a",
+ "face-frown-open": "\uf57a",
+ "hand-point-up": "\uf0a6",
+ "money-bill": "\uf0d6",
+ "bookmark": "\uf02e",
+ "align-justify": "\uf039",
+ "umbrella-beach": "\uf5ca",
+ "helmet-un": "\ue503",
+ "bullseye": "\uf140",
+ "bacon": "\uf7e5",
+ "hand-point-down": "\uf0a7",
+ "arrow-up-from-bracket": "\ue09a",
+ "folder": "\uf07b",
+ "file-waveform": "\uf478",
+ "radiation": "\uf7b9",
+ "chart-simple": "\ue473",
+ "mars-stroke": "\uf229",
+ "vial": "\uf492",
+ "gauge": "\uf624",
+ "wand-magic-sparkles": "\ue2ca",
+ "e": "\u0045",
+ "pen-clip": "\uf305",
+ "bridge-circle-exclamation": "\ue4ca",
+ "user": "\uf007",
+ "school-circle-check": "\ue56b",
+ "dumpster": "\uf793",
+ "van-shuttle": "\uf5b6",
+ "building-user": "\ue4da",
+ "square-caret-left": "\uf191",
+ "highlighter": "\uf591",
+ "key": "\uf084",
+ "bullhorn": "\uf0a1",
+ "globe": "\uf0ac",
+ "synagogue": "\uf69b",
+ "person-half-dress": "\ue548",
+ "road-bridge": "\ue563",
+ "location-arrow": "\uf124",
+ "c": "\u0043",
+ "tablet-button": "\uf10a",
+ "building-lock": "\ue4d6",
+ "pizza-slice": "\uf818",
+ "money-bill-wave": "\uf53a",
+ "chart-area": "\uf1fe",
+ "house-flag": "\ue50d",
+ "person-circle-minus": "\ue540",
+ "ban": "\uf05e",
+ "camera-rotate": "\ue0d8",
+ "spray-can-sparkles": "\uf5d0",
+ "star": "\uf005",
+ "repeat": "\uf363",
+ "cross": "\uf654",
+ "box": "\uf466",
+ "venus-mars": "\uf228",
+ "arrow-pointer": "\uf245",
+ "maximize": "\uf31e",
+ "charging-station": "\uf5e7",
+ "shapes": "\uf61f",
+ "shuffle": "\uf074",
+ "person-running": "\uf70c",
+ "mobile-retro": "\ue527",
+ "grip-lines-vertical": "\uf7a5",
+ "spider": "\uf717",
+ "hands-bound": "\ue4f9",
+ "file-invoice-dollar": "\uf571",
+ "plane-circle-exclamation": "\ue556",
+ "x-ray": "\uf497",
+ "spell-check": "\uf891",
+ "slash": "\uf715",
+ "computer-mouse": "\uf8cc",
+ "arrow-right-to-bracket": "\uf090",
+ "shop-slash": "\ue070",
+ "server": "\uf233",
+ "virus-covid-slash": "\ue4a9",
+ "shop-lock": "\ue4a5",
+ "hourglass-start": "\uf251",
+ "blender-phone": "\uf6b6",
+ "building-wheat": "\ue4db",
+ "person-breastfeeding": "\ue53a",
+ "right-to-bracket": "\uf2f6",
+ "venus": "\uf221",
+ "passport": "\uf5ab",
+ "heart-pulse": "\uf21e",
+ "people-carry-box": "\uf4ce",
+ "temperature-high": "\uf769",
+ "microchip": "\uf2db",
+ "crown": "\uf521",
+ "weight-hanging": "\uf5cd",
+ "xmarks-lines": "\ue59a",
+ "file-prescription": "\uf572",
+ "weight-scale": "\uf496",
+ "user-group": "\uf500",
+ "arrow-up-a-z": "\uf15e",
+ "chess-knight": "\uf441",
+ "face-laugh-squint": "\uf59b",
+ "wheelchair": "\uf193",
+ "circle-arrow-up": "\uf0aa",
+ "toggle-on": "\uf205",
+ "person-walking": "\uf554",
+ "l": "\u004c",
+ "fire": "\uf06d",
+ "bed-pulse": "\uf487",
+ "shuttle-space": "\uf197",
+ "face-laugh": "\uf599",
+ "folder-open": "\uf07c",
+ "heart-circle-plus": "\ue500",
+ "code-fork": "\ue13b",
+ "city": "\uf64f",
+ "microphone-lines": "\uf3c9",
+ "pepper-hot": "\uf816",
+ "unlock": "\uf09c",
+ "colon-sign": "\ue140",
+ "headset": "\uf590",
+ "store-slash": "\ue071",
+ "road-circle-xmark": "\ue566",
+ "user-minus": "\uf503",
+ "mars-stroke-up": "\uf22a",
+ "champagne-glasses": "\uf79f",
+ "clipboard": "\uf328",
+ "house-circle-exclamation": "\ue50a",
+ "file-arrow-up": "\uf574",
+ "wifi": "\uf1eb",
+ "bath": "\uf2cd",
+ "underline": "\uf0cd",
+ "user-pen": "\uf4ff",
+ "signature": "\uf5b7",
+ "stroopwafel": "\uf551",
+ "bold": "\uf032",
+ "anchor-lock": "\ue4ad",
+ "building-ngo": "\ue4d7",
+ "manat-sign": "\ue1d5",
+ "not-equal": "\uf53e",
+ "border-top-left": "\uf853",
+ "map-location-dot": "\uf5a0",
+ "jedi": "\uf669",
+ "square-poll-vertical": "\uf681",
+ "mug-hot": "\uf7b6",
+ "car-battery": "\uf5df",
+ "gift": "\uf06b",
+ "dice-two": "\uf528",
+ "chess-queen": "\uf445",
+ "glasses": "\uf530",
+ "chess-board": "\uf43c",
+ "building-circle-check": "\ue4d2",
+ "person-chalkboard": "\ue53d",
+ "mars-stroke-right": "\uf22b",
+ "hand-back-fist": "\uf255",
+ "square-caret-up": "\uf151",
+ "cloud-showers-water": "\ue4e4",
+ "chart-bar": "\uf080",
+ "hands-bubbles": "\ue05e",
+ "less-than-equal": "\uf537",
+ "train": "\uf238",
+ "eye-low-vision": "\uf2a8",
+ "crow": "\uf520",
+ "sailboat": "\ue445",
+ "window-restore": "\uf2d2",
+ "square-plus": "\uf0fe",
+ "torii-gate": "\uf6a1",
+ "frog": "\uf52e",
+ "bucket": "\ue4cf",
+ "image": "\uf03e",
+ "microphone": "\uf130",
+ "cow": "\uf6c8",
+ "caret-up": "\uf0d8",
+ "screwdriver": "\uf54a",
+ "folder-closed": "\ue185",
+ "house-tsunami": "\ue515",
+ "square-nfi": "\ue576",
+ "arrow-up-from-ground-water": "\ue4b5",
+ "martini-glass": "\uf57b",
+ "rotate-left": "\uf2ea",
+ "table-columns": "\uf0db",
+ "lemon": "\uf094",
+ "head-side-mask": "\ue063",
+ "handshake": "\uf2b5",
+ "gem": "\uf3a5",
+ "dolly": "\uf472",
+ "smoking": "\uf48d",
+ "minimize": "\uf78c",
+ "monument": "\uf5a6",
+ "snowplow": "\uf7d2",
+ "angles-right": "\uf101",
+ "cannabis": "\uf55f",
+ "circle-play": "\uf144",
+ "tablets": "\uf490",
+ "ethernet": "\uf796",
+ "euro-sign": "\uf153",
+ "chair": "\uf6c0",
+ "circle-check": "\uf058",
+ "circle-stop": "\uf28d",
+ "compass-drafting": "\uf568",
+ "plate-wheat": "\ue55a",
+ "icicles": "\uf7ad",
+ "person-shelter": "\ue54f",
+ "neuter": "\uf22c",
+ "id-badge": "\uf2c1",
+ "marker": "\uf5a1",
+ "face-laugh-beam": "\uf59a",
+ "helicopter-symbol": "\ue502",
+ "universal-access": "\uf29a",
+ "circle-chevron-up": "\uf139",
+ "lari-sign": "\ue1c8",
+ "volcano": "\uf770",
+ "person-walking-dashed-line-arrow-right": "\ue553",
+ "sterling-sign": "\uf154",
+ "viruses": "\ue076",
+ "square-person-confined": "\ue577",
+ "user-tie": "\uf508",
+ "arrow-down-long": "\uf175",
+ "tent-arrow-down-to-line": "\ue57e",
+ "certificate": "\uf0a3",
+ "reply-all": "\uf122",
+ "suitcase": "\uf0f2",
+ "person-skating": "\uf7c5",
+ "filter-circle-dollar": "\uf662",
+ "camera-retro": "\uf083",
+ "circle-arrow-down": "\uf0ab",
+ "file-import": "\uf56f",
+ "square-arrow-up-right": "\uf14c",
+ "box-open": "\uf49e",
+ "scroll": "\uf70e",
+ "spa": "\uf5bb",
+ "location-pin-lock": "\ue51f",
+ "pause": "\uf04c",
+ "hill-avalanche": "\ue507",
+ "temperature-empty": "\uf2cb",
+ "bomb": "\uf1e2",
+ "registered": "\uf25d",
+ "address-card": "\uf2bb",
+ "scale-unbalanced-flip": "\uf516",
+ "subscript": "\uf12c",
+ "diamond-turn-right": "\uf5eb",
+ "burst": "\ue4dc",
+ "house-laptop": "\ue066",
+ "face-tired": "\uf5c8",
+ "money-bills": "\ue1f3",
+ "smog": "\uf75f",
+ "crutch": "\uf7f7",
+ "font-awesome": "\uf2b4",
+ "cloud-arrow-up": "\uf0ee",
+ "palette": "\uf53f",
+ "arrows-turn-right": "\ue4c0",
+ "vest": "\ue085",
+ "ferry": "\ue4ea",
+ "arrows-down-to-people": "\ue4b9",
+ "seedling": "\uf4d8",
+ "left-right": "\uf337",
+ "boxes-packing": "\ue4c7",
+ "circle-arrow-left": "\uf0a8",
+ "group-arrows-rotate": "\ue4f6",
+ "bowl-food": "\ue4c6",
+ "candy-cane": "\uf786",
+ "arrow-down-wide-short": "\uf160",
+ "cloud-bolt": "\uf76c",
+ "text-slash": "\uf87d",
+ "face-smile-wink": "\uf4da",
+ "file-word": "\uf1c2",
+ "file-powerpoint": "\uf1c4",
+ "arrows-left-right": "\uf07e",
+ "house-lock": "\ue510",
+ "cloud-arrow-down": "\uf0ed",
+ "children": "\ue4e1",
+ "chalkboard": "\uf51b",
+ "user-large-slash": "\uf4fa",
+ "envelope-open": "\uf2b6",
+ "handshake-simple-slash": "\ue05f",
+ "mattress-pillow": "\ue525",
+ "guarani-sign": "\ue19a",
+ "arrows-rotate": "\uf021",
+ "fire-extinguisher": "\uf134",
+ "cruzeiro-sign": "\ue152",
+ "greater-than-equal": "\uf532",
+ "shield-halved": "\uf3ed",
+ "book-atlas": "\uf558",
+ "virus": "\ue074",
+ "envelope-circle-check": "\ue4e8",
+ "layer-group": "\uf5fd",
+ "arrows-to-dot": "\ue4be",
+ "archway": "\uf557",
+ "heart-circle-check": "\ue4fd",
+ "house-chimney-crack": "\uf6f1",
+ "file-zipper": "\uf1c6",
+ "square": "\uf0c8",
+ "martini-glass-empty": "\uf000",
+ "couch": "\uf4b8",
+ "cedi-sign": "\ue0df",
+ "italic": "\uf033",
+ "church": "\uf51d",
+ "comments-dollar": "\uf653",
+ "democrat": "\uf747",
+ "z": "\u005a",
+ "person-skiing": "\uf7c9",
+ "road-lock": "\ue567",
+ "a": "\u0041",
+ "temperature-arrow-down": "\ue03f",
+ "feather-pointed": "\uf56b",
+ "p": "\u0050",
+ "snowflake": "\uf2dc",
+ "newspaper": "\uf1ea",
+ "rectangle-ad": "\uf641",
+ "circle-arrow-right": "\uf0a9",
+ "filter-circle-xmark": "\ue17b",
+ "locust": "\ue520",
+ "sort": "\uf0dc",
+ "list-ol": "\uf0cb",
+ "person-dress-burst": "\ue544",
+ "money-check-dollar": "\uf53d",
+ "vector-square": "\uf5cb",
+ "bread-slice": "\uf7ec",
+ "language": "\uf1ab",
+ "face-kiss-wink-heart": "\uf598",
+ "filter": "\uf0b0",
+ "question": "\u003f",
+ "file-signature": "\uf573",
+ "up-down-left-right": "\uf0b2",
+ "house-chimney-user": "\ue065",
+ "hand-holding-heart": "\uf4be",
+ "puzzle-piece": "\uf12e",
+ "money-check": "\uf53c",
+ "star-half-stroke": "\uf5c0",
+ "code": "\uf121",
+ "whiskey-glass": "\uf7a0",
+ "building-circle-exclamation": "\ue4d3",
+ "magnifying-glass-chart": "\ue522",
+ "arrow-up-right-from-square": "\uf08e",
+ "cubes-stacked": "\ue4e6",
+ "won-sign": "\uf159",
+ "virus-covid": "\ue4a8",
+ "austral-sign": "\ue0a9",
+ "f": "\u0046",
+ "leaf": "\uf06c",
+ "road": "\uf018",
+ "taxi": "\uf1ba",
+ "person-circle-plus": "\ue541",
+ "chart-pie": "\uf200",
+ "bolt-lightning": "\ue0b7",
+ "sack-xmark": "\ue56a",
+ "file-excel": "\uf1c3",
+ "file-contract": "\uf56c",
+ "fish-fins": "\ue4f2",
+ "building-flag": "\ue4d5",
+ "face-grin-beam": "\uf582",
+ "object-ungroup": "\uf248",
+ "poop": "\uf619",
+ "location-pin": "\uf041",
+ "kaaba": "\uf66b",
+ "toilet-paper": "\uf71e",
+ "helmet-safety": "\uf807",
+ "eject": "\uf052",
+ "circle-right": "\uf35a",
+ "plane-circle-check": "\ue555",
+ "face-rolling-eyes": "\uf5a5",
+ "object-group": "\uf247",
+ "chart-line": "\uf201",
+ "mask-ventilator": "\ue524",
+ "arrow-right": "\uf061",
+ "signs-post": "\uf277",
+ "cash-register": "\uf788",
+ "person-circle-question": "\ue542",
+ "h": "\u0048",
+ "tarp": "\ue57b",
+ "screwdriver-wrench": "\uf7d9",
+ "arrows-to-eye": "\ue4bf",
+ "plug-circle-bolt": "\ue55b",
+ "heart": "\uf004",
+ "mars-and-venus": "\uf224",
+ "house-user": "\ue1b0",
+ "dumpster-fire": "\uf794",
+ "house-crack": "\ue3b1",
+ "martini-glass-citrus": "\uf561",
+ "face-surprise": "\uf5c2",
+ "bottle-water": "\ue4c5",
+ "circle-pause": "\uf28b",
+ "toilet-paper-slash": "\ue072",
+ "apple-whole": "\uf5d1",
+ "kitchen-set": "\ue51a",
+ "r": "\u0052",
+ "temperature-quarter": "\uf2ca",
+ "cube": "\uf1b2",
+ "bitcoin-sign": "\ue0b4",
+ "shield-dog": "\ue573",
+ "solar-panel": "\uf5ba",
+ "lock-open": "\uf3c1",
+ "elevator": "\ue16d",
+ "money-bill-transfer": "\ue528",
+ "money-bill-trend-up": "\ue529",
+ "house-flood-water-circle-arrow-right": "\ue50f",
+ "square-poll-horizontal": "\uf682",
+ "circle": "\uf111",
+ "backward-fast": "\uf049",
+ "recycle": "\uf1b8",
+ "user-astronaut": "\uf4fb",
+ "plane-slash": "\ue069",
+ "trademark": "\uf25c",
+ "basketball": "\uf434",
+ "satellite-dish": "\uf7c0",
+ "circle-up": "\uf35b",
+ "mobile-screen-button": "\uf3cd",
+ "volume-high": "\uf028",
+ "users-rays": "\ue593",
+ "wallet": "\uf555",
+ "clipboard-check": "\uf46c",
+ "file-audio": "\uf1c7",
+ "burger": "\uf805",
+ "wrench": "\uf0ad",
+ "bugs": "\ue4d0",
+ "rupee-sign": "\uf156",
+ "file-image": "\uf1c5",
+ "circle-question": "\uf059",
+ "plane-departure": "\uf5b0",
+ "handshake-slash": "\ue060",
+ "book-bookmark": "\ue0bb",
+ "code-branch": "\uf126",
+ "hat-cowboy": "\uf8c0",
+ "bridge": "\ue4c8",
+ "phone-flip": "\uf879",
+ "truck-front": "\ue2b7",
+ "cat": "\uf6be",
+ "anchor-circle-exclamation": "\ue4ab",
+ "truck-field": "\ue58d",
+ "route": "\uf4d7",
+ "clipboard-question": "\ue4e3",
+ "panorama": "\ue209",
+ "comment-medical": "\uf7f5",
+ "teeth-open": "\uf62f",
+ "file-circle-minus": "\ue4ed",
+ "tags": "\uf02c",
+ "wine-glass": "\uf4e3",
+ "forward-fast": "\uf050",
+ "face-meh-blank": "\uf5a4",
+ "square-parking": "\uf540",
+ "house-signal": "\ue012",
+ "bars-progress": "\uf828",
+ "faucet-drip": "\ue006",
+ "cart-flatbed": "\uf474",
+ "ban-smoking": "\uf54d",
+ "terminal": "\uf120",
+ "mobile-button": "\uf10b",
+ "house-medical-flag": "\ue514",
+ "basket-shopping": "\uf291",
+ "tape": "\uf4db",
+ "bus-simple": "\uf55e",
+ "eye": "\uf06e",
+ "face-sad-cry": "\uf5b3",
+ "audio-description": "\uf29e",
+ "person-military-to-person": "\ue54c",
+ "file-shield": "\ue4f0",
+ "user-slash": "\uf506",
+ "pen": "\uf304",
+ "tower-observation": "\ue586",
+ "file-code": "\uf1c9",
+ "signal": "\uf012",
+ "bus": "\uf207",
+ "heart-circle-xmark": "\ue501",
+ "house-chimney": "\ue3af",
+ "window-maximize": "\uf2d0",
+ "face-frown": "\uf119",
+ "prescription": "\uf5b1",
+ "shop": "\uf54f",
+ "floppy-disk": "\uf0c7",
+ "vihara": "\uf6a7",
+ "scale-unbalanced": "\uf515",
+ "sort-up": "\uf0de",
+ "comment-dots": "\uf4ad",
+ "plant-wilt": "\ue5aa",
+ "diamond": "\uf219",
+ "face-grin-squint": "\uf585",
+ "hand-holding-dollar": "\uf4c0",
+ "bacterium": "\ue05a",
+ "hand-pointer": "\uf25a",
+ "drum-steelpan": "\uf56a",
+ "hand-scissors": "\uf257",
+ "hands-praying": "\uf684",
+ "arrow-rotate-right": "\uf01e",
+ "biohazard": "\uf780",
+ "location-crosshairs": "\uf601",
+ "mars-double": "\uf227",
+ "child-dress": "\ue59c",
+ "users-between-lines": "\ue591",
+ "lungs-virus": "\ue067",
+ "face-grin-tears": "\uf588",
+ "phone": "\uf095",
+ "calendar-xmark": "\uf273",
+ "child-reaching": "\ue59d",
+ "head-side-virus": "\ue064",
+ "user-gear": "\uf4fe",
+ "arrow-up-1-9": "\uf163",
+ "door-closed": "\uf52a",
+ "shield-virus": "\ue06c",
+ "dice-six": "\uf526",
+ "mosquito-net": "\ue52c",
+ "bridge-water": "\ue4ce",
+ "person-booth": "\uf756",
+ "text-width": "\uf035",
+ "hat-wizard": "\uf6e8",
+ "pen-fancy": "\uf5ac",
+ "person-digging": "\uf85e",
+ "trash": "\uf1f8",
+ "gauge-simple": "\uf629",
+ "book-medical": "\uf7e6",
+ "poo": "\uf2fe",
+ "quote-right": "\uf10e",
+ "shirt": "\uf553",
+ "cubes": "\uf1b3",
+ "divide": "\uf529",
+ "tenge-sign": "\uf7d7",
+ "headphones": "\uf025",
+ "hands-holding": "\uf4c2",
+ "hands-clapping": "\ue1a8",
+ "republican": "\uf75e",
+ "arrow-left": "\uf060",
+ "person-circle-xmark": "\ue543",
+ "ruler": "\uf545",
+ "align-left": "\uf036",
+ "dice-d6": "\uf6d1",
+ "restroom": "\uf7bd",
+ "j": "\u004a",
+ "users-viewfinder": "\ue595",
+ "file-video": "\uf1c8",
+ "up-right-from-square": "\uf35d",
+ "table-cells": "\uf00a",
+ "file-pdf": "\uf1c1",
+ "book-bible": "\uf647",
+ "o": "\u004f",
+ "suitcase-medical": "\uf0fa",
+ "user-secret": "\uf21b",
+ "otter": "\uf700",
+ "person-dress": "\uf182",
+ "comment-dollar": "\uf651",
+ "business-time": "\uf64a",
+ "table-cells-large": "\uf009",
+ "book-tanakh": "\uf827",
+ "phone-volume": "\uf2a0",
+ "hat-cowboy-side": "\uf8c1",
+ "clipboard-user": "\uf7f3",
+ "child": "\uf1ae",
+ "lira-sign": "\uf195",
+ "satellite": "\uf7bf",
+ "plane-lock": "\ue558",
+ "tag": "\uf02b",
+ "comment": "\uf075",
+ "cake-candles": "\uf1fd",
+ "envelope": "\uf0e0",
+ "angles-up": "\uf102",
+ "paperclip": "\uf0c6",
+ "arrow-right-to-city": "\ue4b3",
+ "ribbon": "\uf4d6",
+ "lungs": "\uf604",
+ "arrow-up-9-1": "\uf887",
+ "litecoin-sign": "\ue1d3",
+ "border-none": "\uf850",
+ "circle-nodes": "\ue4e2",
+ "parachute-box": "\uf4cd",
+ "indent": "\uf03c",
+ "truck-field-un": "\ue58e",
+ "hourglass": "\uf254",
+ "mountain": "\uf6fc",
+ "user-doctor": "\uf0f0",
+ "circle-info": "\uf05a",
+ "cloud-meatball": "\uf73b",
+ "camera": "\uf030",
+ "square-virus": "\ue578",
+ "meteor": "\uf753",
+ "car-on": "\ue4dd",
+ "sleigh": "\uf7cc",
+ "arrow-down-1-9": "\uf162",
+ "hand-holding-droplet": "\uf4c1",
+ "water": "\uf773",
+ "calendar-check": "\uf274",
+ "braille": "\uf2a1",
+ "prescription-bottle-medical": "\uf486",
+ "landmark": "\uf66f",
+ "truck": "\uf0d1",
+ "crosshairs": "\uf05b",
+ "person-cane": "\ue53c",
+ "tent": "\ue57d",
+ "vest-patches": "\ue086",
+ "check-double": "\uf560",
+ "arrow-down-a-z": "\uf15d",
+ "money-bill-wheat": "\ue52a",
+ "cookie": "\uf563",
+ "arrow-rotate-left": "\uf0e2",
+ "hard-drive": "\uf0a0",
+ "face-grin-squint-tears": "\uf586",
+ "dumbbell": "\uf44b",
+ "rectangle-list": "\uf022",
+ "tarp-droplet": "\ue57c",
+ "house-medical-circle-check": "\ue511",
+ "person-skiing-nordic": "\uf7ca",
+ "calendar-plus": "\uf271",
+ "plane-arrival": "\uf5af",
+ "circle-left": "\uf359",
+ "train-subway": "\uf239",
+ "chart-gantt": "\ue0e4",
+ "indian-rupee-sign": "\ue1bc",
+ "crop-simple": "\uf565",
+ "money-bill-1": "\uf3d1",
+ "left-long": "\uf30a",
+ "dna": "\uf471",
+ "virus-slash": "\ue075",
+ "minus": "\uf068",
+ "chess": "\uf439",
+ "arrow-left-long": "\uf177",
+ "plug-circle-check": "\ue55c",
+ "street-view": "\uf21d",
+ "franc-sign": "\ue18f",
+ "volume-off": "\uf026",
+ "hands-asl-interpreting": "\uf2a3",
+ "gear": "\uf013",
+ "droplet-slash": "\uf5c7",
+ "mosque": "\uf678",
+ "mosquito": "\ue52b",
+ "star-of-david": "\uf69a",
+ "person-military-rifle": "\ue54b",
+ "cart-shopping": "\uf07a",
+ "vials": "\uf493",
+ "plug-circle-plus": "\ue55f",
+ "place-of-worship": "\uf67f",
+ "grip-vertical": "\uf58e",
+ "arrow-turn-up": "\uf148",
+ "u": "\u0055",
+ "square-root-variable": "\uf698",
+ "clock": "\uf017",
+ "backward-step": "\uf048",
+ "pallet": "\uf482",
+ "faucet": "\ue005",
+ "baseball-bat-ball": "\uf432",
+ "s": "\u0053",
+ "timeline": "\ue29c",
+ "keyboard": "\uf11c",
+ "caret-down": "\uf0d7",
+ "house-chimney-medical": "\uf7f2",
+ "temperature-three-quarters": "\uf2c8",
+ "mobile-screen": "\uf3cf",
+ "plane-up": "\ue22d",
+ "piggy-bank": "\uf4d3",
+ "battery-half": "\uf242",
+ "mountain-city": "\ue52e",
+ "coins": "\uf51e",
+ "khanda": "\uf66d",
+ "sliders": "\uf1de",
+ "folder-tree": "\uf802",
+ "network-wired": "\uf6ff",
+ "map-pin": "\uf276",
+ "hamsa": "\uf665",
+ "cent-sign": "\ue3f5",
+ "flask": "\uf0c3",
+ "person-pregnant": "\ue31e",
+ "wand-sparkles": "\uf72b",
+ "ellipsis-vertical": "\uf142",
+ "ticket": "\uf145",
+ "power-off": "\uf011",
+ "right-long": "\uf30b",
+ "flag-usa": "\uf74d",
+ "laptop-file": "\ue51d",
+ "tty": "\uf1e4",
+ "diagram-next": "\ue476",
+ "person-rifle": "\ue54e",
+ "house-medical-circle-exclamation": "\ue512",
+ "closed-captioning": "\uf20a",
+ "person-hiking": "\uf6ec",
+ "venus-double": "\uf226",
+ "images": "\uf302",
+ "calculator": "\uf1ec",
+ "people-pulling": "\ue535",
+ "n": "\u004e",
+ "cable-car": "\uf7da",
+ "cloud-rain": "\uf73d",
+ "building-circle-xmark": "\ue4d4",
+ "ship": "\uf21a",
+ "arrows-down-to-line": "\ue4b8",
+ "download": "\uf019",
+ "face-grin": "\uf580",
+ "delete-left": "\uf55a",
+ "eye-dropper": "\uf1fb",
+ "file-circle-check": "\ue5a0",
+ "forward": "\uf04e",
+ "mobile": "\uf3ce",
+ "face-meh": "\uf11a",
+ "align-center": "\uf037",
+ "book-skull": "\uf6b7",
+ "id-card": "\uf2c2",
+ "outdent": "\uf03b",
+ "heart-circle-exclamation": "\ue4fe",
+ "house": "\uf015",
+ "calendar-week": "\uf784",
+ "laptop-medical": "\uf812",
+ "b": "\u0042",
+ "file-medical": "\uf477",
+ "dice-one": "\uf525",
+ "kiwi-bird": "\uf535",
+ "arrow-right-arrow-left": "\uf0ec",
+ "rotate-right": "\uf2f9",
+ "utensils": "\uf2e7",
+ "arrow-up-wide-short": "\uf161",
+ "mill-sign": "\ue1ed",
+ "bowl-rice": "\ue2eb",
+ "skull": "\uf54c",
+ "tower-broadcast": "\uf519",
+ "truck-pickup": "\uf63c",
+ "up-long": "\uf30c",
+ "stop": "\uf04d",
+ "code-merge": "\uf387",
+ "upload": "\uf093",
+ "hurricane": "\uf751",
+ "mound": "\ue52d",
+ "toilet-portable": "\ue583",
+ "compact-disc": "\uf51f",
+ "file-arrow-down": "\uf56d",
+ "caravan": "\uf8ff",
+ "shield-cat": "\ue572",
+ "bolt": "\uf0e7",
+ "glass-water": "\ue4f4",
+ "oil-well": "\ue532",
+ "vault": "\ue2c5",
+ "mars": "\uf222",
+ "toilet": "\uf7d8",
+ "plane-circle-xmark": "\ue557",
+ "yen-sign": "\uf157",
+ "ruble-sign": "\uf158",
+ "sun": "\uf185",
+ "guitar": "\uf7a6",
+ "face-laugh-wink": "\uf59c",
+ "horse-head": "\uf7ab",
+ "bore-hole": "\ue4c3",
+ "industry": "\uf275",
+ "circle-down": "\uf358",
+ "arrows-turn-to-dots": "\ue4c1",
+ "florin-sign": "\ue184",
+ "arrow-down-short-wide": "\uf884",
+ "less-than": "\u003c",
+ "angle-down": "\uf107",
+ "car-tunnel": "\ue4de",
+ "head-side-cough": "\ue061",
+ "grip-lines": "\uf7a4",
+ "thumbs-down": "\uf165",
+ "user-lock": "\uf502",
+ "arrow-right-long": "\uf178",
+ "anchor-circle-xmark": "\ue4ac",
+ "ellipsis": "\uf141",
+ "chess-pawn": "\uf443",
+ "kit-medical": "\uf479",
+ "person-through-window": "\ue5a9",
+ "toolbox": "\uf552",
+ "hands-holding-circle": "\ue4fb",
+ "bug": "\uf188",
+ "credit-card": "\uf09d",
+ "car": "\uf1b9",
+ "hand-holding-hand": "\ue4f7",
+ "book-open-reader": "\uf5da",
+ "mountain-sun": "\ue52f",
+ "arrows-left-right-to-line": "\ue4ba",
+ "dice-d20": "\uf6cf",
+ "truck-droplet": "\ue58c",
+ "file-circle-xmark": "\ue5a1",
+ "temperature-arrow-up": "\ue040",
+ "medal": "\uf5a2",
+ "bed": "\uf236",
+ "square-h": "\uf0fd",
+ "podcast": "\uf2ce",
+ "temperature-full": "\uf2c7",
+ "bell": "\uf0f3",
+ "superscript": "\uf12b",
+ "plug-circle-xmark": "\ue560",
+ "star-of-life": "\uf621",
+ "phone-slash": "\uf3dd",
+ "paint-roller": "\uf5aa",
+ "handshake-angle": "\uf4c4",
+ "location-dot": "\uf3c5",
+ "file": "\uf15b",
+ "greater-than": "\u003e",
+ "person-swimming": "\uf5c4",
+ "arrow-down": "\uf063",
+ "droplet": "\uf043",
+ "eraser": "\uf12d",
+ "earth-americas": "\uf57d",
+ "person-burst": "\ue53b",
+ "dove": "\uf4ba",
+ "battery-empty": "\uf244",
+ "socks": "\uf696",
+ "inbox": "\uf01c",
+ "section": "\ue447",
+ "gauge-high": "\uf625",
+ "envelope-open-text": "\uf658",
+ "hospital": "\uf0f8",
+ "wine-bottle": "\uf72f",
+ "chess-rook": "\uf447",
+ "bars-staggered": "\uf550",
+ "dharmachakra": "\uf655",
+ "hotdog": "\uf80f",
+ "person-walking-with-cane": "\uf29d",
+ "drum": "\uf569",
+ "ice-cream": "\uf810",
+ "heart-circle-bolt": "\ue4fc",
+ "fax": "\uf1ac",
+ "paragraph": "\uf1dd",
+ "check-to-slot": "\uf772",
+ "star-half": "\uf089",
+ "boxes-stacked": "\uf468",
+ "link": "\uf0c1",
+ "ear-listen": "\uf2a2",
+ "tree-city": "\ue587",
+ "play": "\uf04b",
+ "font": "\uf031",
+ "rupiah-sign": "\ue23d",
+ "magnifying-glass": "\uf002",
+ "table-tennis-paddle-ball": "\uf45d",
+ "person-dots-from-line": "\uf470",
+ "trash-can-arrow-up": "\uf82a",
+ "naira-sign": "\ue1f6",
+ "cart-arrow-down": "\uf218",
+ "walkie-talkie": "\uf8ef",
+ "file-pen": "\uf31c",
+ "receipt": "\uf543",
+ "square-pen": "\uf14b",
+ "suitcase-rolling": "\uf5c1",
+ "person-circle-exclamation": "\ue53f",
+ "chevron-down": "\uf078",
+ "battery-full": "\uf240",
+ "skull-crossbones": "\uf714",
+ "code-compare": "\ue13a",
+ "list-ul": "\uf0ca",
+ "school-lock": "\ue56f",
+ "tower-cell": "\ue585",
+ "down-long": "\uf309",
+ "ranking-star": "\ue561",
+ "chess-king": "\uf43f",
+ "person-harassing": "\ue549",
+ "brazilian-real-sign": "\ue46c",
+ "landmark-dome": "\uf752",
+ "arrow-up": "\uf062",
+ "tv": "\uf26c",
+ "shrimp": "\ue448",
+ "list-check": "\uf0ae",
+ "jug-detergent": "\ue519",
+ "circle-user": "\uf2bd",
+ "user-shield": "\uf505",
+ "wind": "\uf72e",
+ "car-burst": "\uf5e1",
+ "y": "\u0059",
+ "person-snowboarding": "\uf7ce",
+ "truck-fast": "\uf48b",
+ "fish": "\uf578",
+ "user-graduate": "\uf501",
+ "circle-half-stroke": "\uf042",
+ "clapperboard": "\ue131",
+ "circle-radiation": "\uf7ba",
+ "baseball": "\uf433",
+ "jet-fighter-up": "\ue518",
+ "diagram-project": "\uf542",
+ "copy": "\uf0c5",
+ "volume-xmark": "\uf6a9",
+ "hand-sparkles": "\ue05d",
+ "grip": "\uf58d",
+ "share-from-square": "\uf14d",
+ "child-combatant": "\ue4e0",
+ "gun": "\ue19b",
+ "square-phone": "\uf098",
+ "plus": "\u002b",
+ "expand": "\uf065",
+ "computer": "\ue4e5",
+ "xmark": "\uf00d",
+ "arrows-up-down-left-right": "\uf047",
+ "chalkboard-user": "\uf51c",
+ "peso-sign": "\ue222",
+ "building-shield": "\ue4d8",
+ "baby": "\uf77c",
+ "users-line": "\ue592",
+ "quote-left": "\uf10d",
+ "tractor": "\uf722",
+ "trash-arrow-up": "\uf829",
+ "arrow-down-up-lock": "\ue4b0",
+ "lines-leaning": "\ue51e",
+ "ruler-combined": "\uf546",
+ "copyright": "\uf1f9",
+ "equals": "\u003d",
+ "blender": "\uf517",
+ "teeth": "\uf62e",
+ "shekel-sign": "\uf20b",
+ "map": "\uf279",
+ "rocket": "\uf135",
+ "photo-film": "\uf87c",
+ "folder-minus": "\uf65d",
+ "store": "\uf54e",
+ "arrow-trend-up": "\ue098",
+ "plug-circle-minus": "\ue55e",
+ "sign-hanging": "\uf4d9",
+ "bezier-curve": "\uf55b",
+ "bell-slash": "\uf1f6",
+ "tablet": "\uf3fb",
+ "school-flag": "\ue56e",
+ "fill": "\uf575",
+ "angle-up": "\uf106",
+ "drumstick-bite": "\uf6d7",
+ "holly-berry": "\uf7aa",
+ "chevron-left": "\uf053",
+ "bacteria": "\ue059",
+ "hand-lizard": "\uf258",
+ "notdef": "\ue1fe",
+ "disease": "\uf7fa",
+ "briefcase-medical": "\uf469",
+ "genderless": "\uf22d",
+ "chevron-right": "\uf054",
+ "retweet": "\uf079",
+ "car-rear": "\uf5de",
+ "pump-soap": "\ue06b",
+ "video-slash": "\uf4e2",
+ "battery-quarter": "\uf243",
+ "radio": "\uf8d7",
+ "baby-carriage": "\uf77d",
+ "traffic-light": "\uf637",
+ "thermometer": "\uf491",
+ "vr-cardboard": "\uf729",
+ "hand-middle-finger": "\uf806",
+ "percent": "\u0025",
+ "truck-moving": "\uf4df",
+ "glass-water-droplet": "\ue4f5",
+ "display": "\ue163",
+ "face-smile": "\uf118",
+ "thumbtack": "\uf08d",
+ "trophy": "\uf091",
+ "person-praying": "\uf683",
+ "hammer": "\uf6e3",
+ "hand-peace": "\uf25b",
+ "rotate": "\uf2f1",
+ "spinner": "\uf110",
+ "robot": "\uf544",
+ "peace": "\uf67c",
+ "gears": "\uf085",
+ "warehouse": "\uf494",
+ "arrow-up-right-dots": "\ue4b7",
+ "splotch": "\uf5bc",
+ "face-grin-hearts": "\uf584",
+ "dice-four": "\uf524",
+ "sim-card": "\uf7c4",
+ "transgender": "\uf225",
+ "mercury": "\uf223",
+ "arrow-turn-down": "\uf149",
+ "person-falling-burst": "\ue547",
+ "award": "\uf559",
+ "ticket-simple": "\uf3ff",
+ "building": "\uf1ad",
+ "angles-left": "\uf100",
+ "qrcode": "\uf029",
+ "clock-rotate-left": "\uf1da",
+ "face-grin-beam-sweat": "\uf583",
+ "file-export": "\uf56e",
+ "shield": "\uf132",
+ "arrow-up-short-wide": "\uf885",
+ "house-medical": "\ue3b2",
+ "golf-ball-tee": "\uf450",
+ "circle-chevron-left": "\uf137",
+ "house-chimney-window": "\ue00d",
+ "pen-nib": "\uf5ad",
+ "tent-arrow-turn-left": "\ue580",
+ "tents": "\ue582",
+ "wand-magic": "\uf0d0",
+ "dog": "\uf6d3",
+ "carrot": "\uf787",
+ "moon": "\uf186",
+ "wine-glass-empty": "\uf5ce",
+ "cheese": "\uf7ef",
+ "yin-yang": "\uf6ad",
+ "music": "\uf001",
+ "code-commit": "\uf386",
+ "temperature-low": "\uf76b",
+ "person-biking": "\uf84a",
+ "broom": "\uf51a",
+ "shield-heart": "\ue574",
+ "gopuram": "\uf664",
+ "earth-oceania": "\ue47b",
+ "square-xmark": "\uf2d3",
+ "hashtag": "\u0023",
+ "up-right-and-down-left-from-center": "\uf424",
+ "oil-can": "\uf613",
+ "t": "\u0054",
+ "hippo": "\uf6ed",
+ "chart-column": "\ue0e3",
+ "infinity": "\uf534",
+ "vial-circle-check": "\ue596",
+ "person-arrow-down-to-line": "\ue538",
+ "voicemail": "\uf897",
+ "fan": "\uf863",
+ "person-walking-luggage": "\ue554",
+ "up-down": "\uf338",
+ "cloud-moon-rain": "\uf73c",
+ "calendar": "\uf133",
+ "trailer": "\ue041",
+ "bahai": "\uf666",
+ "sd-card": "\uf7c2",
+ "dragon": "\uf6d5",
+ "shoe-prints": "\uf54b",
+ "circle-plus": "\uf055",
+ "face-grin-tongue-wink": "\uf58b",
+ "hand-holding": "\uf4bd",
+ "plug-circle-exclamation": "\ue55d",
+ "link-slash": "\uf127",
+ "clone": "\uf24d",
+ "person-walking-arrow-loop-left": "\ue551",
+ "arrow-up-z-a": "\uf882",
+ "fire-flame-curved": "\uf7e4",
+ "tornado": "\uf76f",
+ "file-circle-plus": "\ue494",
+ "book-quran": "\uf687",
+ "anchor": "\uf13d",
+ "border-all": "\uf84c",
+ "face-angry": "\uf556",
+ "cookie-bite": "\uf564",
+ "arrow-trend-down": "\ue097",
+ "rss": "\uf09e",
+ "draw-polygon": "\uf5ee",
+ "scale-balanced": "\uf24e",
+ "gauge-simple-high": "\uf62a",
+ "shower": "\uf2cc",
+ "desktop": "\uf390",
+ "m": "\u004d",
+ "table-list": "\uf00b",
+ "comment-sms": "\uf7cd",
+ "book": "\uf02d",
+ "user-plus": "\uf234",
+ "check": "\uf00c",
+ "battery-three-quarters": "\uf241",
+ "house-circle-check": "\ue509",
+ "angle-left": "\uf104",
+ "diagram-successor": "\ue47a",
+ "truck-arrow-right": "\ue58b",
+ "arrows-split-up-and-left": "\ue4bc",
+ "hand-fist": "\uf6de",
+ "cloud-moon": "\uf6c3",
+ "briefcase": "\uf0b1",
+ "person-falling": "\ue546",
+ "image-portrait": "\uf3e0",
+ "user-tag": "\uf507",
+ "rug": "\ue569",
+ "earth-europe": "\uf7a2",
+ "cart-flatbed-suitcase": "\uf59d",
+ "rectangle-xmark": "\uf410",
+ "baht-sign": "\ue0ac",
+ "book-open": "\uf518",
+ "book-journal-whills": "\uf66a",
+ "handcuffs": "\ue4f8",
+ "triangle-exclamation": "\uf071",
+ "database": "\uf1c0",
+ "share": "\uf064",
+ "bottle-droplet": "\ue4c4",
+ "mask-face": "\ue1d7",
+ "hill-rockslide": "\ue508",
+ "right-left": "\uf362",
+ "paper-plane": "\uf1d8",
+ "road-circle-exclamation": "\ue565",
+ "dungeon": "\uf6d9",
+ "align-right": "\uf038",
+ "money-bill-1-wave": "\uf53b",
+ "life-ring": "\uf1cd",
+ "hands": "\uf2a7",
+ "calendar-day": "\uf783",
+ "water-ladder": "\uf5c5",
+ "arrows-up-down": "\uf07d",
+ "face-grimace": "\uf57f",
+ "wheelchair-move": "\ue2ce",
+ "turn-down": "\uf3be",
+ "person-walking-arrow-right": "\ue552",
+ "square-envelope": "\uf199",
+ "dice": "\uf522",
+ "bowling-ball": "\uf436",
+ "brain": "\uf5dc",
+ "bandage": "\uf462",
+ "calendar-minus": "\uf272",
+ "circle-xmark": "\uf057",
+ "gifts": "\uf79c",
+ "hotel": "\uf594",
+ "earth-asia": "\uf57e",
+ "id-card-clip": "\uf47f",
+ "magnifying-glass-plus": "\uf00e",
+ "thumbs-up": "\uf164",
+ "user-clock": "\uf4fd",
+ "hand-dots": "\uf461",
+ "file-invoice": "\uf570",
+ "window-minimize": "\uf2d1",
+ "mug-saucer": "\uf0f4",
+ "brush": "\uf55d",
+ "mask": "\uf6fa",
+ "magnifying-glass-minus": "\uf010",
+ "ruler-vertical": "\uf548",
+ "user-large": "\uf406",
+ "train-tram": "\ue5b4",
+ "user-nurse": "\uf82f",
+ "syringe": "\uf48e",
+ "cloud-sun": "\uf6c4",
+ "stopwatch-20": "\ue06f",
+ "square-full": "\uf45c",
+ "magnet": "\uf076",
+ "jar": "\ue516",
+ "note-sticky": "\uf249",
+ "bug-slash": "\ue490",
+ "arrow-up-from-water-pump": "\ue4b6",
+ "bone": "\uf5d7",
+ "user-injured": "\uf728",
+ "face-sad-tear": "\uf5b4",
+ "plane": "\uf072",
+ "tent-arrows-down": "\ue581",
+ "exclamation": "\u0021",
+ "arrows-spin": "\ue4bb",
+ "print": "\uf02f",
+ "turkish-lira-sign": "\ue2bb",
+ "dollar-sign": "\u0024",
+ "x": "\u0058",
+ "magnifying-glass-dollar": "\uf688",
+ "users-gear": "\uf509",
+ "person-military-pointing": "\ue54a",
+ "building-columns": "\uf19c",
+ "umbrella": "\uf0e9",
+ "trowel": "\ue589",
+ "d": "\u0044",
+ "stapler": "\ue5af",
+ "masks-theater": "\uf630",
+ "kip-sign": "\ue1c4",
+ "hand-point-left": "\uf0a5",
+ "handshake-simple": "\uf4c6",
+ "jet-fighter": "\uf0fb",
+ "square-share-nodes": "\uf1e1",
+ "barcode": "\uf02a",
+ "plus-minus": "\ue43c",
+ "video": "\uf03d",
+ "graduation-cap": "\uf19d",
+ "hand-holding-medical": "\ue05c",
+ "person-circle-check": "\ue53e",
+ "turn-up": "\uf3bf",
+ },
+}
\ No newline at end of file
diff --git a/addons/fontawesome/FontAwesome.gd b/addons/fontawesome/FontAwesome.gd
new file mode 100644
index 0000000..50631eb
--- /dev/null
+++ b/addons/fontawesome/FontAwesome.gd
@@ -0,0 +1,44 @@
+@tool
+extends Label
+
+@export_category("FontAwesome")
+@export_range(1, 16384) var icon_size: int = 16: set = set_icon_size
+@export_enum("solid", "regular", "brands") var icon_type: String = "solid": set = set_icon_type
+@export var icon_name: String = "circle-question": set = set_icon_name
+
+const icon_fonts: Dictionary = {
+ "solid": "res://addons/fontawesome/fonts/fa-solid-900.woff2",
+ "regular": "res://addons/fontawesome/fonts/fa-regular-400.woff2",
+ "brands": "res://addons/fontawesome/fonts/fa-brands-400.woff2"
+}
+
+const cheatsheet: Dictionary = preload("res://addons/fontawesome/All.gd").all
+
+func _init():
+ horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ # disable some things, this is icon not text
+ auto_translate = false
+ localize_numeral_system = false
+
+ set_icon_type(icon_type)
+ set_icon_size(icon_size)
+ set_icon_name(icon_name)
+
+func set_icon_size(new_size: int):
+ icon_size = clamp(new_size, 1, 16384)
+ add_theme_font_size_override("font_size", icon_size)
+ size = Vector2(icon_size, icon_size)
+
+func set_icon_type(new_type: String):
+ icon_type = new_type
+ match icon_type:
+ "solid", "regular", "brands":
+ add_theme_font_override("font", load(icon_fonts[icon_type]))
+
+func set_icon_name(new_name: String):
+ icon_name = new_name
+ var iconcode = ""
+ if icon_name in cheatsheet[icon_type]:
+ iconcode = cheatsheet[icon_type][icon_name]
+ set_text(iconcode)
diff --git a/addons/simple-state/LICENSE.md b/addons/fontawesome/LICENSE.txt
similarity index 96%
rename from addons/simple-state/LICENSE.md
rename to addons/fontawesome/LICENSE.txt
index fbc8e79..01bd21e 100644
--- a/addons/simple-state/LICENSE.md
+++ b/addons/fontawesome/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022 Addons By Aura
+Copyright (c) 2023 LetterN
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/addons/fontawesome/flag-solid.svg b/addons/fontawesome/flag-solid.svg
new file mode 100644
index 0000000..cd62b8c
--- /dev/null
+++ b/addons/fontawesome/flag-solid.svg
@@ -0,0 +1 @@
+
diff --git a/addons/fontawesome/flag-solid.svg.import b/addons/fontawesome/flag-solid.svg.import
new file mode 100644
index 0000000..956141a
--- /dev/null
+++ b/addons/fontawesome/flag-solid.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cdk6bwkag1wre"
+path="res://.godot/imported/flag-solid.svg-f443982cac7d006eea43c772e2428bae.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/fontawesome/flag-solid.svg"
+dest_files=["res://.godot/imported/flag-solid.svg-f443982cac7d006eea43c772e2428bae.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/fontawesome/fonts/LICENSE.txt b/addons/fontawesome/fonts/LICENSE.txt
new file mode 100644
index 0000000..39e18e3
--- /dev/null
+++ b/addons/fontawesome/fonts/LICENSE.txt
@@ -0,0 +1,165 @@
+Fonticons, Inc. (https://fontawesome.com)
+
+--------------------------------------------------------------------------------
+
+Font Awesome Free License
+
+Font Awesome Free is free, open source, and GPL friendly. You can use it for
+commercial projects, open source projects, or really almost whatever you want.
+Full Font Awesome Free license: https://fontawesome.com/license/free.
+
+--------------------------------------------------------------------------------
+
+# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
+
+The Font Awesome Free download is licensed under a Creative Commons
+Attribution 4.0 International License and applies to all icons packaged
+as SVG and JS file types.
+
+--------------------------------------------------------------------------------
+
+# Fonts: SIL OFL 1.1 License
+
+In the Font Awesome Free download, the SIL OFL license applies to all icons
+packaged as web and desktop font files.
+
+Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com)
+with Reserved Font Name: "Font Awesome".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+SIL OPEN FONT LICENSE
+Version 1.1 - 26 February 2007
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting — in part or in whole — any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+# Code: MIT License (https://opensource.org/licenses/MIT)
+
+In the Font Awesome Free download, the MIT license applies to all non-font and
+non-icon files.
+
+Copyright 2023 Fonticons, Inc.
+
+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.
+
+--------------------------------------------------------------------------------
+
+# Attribution
+
+Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
+Awesome Free files already contain embedded comments with sufficient
+attribution, so you shouldn't need to do anything additional when using these
+files normally.
+
+We've kept attribution comments terse, so we ask that you do not actively work
+to remove them from files, especially code. They're a great way for folks to
+learn about Font Awesome.
+
+--------------------------------------------------------------------------------
+
+# Brand Icons
+
+All brand icons are trademarks of their respective owners. The use of these
+trademarks does not indicate endorsement of the trademark holder by Font
+Awesome, nor vice versa. **Please do not use brand logos for any purpose except
+to represent the company, product, or service to which they refer.**
diff --git a/addons/fontawesome/fonts/fa-brands-400.woff2 b/addons/fontawesome/fonts/fa-brands-400.woff2
new file mode 100644
index 0000000..5929101
Binary files /dev/null and b/addons/fontawesome/fonts/fa-brands-400.woff2 differ
diff --git a/addons/fontawesome/fonts/fa-brands-400.woff2.import b/addons/fontawesome/fonts/fa-brands-400.woff2.import
new file mode 100644
index 0000000..38b172a
--- /dev/null
+++ b/addons/fontawesome/fonts/fa-brands-400.woff2.import
@@ -0,0 +1,33 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://clt4puu367e82"
+path="res://.godot/imported/fa-brands-400.woff2-01946f939132baa0e37dbdeac9412933.fontdata"
+
+[deps]
+
+source_file="res://addons/fontawesome/fonts/fa-brands-400.woff2"
+dest_files=["res://.godot/imported/fa-brands-400.woff2-01946f939132baa0e37dbdeac9412933.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/fontawesome/fonts/fa-regular-400.woff2 b/addons/fontawesome/fonts/fa-regular-400.woff2
new file mode 100644
index 0000000..953d554
Binary files /dev/null and b/addons/fontawesome/fonts/fa-regular-400.woff2 differ
diff --git a/addons/fontawesome/fonts/fa-regular-400.woff2.import b/addons/fontawesome/fonts/fa-regular-400.woff2.import
new file mode 100644
index 0000000..cf4d07e
--- /dev/null
+++ b/addons/fontawesome/fonts/fa-regular-400.woff2.import
@@ -0,0 +1,33 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://ew3t30vlpjit"
+path="res://.godot/imported/fa-regular-400.woff2-bb494eebb9050a2fb4b1382e97f43781.fontdata"
+
+[deps]
+
+source_file="res://addons/fontawesome/fonts/fa-regular-400.woff2"
+dest_files=["res://.godot/imported/fa-regular-400.woff2-bb494eebb9050a2fb4b1382e97f43781.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/fontawesome/fonts/fa-solid-900.woff2 b/addons/fontawesome/fonts/fa-solid-900.woff2
new file mode 100644
index 0000000..83433f4
Binary files /dev/null and b/addons/fontawesome/fonts/fa-solid-900.woff2 differ
diff --git a/addons/fontawesome/fonts/fa-solid-900.woff2.import b/addons/fontawesome/fonts/fa-solid-900.woff2.import
new file mode 100644
index 0000000..838bc81
--- /dev/null
+++ b/addons/fontawesome/fonts/fa-solid-900.woff2.import
@@ -0,0 +1,33 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://bkqnxsphaabhw"
+path="res://.godot/imported/fa-solid-900.woff2-a9219e5bf1517e35af668434330a4deb.fontdata"
+
+[deps]
+
+source_file="res://addons/fontawesome/fonts/fa-solid-900.woff2"
+dest_files=["res://.godot/imported/fa-solid-900.woff2-a9219e5bf1517e35af668434330a4deb.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/fontawesome/plugin.cfg b/addons/fontawesome/plugin.cfg
new file mode 100644
index 0000000..4cdc008
--- /dev/null
+++ b/addons/fontawesome/plugin.cfg
@@ -0,0 +1,6 @@
+[plugin]
+name="FontAwesome"
+description="FontAwesome 6 Icons"
+author="LetterN"
+version="6.3.0"
+script="plugin.gd"
diff --git a/addons/fontawesome/plugin.gd b/addons/fontawesome/plugin.gd
new file mode 100644
index 0000000..da01422
--- /dev/null
+++ b/addons/fontawesome/plugin.gd
@@ -0,0 +1,8 @@
+@tool
+extends EditorPlugin
+
+func _enter_tree():
+ add_custom_type("FontAwesome", "Label", preload("res://addons/fontawesome/FontAwesome.gd"), preload("res://addons/fontawesome/flag-solid.svg"))
+
+func _exit_tree():
+ remove_custom_type("FontAwesome")
diff --git a/addons/simple-state/classes/animation_state.gd b/addons/simple-state/classes/animation_state.gd
deleted file mode 100644
index cd3686c..0000000
--- a/addons/simple-state/classes/animation_state.gd
+++ /dev/null
@@ -1,47 +0,0 @@
-@icon("../icons/animation_state.png")
-class_name AnimationState
-extends State
-
-## Plays an animation from the linked [member animation_player].
-## The name of the animation to be played comes from the name of the node.
-
-## Emitted when the animation started by this state has finished playing.
-signal animation_finished
-
-
-@export_range(0, 20, 1, "or_greater")
-## How many times to play before emitting [signal State.choose_new_substate_requested].
-## [b]If set to zero, it will go forever.[/b]
-var loops := 0
-
-
-var _loops_left := 0
-
-
-func _init() -> void:
- set_meta(&"description", "Plays the named animation from the linked AnimationPlayer.")
-
-
-func _on_animation_finished(animation_name: StringName) -> void:
- if animation_name != name: return
- if loops == 0:
- animation_player.play(name)
- elif _loops_left <= 0:
- choose_new_substate_requested.emit()
- else:
- _loops_left -= 1
- animation_player.play(name)
-
-
-func enter(set_target: Node, set_animation_player: AnimationPlayer, set_debug_mode := false) -> void:
- super(set_target, set_animation_player, set_debug_mode)
- assert(animation_player != null, "AnimationPlayer must be set, either directly or by an ancestor.")
- animation_player.animation_finished.connect(_on_animation_finished)
- _loops_left = loops - 1
- animation_player.play(name)
-
-
-func exit() -> void:
- super()
- animation_player.animation_finished.disconnect(_on_animation_finished)
- animation_player.stop()
diff --git a/addons/simple-state/classes/debugger.gd b/addons/simple-state/classes/debugger.gd
deleted file mode 100644
index 9c3c9de..0000000
--- a/addons/simple-state/classes/debugger.gd
+++ /dev/null
@@ -1,139 +0,0 @@
-@icon("../icons/state_machine_debugger.png")
-class_name StateMachineDebugger
-extends Tree
-
-
-## Displays an interactive state tree.
-# This source code is a mess, I'm trying to make it less so.
-
-
-@export
-## Root state machine to reference.
-var state_machine_root : State:
- set(value):
- state_machine_root = value
- _setup_tree()
-
-@export
-## What color to make the item when a state is active.
-var active_color := Color.FOREST_GREEN
-
-@export
-## Forcefully switch states by double-clicking them.
-## Due to its nature, it has the potential to be destructive
-## and/ or not behave completely how one might expect.
-var allow_state_switching := false:
- set(value):
- allow_state_switching = value
- if allow_state_switching:
- item_activated.connect(_on_item_activated)
- else:
- item_activated.disconnect(_on_item_activated)
-
-
-@export_group("Signals", "signal_")
-@export
-## Show when a state emits a relevant signal.
-var signal_show := false:
- set(value):
- signal_show = value
-
- if state_machine_root == null:
- return
-
- if signal_show:
- connect_signals()
- else:
- disconnect_signals()
-
-@export
-## Which signals to connect to on each state, as long as they exist.
-var signal_connections : Array[StringName] = [
- &"entered",
- &"exited",
- &"choose_new_substate_requested",
- &"animation_finished",
-]
-
-@export
-## Delay before hiding signal.
-var signal_hide_delay := 1.0
-
-
-func _init() -> void:
- columns = 2
-
-
-func change_state_by_path(path: NodePath) -> void:
- if not state_machine_root.has_node(path):
- return
- var state := state_machine_root
- for i in path.get_name_count():
- var part := path.get_name(i)
- state = await state.change_state_name(part)
-
-
-func connect_signals(state := state_machine_root) -> void:
- if not state.has_meta(&"tree_item"):
- return
- for signal_name in signal_connections:
- if state.has_signal(signal_name) and not \
- state.is_connected(signal_name, _on_state_signal):
- state.connect(signal_name, _on_state_signal.bind(signal_name, state.get_meta(&"tree_item")))
- for child in(state.get_children() as Array[State]):
- connect_signals(child)
-
-
-func disconnect_signals(state := state_machine_root) -> void:
- for signal_name in signal_connections:
- if state.has_signal(signal_name) and \
- state.is_connected(signal_name, _on_state_signal):
- state.disconnect(signal_name, _on_state_signal)
- for child in (state.get_children() as Array[State]):
- disconnect_signals(child)
-
-
-func _setup_tree(state := state_machine_root, parent_item: TreeItem = null) -> void:
- if state == state_machine_root:
- if get_root() != null:
- disconnect_signals()
- clear()
- if state_machine_root == null:
- return
-# state.print_tree_pretty()
-
- # TODO: add icons
- var item := create_item(parent_item)
- item.set_text(0, state.name)
- item.set_metadata(0, state)
- state.set_meta(&"tree_item", item)
- connect_signals(state)
-
- for child in (state.get_children() as Array[State]):
- _setup_tree(child, item)
-
-
-func _on_item_activated() -> void:
- change_state_by_path(state_machine_root.get_path_to(
- get_selected().get_metadata(0) as State))
-
-
-func _on_state_signal(signal_name: StringName, state_item: TreeItem) -> void:
- match signal_name:
- &"entered":
- for i in columns:
- state_item.set_custom_color(i, active_color)
- &"exited":
- for i in columns:
- state_item.clear_custom_color(i)
-
- if not signal_show:
- return
-
- state_item.set_text(1, signal_name)
- var timer := state_item.get_metadata(1) as SceneTreeTimer
- if timer != null:
- timer.timeout.disconnect(state_item.set_text)
- timer = get_tree().create_timer(signal_hide_delay)
- timer.timeout.connect(state_item.set_text.bind(1, ""))
- state_item.set_metadata(1, timer)
diff --git a/addons/simple-state/classes/random_state.gd b/addons/simple-state/classes/random_state.gd
deleted file mode 100644
index 1e6cbea..0000000
--- a/addons/simple-state/classes/random_state.gd
+++ /dev/null
@@ -1,34 +0,0 @@
-@icon("../icons/random_state.png")
-class_name RandomState
-extends State
-
-## Activates a random one of its substates.
-## Useful in conjuction with [AnimationState] for random idles.
-
-
-@export
-## When one of its children asks for a state change,
-## instead of picking another one itself, it defers that choice to its parent.
-## Allows for nested random states for finer control over flow and probability.
-var defer_choice := false
-
-
-func _init() -> void:
- set_meta(&"description", "Pseudo-randomly picks a state to start.")
-
-
-func _ready() -> void:
- randomize()
- super()
-
-
-# You can define which state is picked automatically (like on enter).
-# If you would like to call it yourself, use the public version (choose_substate).
-func _choose_substate() -> State:
- if get_child_count() == 0:
- return null
- if defer_choice and _active_substate != null:
- choose_new_substate_requested.emit()
- return null
- return get_child(randi() % get_child_count()) as State
-
diff --git a/addons/simple-state/classes/sequence_state.gd b/addons/simple-state/classes/sequence_state.gd
deleted file mode 100644
index 6e7f35b..0000000
--- a/addons/simple-state/classes/sequence_state.gd
+++ /dev/null
@@ -1,43 +0,0 @@
-@icon("../icons/sequence_state.png")
-class_name SequenceState
-extends State
-
-
-## Executes its children in order, one after the other. Like an [Array] in [State] form!
-
-
-@export_range(0, 20, 1, "or_greater")
-## How many times the sequence should be looped through before emitting [signal State.choose_new_substate_requested].
-## [b]If set to zero, it will go forever.[/b]
-var loops := 1
-
-
-var _loops_left := 0
-
-
-func _init() -> void:
- set_meta(&"description", "Starts its children one after the other in order, \
- waiting for each one to be done before starting the next.")
-
-
-# You can define which state is picked automatically (like on [method enter]).
-# If you would like to call it yourself, use the public version ([method choose_substate]).
-func _choose_substate() -> State:
- if _active_substate == null:
- return get_child(0) as State if get_child_count() > 0 else null
-
- if _active_substate.get_index() == get_child_count() - 1:
- if loops == 0:
- return get_child(0) as State
- elif _loops_left == 0:
- choose_new_substate_requested.emit()
- return null
- else:
- _loops_left -= 1
- return get_child(0) as State
- return get_child(_active_substate.get_index() + 1) as State
-
-
-func enter(set_target: Node, set_animation_player: AnimationPlayer, set_debug := false) -> void:
- super(set_target, set_animation_player, set_debug)
- _loops_left = loops - 1
diff --git a/addons/simple-state/classes/state.gd b/addons/simple-state/classes/state.gd
deleted file mode 100644
index ef11ac2..0000000
--- a/addons/simple-state/classes/state.gd
+++ /dev/null
@@ -1,291 +0,0 @@
-@icon("../icons/state.png")
-class_name State
-extends Node
-
-## The bare, basic state. Use it if you want total control over the state-flow.
-##
-## Properties marked as [b](inherited)[/b] are passed to substates,
-## meaning you don't have to set it on each individual state, only the root.
-## You can override it of course, and that will be passed to all of [i]its[/i] children.
-
-
-## Emitted between [method _enter] and [method _after_enter].
-signal entered
-
-## Emitted after [method _exit].
-signal exited
-
-## Emitted between [method _update] and [method _after_update]
-signal updated
-
-## Switched active substates.
-signal active_substate_changed(new: State, old: State)
-
-## A request for the parent to pick a new substate to activate.
-## Mainly used by children of [RandomState], such as an [AnimationState].
-signal choose_new_substate_requested
-
-
-## Active or not.
-enum Status {
- INACTIVE, ## Inactive
- ACTIVE, ## Active
- }
-
-
-@export
-## The node that the states will act upon. [b](inherited)[/b]
-## Doesn't actually get used in the addon scripts, it's just
-## included for your convenience when scripting your own behaviour.
-var target: Node:
- set(value):
- target = value
- if _active_substate != null:
- _active_substate.target = target
-
-@export
-## Where to play animations from. [b](inherited)[/b]
-var animation_player: AnimationPlayer
-
-@export_range(0, 120, 1, "or_greater")
-## How many seconds the state should be active before emitting [signal choose_new_substate_requested].
-## [b]If set to zero, it will go forever.[/b]
-var timer := 0.0
-
-@export
-## Whether to force-restart the chosen substate in the callback for [signal choose_new_substate_requested] if it was already active.
-var force := true
-
-@export
-## The state will not be activated under any circumstances.
-var disabled := false:
- set(value):
- disabled = value
- var root := is_root()
- if root and not disabled:
- enter(target, animation_player, debug_mode)
- elif status == Status.ACTIVE:
- exit()
-
-@export
-## Print a message avery time there is a state change. [b](inherited)[/b]
-var debug_mode := false:
- set(value):
- debug_mode = value
- if _active_substate != null:
- _active_substate.debug_mode = debug_mode
-
-## The status of this state, ie. whether it's running or not.
-var status := Status.INACTIVE
-
-
-# The substate that is currently active, if any.
-var _active_substate: State:
- set(value):
- if _active_substate != null:
- _active_substate.choose_new_substate_requested.disconnect(_on_choose_new_substate_requested)
- active_substate_changed.emit(value, _active_substate)
- _active_substate = value
- if _active_substate != null:
- _active_substate.choose_new_substate_requested.connect(_on_choose_new_substate_requested)
-
-# If a timer is set, the object will be stored here.
-var _timer_object: SceneTreeTimer
-
-
-#########################
-### VIRTUAL METHODS ###
-#########################
-func _init() -> void:
- set_physics_process(false)
- set_meta(&"description", "A bare, basic state - will only ever automatically start its first child.")
-
-
-func _ready() -> void:
- for child in get_children():
- assert(child is State, "A State should not have any children that are not other States.")
- if is_root() and not disabled:
- enter(target, animation_player, debug_mode)
-
-
-func _physics_process(delta: float) -> void:
- if status == Status.INACTIVE:
- set_physics_process(false)
- return
- update(delta)
-
-
-## [b][parents, then children][/b] Called when the state is activated.
-func _enter() -> void:
- pass
-
-
-## [b][children, then parents][/b] Called after the state is activated.
-func _after_enter() -> void:
- pass
-
-
-## [b][parents, then children][/b] Called every physics frame (only when the state is active, of course).
-func _update(delta: float) -> void:
- pass
-
-
-## [b][children, then parents][/b] Called at the end of every physics frame.
-func _after_update(delta: float) -> void:
- pass
-
-
-## [b][parents, then children][/b] Called before the state is deactivated.
-func _before_exit() -> void:
- pass
-
-
-## [b][children, then parents][/b] Called when the state is deactivated.
-func _exit() -> void:
- pass
-
-
-## You can define which state is picked automatically (like on [method enter]).
-## Return `null` to not change substate at all.
-## If you would like to call it yourself, use the public version ([method choose_substate]).
-func _choose_substate() -> State:
- return get_child(0) as State if get_child_count() > 0 else null
-
-
-########################
-### PUBLIC METHODS ###
-########################
-
-## Switch to the specified substate by name. It is just a shortcut to [method change_state_node].
-func change_state_name(name: String, force := false) -> State:
- return await change_state_node(get_node_or_null(name) as State, force)
-
-
-## Switch to the specified substate by node. If it is not a direct child, nothing will happen.
-## If `force`, it will start a state again even if it's already running.
-## It waits for the next [signal updated] to make sure it's not
-## switching all over the place in one tick.
-func change_state_node(node: State, force := false) -> State:
- await updated
- if (
- node == null
- or node.disabled
- or (node.status != Status.INACTIVE and not force)
- or node.get_parent() != self
- ):
- return node
-
- var old := _active_substate
- _active_substate = node
- if old != null:
- old.exit()
- _active_substate.enter(target, animation_player, debug_mode)
-
- if debug_mode:
- print(
- ("FORCE " if force else "") +
- "STATE: " +
- str(get_root().get_parent().get_path_to(_active_substate))
- )
- return _active_substate
-
-
-## Return the currently active substate, if any.
-func get_active_substate() -> State:
- return _active_substate
-
-
-## Public [method _choose_substate].
-func choose_substate() -> State:
- return _choose_substate()
-
-
-## Shortcut for `change_state_node(choose_substate())`.
-func change_to_next_substate(force := false) -> void:
- await change_state_node(choose_substate(), force)
-
-
-## Whether this state is the root of the state tree,
-## ie. it is the common ancestor of all the others.
-func is_root() -> bool:
- # If your parent is not a state, then you are the root.
- return not get_parent() is State
-
-
-## Get the root state.
-func get_root() -> State:
- var node: State = self
- while not node.is_root():
- node = node.get_parent() as State
- return node
-
-
-## Runs [method _enter] and [method _after_enter],
-## not a good idea to call it yourself unless you really know what you're doing.
-func enter(set_target: Node, set_animation_player: AnimationPlayer, set_debug_mode: bool) -> void:
- for child in get_children():
- assert(child is State, "A State should not have any children that are not other States.")
-
- _enter()
- entered.emit()
- status = Status.ACTIVE
- if timer != 0:
- _timer_object = get_tree().create_timer(timer)
- _timer_object.timeout.connect(_on_timer_timeout)
- set_physics_process(is_root())
-
- # Only set them if they're not being overridden
- if target == null:
- target = set_target
- if animation_player == null:
- animation_player = set_animation_player
- if debug_mode == false:
- debug_mode = set_debug_mode
-
- change_to_next_substate()
- _after_enter()
-
-
-## Runs [method _update] and [method _after_update],
-## not a good idea to call it yourself unless you really know what you're doing.
-func update(delta: float) -> void:
- _update(delta)
- updated.emit()
- if _active_substate != null:
- _active_substate.update(delta)
- _after_update(delta)
-
-
-## Runs [method _exit] and [method _before_exit],
-## not a good idea to call it yourself unless you really know what you're doing.
-func exit() -> void:
- _before_exit()
- status = Status.INACTIVE
- if _active_substate != null:
- _active_substate.exit()
- _active_substate = null
-
- if is_instance_valid(_timer_object):
- _timer_object.timeout.disconnect(_on_timer_timeout)
- _timer_object = null
-
- _exit()
- exited.emit()
- set_physics_process(false)
-
-
-#########################
-### PRIVATE METHODS ###
-#########################
-
-
-#################
-### CALLBACKS ###
-#################
-
-func _on_choose_new_substate_requested() -> void:
- change_to_next_substate(force)
-
-
-func _on_timer_timeout() -> void:
- choose_new_substate_requested.emit()
diff --git a/addons/simple-state/demo/demo.gd b/addons/simple-state/demo/demo.gd
deleted file mode 100644
index 47aaa01..0000000
--- a/addons/simple-state/demo/demo.gd
+++ /dev/null
@@ -1,14 +0,0 @@
-extends State
-
-
-const NEXT_STATE_ACTION = "demo_next_state"
-
-
-func _enter() -> void:
- if InputMap.has_action(NEXT_STATE_ACTION):
- return
- var input_event := InputEventKey.new()
- input_event.keycode = KEY_TAB
-
- InputMap.add_action(NEXT_STATE_ACTION)
- InputMap.action_add_event(NEXT_STATE_ACTION, input_event)
diff --git a/addons/simple-state/demo/demo.tscn b/addons/simple-state/demo/demo.tscn
deleted file mode 100644
index e3fe0e2..0000000
--- a/addons/simple-state/demo/demo.tscn
+++ /dev/null
@@ -1,218 +0,0 @@
-[gd_scene load_steps=17 format=3 uid="uid://clnliyc6fmqy6"]
-
-[ext_resource type="Script" path="res://addons/simple-state/classes/debugger.gd" id="1_rqf1w"]
-[ext_resource type="Script" path="res://addons/simple-state/demo/description_box.gd" id="2_gktik"]
-[ext_resource type="Script" path="res://addons/simple-state/classes/sequence_state.gd" id="3_4afa7"]
-[ext_resource type="Script" path="res://addons/simple-state/demo/demo.gd" id="3_x0hcs"]
-[ext_resource type="Script" path="res://addons/simple-state/classes/random_state.gd" id="4_wxjoe"]
-[ext_resource type="Script" path="res://addons/simple-state/demo/emit_next.gd" id="6_kxcgl"]
-
-[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ysqm7"]
-
-[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wxupu"]
-
-[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7gdgn"]
-draw_center = false
-border_width_left = 1
-border_width_top = 1
-border_width_right = 1
-border_width_bottom = 1
-
-[sub_resource type="Animation" id="Animation_5pgem"]
-resource_name = "RESET"
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-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="Animation" id="Animation_mwhj1"]
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-tracks/0/interp = 1
-tracks/0/loop_wrap = true
-tracks/0/keys = {
-"times": PackedFloat32Array(0),
-"transitions": PackedFloat32Array(1),
-"update": 0,
-"values": [Color(0, 0, 0, 1)]
-}
-
-[sub_resource type="Animation" id="Animation_yyph0"]
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-tracks/0/interp = 1
-tracks/0/loop_wrap = true
-tracks/0/keys = {
-"times": PackedFloat32Array(0),
-"transitions": PackedFloat32Array(1),
-"update": 0,
-"values": [Color(0, 0, 1, 1)]
-}
-
-[sub_resource type="Animation" id="Animation_xmkhy"]
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-tracks/0/interp = 1
-tracks/0/loop_wrap = true
-tracks/0/keys = {
-"times": PackedFloat32Array(0),
-"transitions": PackedFloat32Array(1),
-"update": 0,
-"values": [Color(0, 1, 0, 1)]
-}
-
-[sub_resource type="Animation" id="Animation_4pwkk"]
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-tracks/0/interp = 1
-tracks/0/loop_wrap = true
-tracks/0/keys = {
-"times": PackedFloat32Array(0),
-"transitions": PackedFloat32Array(1),
-"update": 0,
-"values": [Color(1, 0, 0, 1)]
-}
-
-[sub_resource type="Animation" id="Animation_gt7hj"]
-tracks/0/type = "value"
-tracks/0/imported = false
-tracks/0/enabled = true
-tracks/0/path = NodePath("ColorRect:color")
-tracks/0/interp = 1
-tracks/0/loop_wrap = true
-tracks/0/keys = {
-"times": PackedFloat32Array(0),
-"transitions": PackedFloat32Array(1),
-"update": 0,
-"values": [Color(1, 1, 0, 1)]
-}
-
-[sub_resource type="AnimationLibrary" id="AnimationLibrary_01hf1"]
-_data = {
-"RESET": SubResource("Animation_5pgem"),
-"black": SubResource("Animation_mwhj1"),
-"blue": SubResource("Animation_yyph0"),
-"green": SubResource("Animation_xmkhy"),
-"red": SubResource("Animation_4pwkk"),
-"yellow": SubResource("Animation_gt7hj")
-}
-
-[node name="SimpleStateDemo" type="Panel"]
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-
-[node name="MarginContainer" type="MarginContainer" parent="."]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-theme_override_constants/margin_left = 10
-theme_override_constants/margin_top = 10
-theme_override_constants/margin_right = 10
-theme_override_constants/margin_bottom = 10
-
-[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
-layout_mode = 2
-theme_override_constants/separation = 10
-
-[node name="StateMachineDebugger" type="Tree" parent="MarginContainer/HBoxContainer" node_paths=PackedStringArray("state_machine_root")]
-layout_mode = 2
-size_flags_horizontal = 3
-theme_override_constants/relationship_line_width = 2
-theme_override_constants/draw_guides = 0
-theme_override_styles/panel = SubResource("StyleBoxEmpty_ysqm7")
-theme_override_styles/focus = SubResource("StyleBoxEmpty_wxupu")
-theme_override_styles/selected = SubResource("StyleBoxFlat_7gdgn")
-theme_override_styles/selected_focus = SubResource("StyleBoxFlat_7gdgn")
-select_mode = 1
-script = ExtResource("1_rqf1w")
-state_machine_root = NodePath("../../Root")
-allow_state_switching = true
-signal_show = true
-
-[node name="DescriptionBox" type="Label" parent="MarginContainer/HBoxContainer" node_paths=PackedStringArray("tree")]
-layout_mode = 2
-size_flags_horizontal = 3
-size_flags_vertical = 1
-autowrap_mode = 3
-script = ExtResource("2_gktik")
-tree = NodePath("../StateMachineDebugger")
-show_descriptions = 2
-
-[node name="InputInfo" type="Label" parent="MarginContainer/HBoxContainer/DescriptionBox"]
-layout_mode = 1
-anchors_preset = 3
-anchor_left = 1.0
-anchor_top = 1.0
-anchor_right = 1.0
-anchor_bottom = 1.0
-offset_left = -349.0
-offset_top = -78.0
-grow_horizontal = 0
-grow_vertical = 0
-text = "MOUSE CLICK on state: select
-DOUBLE MOUSE CLICK on state: start
-TAB: next substate (only has effect on leaves)"
-horizontal_alignment = 2
-vertical_alignment = 2
-
-[node name="ColorRect" type="ColorRect" parent="MarginContainer"]
-custom_minimum_size = Vector2(50, 50)
-layout_mode = 2
-size_flags_horizontal = 0
-size_flags_vertical = 8
-
-[node name="AnimationPlayer" type="AnimationPlayer" parent="MarginContainer"]
-libraries = {
-"": SubResource("AnimationLibrary_01hf1")
-}
-
-[node name="Root" type="Node" parent="MarginContainer" node_paths=PackedStringArray("target", "animation_player")]
-script = ExtResource("3_x0hcs")
-target = NodePath("")
-animation_player = NodePath("../AnimationPlayer")
-
-[node name="SequenceState" type="Node" parent="MarginContainer/Root"]
-script = ExtResource("3_4afa7")
-
-[node name="RandomState" type="Node" parent="MarginContainer/Root/SequenceState"]
-script = ExtResource("4_wxjoe")
-defer_choice = true
-
-[node name="red" type="Node" parent="MarginContainer/Root/SequenceState/RandomState"]
-script = ExtResource("6_kxcgl")
-
-[node name="yellow" type="Node" parent="MarginContainer/Root/SequenceState/RandomState"]
-script = ExtResource("6_kxcgl")
-
-[node name="green" type="Node" parent="MarginContainer/Root/SequenceState"]
-script = ExtResource("6_kxcgl")
-
-[node name="blue" type="Node" parent="MarginContainer/Root/SequenceState"]
-script = ExtResource("6_kxcgl")
-
-[node name="black" type="Node" parent="MarginContainer/Root"]
-script = ExtResource("6_kxcgl")
-timer = 1.0
-metadata/description = "Will never start automatically, due to its parent being only a normal state."
diff --git a/addons/simple-state/demo/description_box.gd b/addons/simple-state/demo/description_box.gd
deleted file mode 100644
index d175105..0000000
--- a/addons/simple-state/demo/description_box.gd
+++ /dev/null
@@ -1,98 +0,0 @@
-extends Label
-
-
-## Mode of description box rendering.
-enum DisplayModes {
- NONE, ## Description box completely hidden.
- ACTIVE, ## Show descriptions of all active states.
- SELECTION, ## Show description of last selected state (also includes manual switches).
- }
-
-
-@export
-## [StateMachineDebugger] to reference.
-var tree : StateMachineDebugger:
- set(value):
- tree = value
- if show_descriptions == DisplayModes.SELECTION and \
- not tree.item_selected.is_connected(_on_tree_item_selected):
- tree.item_selected.connect(_on_tree_item_selected)
-
-
-@export
-## Show a description of a state.
-## Looks for a string metadata value by the name of [code]description[/code] on each state.
-var show_descriptions := DisplayModes.NONE:
- set(value):
- show_descriptions = value
-
- match show_descriptions:
- DisplayModes.NONE:
- visible = false
- DisplayModes.ACTIVE:
- visible = true
- if not is_instance_valid(tree):
- return
- if tree.item_selected.is_connected(_on_tree_item_selected):
- tree.item_selected.disconnect(_on_tree_item_selected)
- DisplayModes.SELECTION:
- visible = true
- if not is_instance_valid(tree):
- return
- if not tree.item_selected.is_connected(_on_tree_item_selected):
- tree.item_selected.connect(_on_tree_item_selected)
-
-
-var _active_states : Array[State] = []
-
-
-# Called when the node enters the scene tree for the first time.
-func _ready() -> void:
- visible = show_descriptions != DisplayModes.NONE
- connect_signals()
-
-
-func connect_signals(state := tree.state_machine_root) -> void:
- if not state.has_meta(&"tree_item"):
- return
- for signal_name in tree.signal_connections:
- if state.has_signal(signal_name) and not \
- state.is_connected(signal_name, _on_state_signal):
- state.connect(signal_name, _on_state_signal.bind(signal_name, state))
- for child in(state.get_children() as Array[State]):
- connect_signals(child)
-
-
-func disconnect_signals(state := tree.state_machine_root) -> void:
- for signal_name in tree.signal_connections:
- if state.has_signal(signal_name) and \
- state.is_connected(signal_name, _on_state_signal):
- state.disconnect(signal_name, _on_state_signal)
- for child in (state.get_children() as Array[State]):
- disconnect_signals(child)
-
-
-func _set_description_from_active_states() -> void:
- text = ""
- for state in _active_states:
- text += ("" if state.is_root() else "\n\n") + \
- state.name as String + \
- ": " + \
- state.get_meta(&"description", "") as String
-
-
-func _on_tree_item_selected() -> void:
- text = tree.get_selected() \
- .get_metadata(0).get_meta(&"description", "")
-
-
-func _on_state_signal(signal_name: StringName, state: State) -> void:
- match signal_name:
- &"entered":
- if show_descriptions == DisplayModes.ACTIVE:
- _active_states.push_back(state)
- _set_description_from_active_states()
- &"exited":
- if show_descriptions == DisplayModes.ACTIVE:
- _active_states.pop_back()
- _set_description_from_active_states()
diff --git a/addons/simple-state/demo/emit_next.gd b/addons/simple-state/demo/emit_next.gd
deleted file mode 100644
index 255bf28..0000000
--- a/addons/simple-state/demo/emit_next.gd
+++ /dev/null
@@ -1,6 +0,0 @@
-extends AnimationState
-
-
-func _update(_delta: float) -> void:
- if Input.is_action_just_pressed(get_root().NEXT_STATE_ACTION):
- choose_new_substate_requested.emit()
diff --git a/addons/simple-state/icons/animation_state.png b/addons/simple-state/icons/animation_state.png
deleted file mode 100644
index 6e4530c..0000000
Binary files a/addons/simple-state/icons/animation_state.png and /dev/null differ
diff --git a/addons/simple-state/icons/licenses/Visit Kenney.url b/addons/simple-state/icons/licenses/Visit Kenney.url
deleted file mode 100644
index fbdde43..0000000
--- a/addons/simple-state/icons/licenses/Visit Kenney.url
+++ /dev/null
@@ -1,2 +0,0 @@
-[InternetShortcut]
-URL=http://www.kenney.nl/
\ No newline at end of file
diff --git a/addons/simple-state/icons/licenses/Visit Patreon.url b/addons/simple-state/icons/licenses/Visit Patreon.url
deleted file mode 100644
index 439ef26..0000000
--- a/addons/simple-state/icons/licenses/Visit Patreon.url
+++ /dev/null
@@ -1,2 +0,0 @@
-[InternetShortcut]
-URL=https://www.patreon.com/kenney/
\ No newline at end of file
diff --git a/addons/simple-state/icons/licenses/board_game_icons.txt b/addons/simple-state/icons/licenses/board_game_icons.txt
deleted file mode 100644
index a1c2c30..0000000
--- a/addons/simple-state/icons/licenses/board_game_icons.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
- Board Game Icons (1.0)
-
- Created/distributed by Kenney (www.kenney.nl)
- Creation date: 10-01-2022
-
- ------------------------------
-
- License: (Creative Commons Zero, CC0)
- http://creativecommons.org/publicdomain/zero/1.0/
-
- This content is free to use in personal, educational and commercial projects.
- Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
-
- ------------------------------
-
- Donate: http://support.kenney.nl
- Patreon: http://patreon.com/kenney/
-
- Follow on Twitter for updates:
- http://twitter.com/KenneyNL
\ No newline at end of file
diff --git a/addons/simple-state/icons/licenses/game_icons+expansion.txt b/addons/simple-state/icons/licenses/game_icons+expansion.txt
deleted file mode 100644
index d6484ae..0000000
--- a/addons/simple-state/icons/licenses/game_icons+expansion.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-
-###############################################################################
-
- Game icon pack by Kenney Vleugels (www.kenney.nl)
-
- ------------------------------
-
- License (CC0)
- http://creativecommons.org/publicdomain/zero/1.0/
-
- You may use these graphics in personal and commercial projects.
- Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
-
-###############################################################################
\ No newline at end of file
diff --git a/addons/simple-state/icons/licenses/pictogrammers.txt b/addons/simple-state/icons/licenses/pictogrammers.txt
deleted file mode 100644
index 4b5bcf6..0000000
--- a/addons/simple-state/icons/licenses/pictogrammers.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Pictogrammers Free License
---------------------------
-
-This icon collection is released as free, open source, and GPL friendly by
-the [Pictogrammers](http://pictogrammers.com/). You may use it
-for commercial projects, open source projects, or anything really.
-
-# Icons: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
-Some of the icons are redistributed under the Apache 2.0 license. All other
-icons are either redistributed under their respective licenses or are
-distributed under the Apache 2.0 license.
-
-# Fonts: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
-All web and desktop fonts are distributed under the Apache 2.0 license. Web
-and desktop fonts contain some icons that are redistributed under the Apache
-2.0 license. All other icons are either redistributed under their respective
-licenses or are distributed under the Apache 2.0 license.
-
-# Code: MIT (https://opensource.org/licenses/MIT)
-The MIT license applies to all non-font and non-icon files.
diff --git a/addons/simple-state/icons/random_state.png b/addons/simple-state/icons/random_state.png
deleted file mode 100644
index 9d5d7ab..0000000
Binary files a/addons/simple-state/icons/random_state.png and /dev/null differ
diff --git a/addons/simple-state/icons/sequence_state.png b/addons/simple-state/icons/sequence_state.png
deleted file mode 100644
index 0d0f455..0000000
Binary files a/addons/simple-state/icons/sequence_state.png and /dev/null differ
diff --git a/addons/simple-state/icons/sources.txt b/addons/simple-state/icons/sources.txt
deleted file mode 100644
index 9a429f7..0000000
--- a/addons/simple-state/icons/sources.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Icon Sources
------------
-[0] Board Game Icons: https://kenney.nl/assets/board-game-icons
-- state.png
-- random_state.png
-- sequence_state.png
-
-[1] Game Icons: https://kenney.nl/assets/game-icons
-- animation_state.png
-
-[2] Pictogrammers:
-- state_machine_debugger.png (tinted)
diff --git a/addons/simple-state/icons/state.png b/addons/simple-state/icons/state.png
deleted file mode 100644
index 4d22ed7..0000000
Binary files a/addons/simple-state/icons/state.png and /dev/null differ
diff --git a/addons/simple-state/icons/state_fullsize.png b/addons/simple-state/icons/state_fullsize.png
deleted file mode 100644
index 4f5f80d..0000000
Binary files a/addons/simple-state/icons/state_fullsize.png and /dev/null differ
diff --git a/addons/simple-state/icons/state_machine_debugger.png b/addons/simple-state/icons/state_machine_debugger.png
deleted file mode 100644
index e226db7..0000000
Binary files a/addons/simple-state/icons/state_machine_debugger.png and /dev/null differ
diff --git a/addons/simple-state/icons/state_machine_debugger.png.import b/addons/simple-state/icons/state_machine_debugger.png.import
deleted file mode 100644
index 12307dc..0000000
--- a/addons/simple-state/icons/state_machine_debugger.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://dsglk01amsawf"
-path="res://.godot/imported/state_machine_debugger.png-4af22f2c577f0756aa0b1d50ce679700.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://addons/simple-state/icons/state_machine_debugger.png"
-dest_files=["res://.godot/imported/state_machine_debugger.png-4af22f2c577f0756aa0b1d50ce679700.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/simple-state/plugin.cfg b/addons/simple-state/plugin.cfg
deleted file mode 100644
index c77753a..0000000
--- a/addons/simple-state/plugin.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-[plugin]
-
-name="SimpleState"
-description="A super-simple state machine. Lightweight and (hopefully) reliable."
-author="AuraTheEnby"
-version="1.3.2"
-script="plugin.gd"
diff --git a/addons/simple-state/plugin.gd b/addons/simple-state/plugin.gd
deleted file mode 100644
index eb7db46..0000000
--- a/addons/simple-state/plugin.gd
+++ /dev/null
@@ -1,25 +0,0 @@
-@tool
-extends EditorPlugin
-
-
-## It uses the icons provided by the scripts anyway, so
-## we don't really need to specify the real ones here.
-## Plus, it might help with enabling it before the project
-## has been reloaded for the first time.
-var placeholder_texture := PlaceholderTexture2D.new()
-
-
-func _enter_tree() -> void:
- add_custom_type("State", "Node", State, placeholder_texture)
- add_custom_type("RandomState", "Node", RandomState, placeholder_texture)
- add_custom_type("AnimationState", "Node", AnimationState, placeholder_texture)
- add_custom_type("SequenceState", "Node", SequenceState, placeholder_texture)
- add_custom_type("StateMachineDebugger", "Tree", StateMachineDebugger, placeholder_texture)
-
-
-func _exit_tree() -> void:
- remove_custom_type("State")
- remove_custom_type("RandomState")
- remove_custom_type("AnimationState")
- remove_custom_type("SequenceState")
- remove_custom_type("StateMachineDebugger")
diff --git a/addons/simple-state/templates/State/empty_state.gd b/addons/simple-state/templates/State/empty_state.gd
deleted file mode 100644
index 132b1c4..0000000
--- a/addons/simple-state/templates/State/empty_state.gd
+++ /dev/null
@@ -1,33 +0,0 @@
-# meta-default: true
-extends _BASE_
-
-
-# Called when the state is activated. (parents, then children)
-func _enter() -> void:
- pass
-
-
-# Called after the state is activated. (children, then parents)
-func _after_enter() -> void:
- pass
-
-
-# Called every physics frame (only when the state is active, of course). (parents, then children)
-func _update(delta: float) -> void:
- pass
-
-
-# Called at the end of every physics frame. (children, then parents)
-func _after_update(delta: float) -> void:
- pass
-
-
-# Called before the state is deactivated. (parents, then children)
-func _before_exit() -> void:
- pass
-
-
-# Called when the state is deactivated. (children, then parents)
-func _exit() -> void:
- pass
-
diff --git a/addons/yet_another_behavior_tree/LICENSE.md b/addons/yet_another_behavior_tree/LICENSE.md
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/addons/yet_another_behavior_tree/LICENSE.md
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/addons/yet_another_behavior_tree/plugin.cfg b/addons/yet_another_behavior_tree/plugin.cfg
new file mode 100644
index 0000000..269d32a
--- /dev/null
+++ b/addons/yet_another_behavior_tree/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Yet Another Behavior Tree"
+description="A Behavior Tree implementation for Godot Engine"
+author="Adrien Quillet"
+version="3.0.0"
+script="yet_another_behavior_tree.gd"
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/Icons.pdn b/addons/yet_another_behavior_tree/src/Assets/Icons/Icons.pdn
new file mode 100644
index 0000000..22a75da
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/Icons.pdn differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png
new file mode 100644
index 0000000..c8a9c0b
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png differ
diff --git a/addons/simple-state/icons/animation_state.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png.import
similarity index 65%
rename from addons/simple-state/icons/animation_state.png.import
rename to addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png.import
index 4b338d3..536efe2 100644
--- a/addons/simple-state/icons/animation_state.png.import
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cna1s8hi8xc58"
-path="res://.godot/imported/animation_state.png-5f10255295e45d80e555d2885de83e22.ctex"
+uid="uid://b48sosvxi4n24"
+path="res://.godot/imported/btaction.png-18977c497a76704723d083978b2ea595.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/simple-state/icons/animation_state.png"
-dest_files=["res://.godot/imported/animation_state.png-5f10255295e45d80e555d2885de83e22.ctex"]
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png"
+dest_files=["res://.godot/imported/btaction.png-18977c497a76704723d083978b2ea595.ctex"]
[params]
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png
new file mode 100644
index 0000000..bfdc4c0
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png.import
new file mode 100644
index 0000000..f3b65be
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cbnehk2kgxha0"
+path="res://.godot/imported/btactionblackboarddelete.png-e03a9a160a95593083f9e35e81bdc103.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png"
+dest_files=["res://.godot/imported/btactionblackboarddelete.png-e03a9a160a95593083f9e35e81bdc103.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/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png
new file mode 100644
index 0000000..6978498
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png.import
new file mode 100644
index 0000000..54485a4
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://boyttypw3logy"
+path="res://.godot/imported/btactionblackboardset.png-1278deabaaedde513ea46a9a4bb621bb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png"
+dest_files=["res://.godot/imported/btactionblackboardset.png-1278deabaaedde513ea46a9a4bb621bb.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/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png
new file mode 100644
index 0000000..b9a8084
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png.import
new file mode 100644
index 0000000..37275a9
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2oj3rjstv2l6"
+path="res://.godot/imported/btactioncallable.png-8ff3e8c57248454d9afe7b8c14b8034f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png"
+dest_files=["res://.godot/imported/btactioncallable.png-8ff3e8c57248454d9afe7b8c14b8034f.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/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png
new file mode 100644
index 0000000..b120281
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png.import
new file mode 100644
index 0000000..848abfd
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ct3k7bwcr4n23"
+path="res://.godot/imported/btactionwait.png-35eefe67990a95ed2449e53d56e2cf8f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png"
+dest_files=["res://.godot/imported/btactionwait.png-35eefe67990a95ed2449e53d56e2cf8f.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/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png
new file mode 100644
index 0000000..49cfc76
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png.import
new file mode 100644
index 0000000..915becb
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bc3t7dlq5ojd7"
+path="res://.godot/imported/btblackboard.png-7f733cee4dbff616004368a725149731.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png"
+dest_files=["res://.godot/imported/btblackboard.png-7f733cee4dbff616004368a725149731.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/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png
new file mode 100644
index 0000000..0e938f2
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png.import
new file mode 100644
index 0000000..55f80a8
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://61mky8di67wd"
+path="res://.godot/imported/btcomposite.png-e026d4d004e93322dbcd1e6f926f41a2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png"
+dest_files=["res://.godot/imported/btcomposite.png-e026d4d004e93322dbcd1e6f926f41a2.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/yet_another_behavior_tree/src/Assets/Icons/btcondition.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png
new file mode 100644
index 0000000..0cca0b8
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png.import
new file mode 100644
index 0000000..8b5934a
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d30ihyblas1o0"
+path="res://.godot/imported/btcondition.png-afc407604bdfe2e82f2cbf0d2c34ed27.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png"
+dest_files=["res://.godot/imported/btcondition.png-afc407604bdfe2e82f2cbf0d2c34ed27.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/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png
new file mode 100644
index 0000000..3462a2d
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png.import
new file mode 100644
index 0000000..6f7ad32
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b4q7fnl5uons7"
+path="res://.godot/imported/btconditionblackboardkeyexists.png-6b1c537ccdd672a561e7966389cda13e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png"
+dest_files=["res://.godot/imported/btconditionblackboardkeyexists.png-6b1c537ccdd672a561e7966389cda13e.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/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png
new file mode 100644
index 0000000..72e8ac2
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png.import
new file mode 100644
index 0000000..4bddb36
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://fcjo6te2i4fh"
+path="res://.godot/imported/btconditionblackboardvaluescomparison.png-eecd4feaf86eb841c950cbbeb86bd20e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png"
+dest_files=["res://.godot/imported/btconditionblackboardvaluescomparison.png-eecd4feaf86eb841c950cbbeb86bd20e.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/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png
new file mode 100644
index 0000000..918fb1a
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png.import
new file mode 100644
index 0000000..df5516d
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://y6usgtpm8654"
+path="res://.godot/imported/btconditioncallable.png-536cf1db6a6b7ab4d372e700c9ec5b2a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png"
+dest_files=["res://.godot/imported/btconditioncallable.png-536cf1db6a6b7ab4d372e700c9ec5b2a.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/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png
new file mode 100644
index 0000000..aed229f
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png.import
new file mode 100644
index 0000000..71f4e9c
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmwqhdcyhptro"
+path="res://.godot/imported/btdecorator.png-8fdc93bf956127379273d9a2949c9c1c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png"
+dest_files=["res://.godot/imported/btdecorator.png-8fdc93bf956127379273d9a2949c9c1c.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/yet_another_behavior_tree/src/Assets/Icons/btfailure.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png
new file mode 100644
index 0000000..f688508
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png.import
new file mode 100644
index 0000000..37fc346
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c73l5rvxyrnda"
+path="res://.godot/imported/btfailure.png-43c62fa29ae4a153c528e033a8bbecc9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png"
+dest_files=["res://.godot/imported/btfailure.png-43c62fa29ae4a153c528e033a8bbecc9.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/yet_another_behavior_tree/src/Assets/Icons/btinverter.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png
new file mode 100644
index 0000000..de30df7
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png.import
new file mode 100644
index 0000000..229d611
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gutmaw46jlyh"
+path="res://.godot/imported/btinverter.png-2f7d6a7358d40fcfcaf67b8a6acea047.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png"
+dest_files=["res://.godot/imported/btinverter.png-2f7d6a7358d40fcfcaf67b8a6acea047.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/yet_another_behavior_tree/src/Assets/Icons/btleaf.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png
new file mode 100644
index 0000000..f22f9a1
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png differ
diff --git a/addons/simple-state/icons/random_state.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png.import
similarity index 66%
rename from addons/simple-state/icons/random_state.png.import
rename to addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png.import
index 01bee1d..a7544ac 100644
--- a/addons/simple-state/icons/random_state.png.import
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cgoc214akn2d6"
-path="res://.godot/imported/random_state.png-0878745fbdc123f3f0d51012c73a7024.ctex"
+uid="uid://gxdluh7wjomk"
+path="res://.godot/imported/btleaf.png-c2431b3a96122c6dc535e0b78c164efc.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/simple-state/icons/random_state.png"
-dest_files=["res://.godot/imported/random_state.png-0878745fbdc123f3f0d51012c73a7024.ctex"]
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png"
+dest_files=["res://.godot/imported/btleaf.png-c2431b3a96122c6dc535e0b78c164efc.ctex"]
[params]
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png
new file mode 100644
index 0000000..2ba3769
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png.import
new file mode 100644
index 0000000..ad6ebfb
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://byk2pkxf1m2xm"
+path="res://.godot/imported/btlimiter.png-4154342ba1ecd7c753485e0c1895d99d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png"
+dest_files=["res://.godot/imported/btlimiter.png-4154342ba1ecd7c753485e0c1895d99d.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/yet_another_behavior_tree/src/Assets/Icons/btnode.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png
new file mode 100644
index 0000000..7b10b33
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png differ
diff --git a/addons/simple-state/icons/sequence_state.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png.import
similarity index 66%
rename from addons/simple-state/icons/sequence_state.png.import
rename to addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png.import
index 8231c76..1969413 100644
--- a/addons/simple-state/icons/sequence_state.png.import
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://bcviu3dccvvm2"
-path="res://.godot/imported/sequence_state.png-4718f2c926301319d99a0ccd15ff62fb.ctex"
+uid="uid://sccepp5a5goa"
+path="res://.godot/imported/btnode.png-3ebad094cfaf4e989cef065b27b78174.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/simple-state/icons/sequence_state.png"
-dest_files=["res://.godot/imported/sequence_state.png-4718f2c926301319d99a0ccd15ff62fb.ctex"]
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png"
+dest_files=["res://.godot/imported/btnode.png-3ebad094cfaf4e989cef065b27b78174.ctex"]
[params]
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png
new file mode 100644
index 0000000..ca32a9c
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png.import
new file mode 100644
index 0000000..f5aeff4
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://i8yclvjdd2po"
+path="res://.godot/imported/btparallel.png-72c2892f41b80e22bfa4396abf59a1a3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png"
+dest_files=["res://.godot/imported/btparallel.png-72c2892f41b80e22bfa4396abf59a1a3.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/yet_another_behavior_tree/src/Assets/Icons/btrandom.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png
new file mode 100644
index 0000000..08d368b
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png.import
new file mode 100644
index 0000000..fcd9245
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cj1bxkdykjv23"
+path="res://.godot/imported/btrandom.png-0f3e4c1202cc1fd7fd32f36ce482ef97.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png"
+dest_files=["res://.godot/imported/btrandom.png-0f3e4c1202cc1fd7fd32f36ce482ef97.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/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png
new file mode 100644
index 0000000..d570781
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png.import
new file mode 100644
index 0000000..707a69c
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3s6eyhvmsmca"
+path="res://.godot/imported/btrandomselector.png-5bbaa6b1d149c5357046de0f8d6b7707.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png"
+dest_files=["res://.godot/imported/btrandomselector.png-5bbaa6b1d149c5357046de0f8d6b7707.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/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png
new file mode 100644
index 0000000..0aa49d2
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png.import
new file mode 100644
index 0000000..4210998
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dtc18hhykvswh"
+path="res://.godot/imported/btrepeatuntil.png-b92f22528a4b73d0ca2d27142e092367.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png"
+dest_files=["res://.godot/imported/btrepeatuntil.png-b92f22528a4b73d0ca2d27142e092367.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/yet_another_behavior_tree/src/Assets/Icons/btroot.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png
new file mode 100644
index 0000000..0258886
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png differ
diff --git a/addons/simple-state/icons/state_fullsize.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png.import
similarity index 66%
rename from addons/simple-state/icons/state_fullsize.png.import
rename to addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png.import
index 561a0d8..594cd7b 100644
--- a/addons/simple-state/icons/state_fullsize.png.import
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://b32u1sa3robj0"
-path="res://.godot/imported/state_fullsize.png-41a4827bc36d7745ba55c17c0bc213e9.ctex"
+uid="uid://bs628dnvnsxwl"
+path="res://.godot/imported/btroot.png-5ce207fd487015fa6d9a79efff94f0e4.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/simple-state/icons/state_fullsize.png"
-dest_files=["res://.godot/imported/state_fullsize.png-41a4827bc36d7745ba55c17c0bc213e9.ctex"]
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png"
+dest_files=["res://.godot/imported/btroot.png-5ce207fd487015fa6d9a79efff94f0e4.ctex"]
[params]
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png
new file mode 100644
index 0000000..1af7092
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png.import
new file mode 100644
index 0000000..b093cc0
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ctyhhfi6jks5a"
+path="res://.godot/imported/btselector.png-68934baa4bd97bc50fd24775d28386ac.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png"
+dest_files=["res://.godot/imported/btselector.png-68934baa4bd97bc50fd24775d28386ac.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/yet_another_behavior_tree/src/Assets/Icons/btsequence.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png
new file mode 100644
index 0000000..1d54127
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png.import
new file mode 100644
index 0000000..6f5c6e9
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://chnob03fb7a5"
+path="res://.godot/imported/btsequence.png-c2e63f47520b7d091ffbf0c6bc2a76a0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png"
+dest_files=["res://.godot/imported/btsequence.png-c2e63f47520b7d091ffbf0c6bc2a76a0.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/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png b/addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png
new file mode 100644
index 0000000..1ec1eb7
Binary files /dev/null and b/addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png differ
diff --git a/addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png.import b/addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png.import
new file mode 100644
index 0000000..525a337
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://v47uxlomir5w"
+path="res://.godot/imported/btsuccess.png-7ab9f0bea97f3881521c44275dcb2c13.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png"
+dest_files=["res://.godot/imported/btsuccess.png-7ab9f0bea97f3881521c44275dcb2c13.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/yet_another_behavior_tree/src/Blackboard/BTBlackboard.gd b/addons/yet_another_behavior_tree/src/Blackboard/BTBlackboard.gd
new file mode 100644
index 0000000..90a8ce6
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Blackboard/BTBlackboard.gd
@@ -0,0 +1,89 @@
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btblackboard.png")
+class_name BTBlackboard
+extends Node
+
+
+## Blackboard allows to share data across nodes and behavior trees. You can create/retrieve/erase
+## pairs of key-value. Keys and values are variants and can be anything.
+## [br][br]
+## Data in blackboard can be isolated in so-called [i]namespaces[/i]. A data key can exists only once in
+## a namespace, but can exists multiple times across namespaces, allowing the user to isolate data when,
+## for example, a blackboard is shared between multiple behavior trees. By default, if no namespace is
+## specified when inserting a data into a blackboard, the [i]default namespace[/i] is used.
+
+
+const DEFAULT_NAMESPACE:String = "_default_namespace"
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+## A dictionnary allowing to specifies default entries before tree first execution.
+## [br][br]
+## Those entries are added in the default namespace of the blackboard. If you want to add default entries
+## in another namespace, you must do it in a script.
+@export var data:Dictionary = {}
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+# {
+# "namespace" : {
+# DATAS
+# }
+# }
+var _execution_data:Dictionary = {}
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ # On copie le dico défini par l'utilisateur dans le dico privé
+ _get_namespace_board(DEFAULT_NAMESPACE).merge(data)
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func get_delta() -> float:
+ # Delta is not in any namespace, since its a volatile data, that is valid just inside one tree tick
+ return get_data("delta")
+
+func has_data(key:Variant, board_namespace:String = DEFAULT_NAMESPACE) -> bool:
+ var namespace_dico:Dictionary = _get_namespace_board(board_namespace)
+ return namespace_dico.has(key)
+
+func get_data(key:Variant, default_value:Variant = null, board_namespace:String = DEFAULT_NAMESPACE) -> Variant:
+ var result:Variant = _get_namespace_board(board_namespace).get(key, default_value)
+ return result.get_ref() if result is WeakRef else result
+
+func set_data(key:Variant, value:Variant, board_namespace:String = DEFAULT_NAMESPACE) -> Variant:
+ var namespace_dico:Dictionary = _get_namespace_board(board_namespace)
+ var old_value:Variant = namespace_dico[key] if namespace_dico.has(key) else null
+ namespace_dico[key] = weakref(value) if value is Node else value
+ return old_value.get_ref() if old_value is WeakRef else old_value
+
+func delete_data(key:Variant, board_namespace:String = DEFAULT_NAMESPACE) -> Variant:
+ var namespace_dico:Dictionary = _get_namespace_board(board_namespace)
+ var old_value = namespace_dico[key] if namespace_dico.has(key) else null
+ namespace_dico.erase(key)
+ return old_value.get_ref() if old_value is WeakRef else old_value
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func _get_namespace_board(board_namespace:String) -> Dictionary:
+ if not _execution_data.has(board_namespace):
+ _execution_data[board_namespace] = {}
+ return _execution_data[board_namespace]
diff --git a/addons/yet_another_behavior_tree/src/Nodes/BTNode.gd b/addons/yet_another_behavior_tree/src/Nodes/BTNode.gd
new file mode 100644
index 0000000..09b7152
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/BTNode.gd
@@ -0,0 +1,126 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btnode.png")
+class_name BTNode
+extends Node
+
+
+## Base object for all behavior tree nodes.
+## [b][u]This node should never be used directly.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+@onready var _is_in_editor:bool = Engine.is_editor_hint()
+var _children:Array[BTNode] = []
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _init() -> void:
+ super._init()
+ if _is_in_editor:
+ _connect_signal_if_needed(child_entered_tree, _update_configuration_warnings_1)
+ _connect_signal_if_needed(child_exiting_tree, _update_configuration_warnings_1)
+ _connect_signal_if_needed(tree_entered, _update_configuration_warnings_0)
+ _connect_signal_if_needed(tree_exited, _update_configuration_warnings_0)
+ _connect_signal_if_needed(child_entered_tree, _update_cached_children)
+ _connect_signal_if_needed(child_exiting_tree, _update_cached_children)
+
+func _ready() -> void:
+ if _is_in_editor:
+ update_configuration_warnings()
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ return BTTickResult.SUCCESS
+
+func is_leaf() -> bool:
+ return false
+
+func enter(blackboard:BTBlackboard) -> void:
+ pass
+
+func start(blackboard:BTBlackboard) -> void:
+ pass
+
+func stop(blackboard:BTBlackboard) -> void:
+ pass
+
+func exit(blackboard:BTBlackboard) -> void:
+ pass
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func _update_configuration_warnings_0() -> void:
+ update_configuration_warnings()
+
+func _update_configuration_warnings_1(any) -> void:
+ update_configuration_warnings()
+
+func is_valid() -> bool:
+ return false
+
+func _update_cached_children(any) -> void:
+ _children.clear()
+ for child in get_children():
+ if child is BTNode:
+ _children.append(child)
+
+func _execute(actor:Node, blackboard:BTBlackboard) -> int:
+ if _is_in_editor:
+ return BTTickResult.FAILURE
+
+ _enter(blackboard);
+
+ _start(actor, blackboard)
+
+ var result:int = tick(actor, blackboard)
+ if result != BTTickResult.RUNNING:
+ _stop(actor, blackboard)
+
+ _exit(blackboard)
+ return result
+
+func _enter(blackboard:BTBlackboard) -> void:
+ enter(blackboard)
+ pass
+
+func _start(actor:Node, blackboard:BTBlackboard) -> void:
+ var blackboard_namespace:String = str(actor.get_instance_id())
+ blackboard.get_data("running_nodes", [], blackboard_namespace).append(self)
+
+ if not blackboard.get_data("previously_running_nodes", [], blackboard_namespace).has(self):
+ start(blackboard)
+
+func _stop(actor:Node, blackboard:BTBlackboard) -> void:
+ var blackboard_namespace:String = str(actor.get_instance_id())
+ blackboard.get_data("running_nodes", [], blackboard_namespace).erase(self)
+ exit(blackboard)
+ stop(blackboard)
+
+func _exit(blackboard:BTBlackboard) -> void:
+ pass
+
+func _connect_signal_if_needed(sig:Signal, callable:Callable) -> void:
+ if not sig.is_connected(callable):
+ sig.connect(callable)
diff --git a/addons/yet_another_behavior_tree/src/Nodes/BTRoot.gd b/addons/yet_another_behavior_tree/src/Nodes/BTRoot.gd
new file mode 100644
index 0000000..fa23a40
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/BTRoot.gd
@@ -0,0 +1,196 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btroot.png")
+class_name BTRoot
+extends BTNode
+
+
+## This behavior tree is a Godot node that can be added to your Scene tree. The logic inside tree nodes
+## will be run every frame, during process or physics process, depending on tree process mode.
+## [br][br]
+## At each frame, the [code]tick[/code] function of tree nodes will be run. This function has access to the
+## [i]actor[/i] (the node the tree is describing behavior for), and a [i]blackboard[/i] (allowing to share
+## data between nodes). The tick function can either returns:
+## [br]
+## - [code]SUCCESS[/code], indicating that node execution is successful,[br]
+## - [code]RUNNING[/code], indicating that node is doing a long computation/action/whatever you want, that is not finished yet,[br]
+## - [code]FAILURE[/code], indicating that something went wrong during child execution (condition not met, ...).
+## [br][br]
+## Depending on your tree structure, node result will produce various behaviors. See node documentation for
+## mor details.
+
+
+enum BTRootProcessMode {
+ PROCESS,
+ PHYSIC_PROCESS
+}
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+signal on_running(running_node_names:Array)
+signal on_idle()
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Indicates if tree should run or not
+@export var enabled:bool = true:
+ set(value):
+ enabled = value
+ set_process(enabled)
+ set_physics_process(enabled)
+
+## Indicates whether tree should execute during [i]process[/i] or [i]physics process[/i].
+@export var root_process_mode:BTRootProcessMode = BTRootProcessMode.PHYSIC_PROCESS
+
+## Path to the node that the tree is drescribing actions for. This is the node that will be passed to all
+## tree nodes, allowing you to manipulate the actor at every tree step.
+@export var actor_path:NodePath :
+ set(value):
+ actor_path = value
+ _update_actor_from_path()
+ update_configuration_warnings()
+
+## Path to the blackboard node. This allows to share a same blackboard between several trees, for example to code
+## a group of enemies acting together, or to specify some default entries using the editor. If empty, a default
+## empty blackboard will be used during tree execution.
+@export var blackboard:BTBlackboard = null :
+ set(value):
+ blackboard = value
+ _update_blackboard()
+ update_configuration_warnings()
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _blackboard:BTBlackboard
+var _previous_running_nodes:Array[BTNode] = []
+var _actor:Node
+
+var _execution_start_time_ms:float
+var _execution_stop_time_ms:float
+
+@onready var _performance_monitor_identifier:String = "BTRoot/%s-%s" % [get_name(), get_instance_id()]
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ super._ready()
+
+ if not is_valid():
+ push_error("BTRoot '%s'(%s) is not valid, check its configuration" % [get_name(), get_instance_id()])
+ # Init du blackboard: soit celui de l'utilisateur, soit un tout neuf
+ _update_blackboard()
+
+ if not Engine.is_editor_hint():
+ _add_custom_performance_monitor()
+ tree_entered.connect(_add_custom_performance_monitor)
+ tree_exited.connect(_remove_custom_performance_monitor)
+
+func _process(delta:float) -> void:
+ if not Engine.is_editor_hint() and enabled and root_process_mode == BTRootProcessMode.PROCESS:
+ _do_execute(delta)
+
+func _physics_process(delta:float) -> void:
+ if not Engine.is_editor_hint() and enabled and root_process_mode == BTRootProcessMode.PHYSIC_PROCESS:
+ _do_execute(delta)
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ if not _check_direct_children_validity():
+ warnings.append("Root tree must contains only one child of type BTComposite")
+ if not _check_actor_validity():
+ warnings.append("Root tree actor must be filled")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _check_direct_children_validity() and _check_actor_validity()
+
+func _check_direct_children_validity() -> bool:
+ var is_valid:bool = get_child_count() == 1
+ if is_valid:
+ is_valid = get_child(0) is BTComposite
+ return is_valid
+
+func _check_actor_validity() -> bool:
+ var is_valid:bool = actor_path != null and not actor_path.is_empty()
+ if is_valid:
+ _update_actor_from_path()
+ is_valid =_actor != null and is_instance_valid(_actor)
+ return is_valid
+
+func _update_blackboard() -> void:
+ if blackboard != null and is_instance_valid(blackboard):
+ _blackboard= blackboard
+ else:
+ _blackboard = BTBlackboard.new()
+
+func _update_actor_from_path() -> void:
+ _actor = get_node_or_null(actor_path)
+ if not is_instance_valid(_actor) and is_inside_tree():
+ # Fallback : si le chemin donné n'était pas relatif à la scene courante, on le check en absolu
+ _actor = get_tree().current_scene.get_node_or_null(actor_path)
+
+func _do_execute(delta:float):
+ _register_execution_start()
+ var blackboard_namespace:String = str(_actor.get_instance_id())
+ # delta est une donnée volatile, elle n'est donc pas dans un namespace puisque chaque arbre tourne
+ # séquentiellement, donc il n'y a pas de collision de données en cas de partage du blackboard
+ _blackboard.set_data("delta", delta)
+ _blackboard.set_data("previously_running_nodes", Array(_previous_running_nodes), blackboard_namespace)
+ _blackboard.set_data("running_nodes", [], blackboard_namespace)
+
+ _children[0]._execute(_actor, _blackboard)
+
+ var raw_running_nodes:Array = _blackboard.get_data("running_nodes", [], blackboard_namespace)
+ var running_nodes:Array[BTNode] = []
+ running_nodes.append_array(raw_running_nodes)
+ if _previous_running_nodes != running_nodes:
+ for n in _previous_running_nodes:
+ if not running_nodes.has(n):
+ n._stop(_actor, _blackboard)
+
+ if not running_nodes.is_empty():
+ var running_node_names:Array[String] = []
+ for running_node in running_nodes:
+ if running_node.is_leaf():
+ running_node_names.append(str(running_node.name))
+ on_running.emit(running_node_names)
+ else:
+ on_idle.emit()
+ _previous_running_nodes = running_nodes
+ _register_execution_stop()
+
+func _add_custom_performance_monitor() -> void:
+ if not Performance.has_custom_monitor(_performance_monitor_identifier):
+ Performance.add_custom_monitor(_performance_monitor_identifier, _compute_last_exec_time)
+
+func _remove_custom_performance_monitor() -> void:
+ if Performance.has_custom_monitor(_performance_monitor_identifier):
+ Performance.remove_custom_monitor(_performance_monitor_identifier)
+
+func _register_execution_start() -> void:
+ _execution_start_time_ms = Time.get_ticks_msec()
+
+func _register_execution_stop() -> void:
+ _execution_stop_time_ms = Time.get_ticks_msec()
+
+func _compute_last_exec_time() -> float:
+ return _execution_stop_time_ms - _execution_start_time_ms
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Composite/BTComposite.gd b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTComposite.gd
new file mode 100644
index 0000000..619e066
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTComposite.gd
@@ -0,0 +1,59 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btcomposite.png")
+class_name BTComposite
+extends BTNode
+
+
+## Base object for all behavior tree composites.
+## Composite nodes defines the root of a tree branch : each branch can be seen as a rule for your AI.
+## They accept any kind of behavior tree nodes as children. It can either be a composite, decorator or leaf nodes
+## [b][u]This node should never be used directly.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ if not _has_at_least_one_child():
+ warnings.append("A composite must have at least one child node")
+ if not _all_children_are_bt_nodes():
+ warnings.append("A composite must have children nodes of type BTNode")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _has_at_least_one_child() and _all_children_are_bt_nodes()
+
+func _has_at_least_one_child() -> bool:
+ return get_child_count() >= 1
+
+func _all_children_are_bt_nodes() -> bool:
+ for child in get_children():
+ if not child is BTNode:
+ return false
+ return true
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Composite/BTParallel.gd b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTParallel.gd
new file mode 100644
index 0000000..ef216ee
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTParallel.gd
@@ -0,0 +1,58 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btparallel.png")
+class_name BTParallel
+extends BTComposite
+
+
+## The parallel node is a [i]composite node[/i] that executes all its children at each [code]tick[/code].
+## If at least one child is is running, the parallel reports it's running too. If no child is running,
+## then if at least one child succeeded, the parallel reports success, else it reports failure.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var at_least_one_child_running:bool = false
+ var at_least_one_child_success:bool = false
+
+ for child in _children:
+ var result:int = child._execute(actor, blackboard)
+ if result == BTTickResult.SUCCESS:
+ at_least_one_child_success = true
+ if result == BTTickResult.RUNNING:
+ at_least_one_child_running = true
+
+ # At least one running : this is still running !
+ if at_least_one_child_running:
+ return BTTickResult.RUNNING
+ # No one is running, so, at least one success > success, else failure
+ elif at_least_one_child_success:
+ return BTTickResult.SUCCESS
+ else:
+ return BTTickResult.FAILURE
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Composite/BTRandomSelector.gd b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTRandomSelector.gd
new file mode 100644
index 0000000..767a674
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTRandomSelector.gd
@@ -0,0 +1,42 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btrandomselector.png")
+class_name BTRandomSelector
+extends BTSelector
+
+
+## The random selector node is a [i]composite node[/i] that behaves like the [code]BTSelector[/code] node,
+## except that it executes its children in random order.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func start(blackboard:BTBlackboard) -> void:
+ super.start(blackboard)
+ if not save_progression or _running_child_index == -1:
+ _children.shuffle()
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSelector.gd b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSelector.gd
new file mode 100644
index 0000000..c738fb0
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSelector.gd
@@ -0,0 +1,63 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btselector.png")
+class_name BTSelector
+extends BTComposite
+
+
+## The selector node is a [i]composite node[/i] that executes its children from the first one to the last one,
+## in order, until one of them returns [code]SUCCESS[/code]. If a selector child succeeds, the selector
+## succeed too. If all selector children failed, the selector fails too.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Indicates whether the selector should resume to the last running child on next tree execution ([code]on[/code]),
+## or restart from its first child ([code]off[/code]). Its usefull to describe a non-interruptible action,
+## or to optimize process time.
+@export var save_progression:bool = false
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _running_child_index:int = -1
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ for child_index in _children.size():
+ if not save_progression or child_index >= _running_child_index:
+ var child:BTNode = _children[child_index]
+ var result:int = child._execute(actor, blackboard)
+ if result != BTTickResult.FAILURE:
+ if save_progression and result == BTTickResult.RUNNING:
+ _running_child_index = child_index
+ return result
+
+ return BTTickResult.FAILURE
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func start(blackboard:BTBlackboard) -> void:
+ _running_child_index = 0
+
+func stop(blackboard:BTBlackboard) -> void:
+ _running_child_index = -1
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSequence.gd b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSequence.gd
new file mode 100644
index 0000000..24f8181
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Composite/BTSequence.gd
@@ -0,0 +1,63 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btsequence.png")
+class_name BTSequence
+extends BTComposite
+
+
+## The sequence node is a [i]composite node[/i] that executes its children from the first one to the last
+## one, until all children succeed or one of its children fails. If all children succeed, the sequence
+## succeeds too ; if one child fails, the sequence fails too.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Indicates whether the sequence should resume to the last running child on next tree execution ([code]on[/code]),
+## or restart from its first child ([code]off[/code]). Its usefull to describe a non-interruptible action, or to
+## optimize process time.
+@export var save_progression:bool = false
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _running_child_index:int = -1
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ for child_index in _children.size():
+ if not save_progression or child_index >= _running_child_index:
+ var child:BTNode = _children[child_index]
+ var result:int = child._execute(actor, blackboard)
+ if result != BTTickResult.SUCCESS:
+ if save_progression and result == BTTickResult.RUNNING:
+ _running_child_index = child_index
+ return result
+
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func start(blackboard:BTBlackboard) -> void:
+ _running_child_index = 0
+
+func stop(blackboard:BTBlackboard) -> void:
+ _running_child_index = -1
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTDecorator.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTDecorator.gd
new file mode 100644
index 0000000..9185a47
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTDecorator.gd
@@ -0,0 +1,58 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btdecorator.png")
+class_name BTDecorator
+extends BTNode
+
+
+## Base object for all behavior tree decorators.
+## Decorator nodes allow to customize result of its only child node.
+## [b][u]This node should never be used directly.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ if not _has_only_one_child():
+ warnings.append("A decorator must have only one child")
+ if not _child_is_bt_node():
+ warnings.append("A decorator must have a child of type BTNode")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _has_only_one_child() and _child_is_bt_node()
+
+func _has_only_one_child() -> bool:
+ return get_child_count() >= 1
+
+func _child_is_bt_node() -> bool:
+ for child in get_children():
+ if not child is BTNode:
+ return false
+ return true
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTFailure.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTFailure.gd
new file mode 100644
index 0000000..5185bc6
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTFailure.gd
@@ -0,0 +1,41 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btfailure.png")
+class_name BTFailure
+extends BTDecorator
+
+
+## The failure node is a [i]decorator[/i] node that always returns [i]failed[/i] on child execution.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ _children[0]._execute(actor, blackboard)
+ return BTTickResult.FAILURE
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTInverter.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTInverter.gd
new file mode 100644
index 0000000..530e78b
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTInverter.gd
@@ -0,0 +1,47 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btinverter.png")
+class_name BTInverter
+extends BTDecorator
+
+
+## The inverter node is a [i]decorator[/i] node returns [i]success[/i] when its child fails its execution,
+## and [i]failure[/i] when its child succeeds its execution. When its child is [i]running[/i], it returns
+## [i]running[/i] too.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var child_result:int = _children[0]._execute(actor, blackboard)
+ if child_result == BTTickResult.SUCCESS:
+ return BTTickResult.FAILURE
+ if child_result == BTTickResult.FAILURE:
+ return BTTickResult.SUCCESS
+ return BTTickResult.RUNNING
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTLimiter.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTLimiter.gd
new file mode 100644
index 0000000..49cd82a
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTLimiter.gd
@@ -0,0 +1,60 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btlimiter.png")
+class_name BTLimiter
+extends BTDecorator
+
+
+## The limiter node is a [i]decorator[/i] node that limits the total number of execution of its child node.
+## When the limit is not reachs, the limiter nodes reports its child execution status. Once the limit is reachs,
+## it never executs its child and always report a [i]failed[/i] execution.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Number of allowed child execution.
+@export_range(0, 9999999, 1) var limit:int = 1
+
+## Whether or not the [code]limit[/code] value is included into the number of times the child can run.
+## It clarifies the usage of the limit.
+@export var include_limit:bool = true
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _invocation_count:int = 0
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var limit_reached:bool = _invocation_count >= limit
+ if not include_limit:
+ limit_reached = _invocation_count >= limit - 1
+
+ if limit_reached:
+ return BTTickResult.FAILURE
+
+ var result:int = _children[0]._execute(actor, blackboard)
+ if result != BTTickResult.RUNNING:
+ _invocation_count += 1
+ return result
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRandom.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRandom.gd
new file mode 100644
index 0000000..07cd4e2
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRandom.gd
@@ -0,0 +1,47 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btrandom.png")
+class_name BTRandom
+extends BTDecorator
+
+
+## The random node is a [i]decorator[/i] node randomly execute its child. If the child is executed,
+## the node result is the same as its child result. Otherwise, result is [i]failure[/i].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## A float between [code]0[/code] (included) and [code]1[/code] (included) indicating the probability of child
+## execution.
+@export_range(0, 1) var probability:float = 0.5
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var random_float:float = randf()
+ if random_float > probability:
+ return BTTickResult.FAILURE
+ return _children[0]._execute(actor, blackboard)
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRepeatUntil.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRepeatUntil.gd
new file mode 100644
index 0000000..8ceffdf
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTRepeatUntil.gd
@@ -0,0 +1,67 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btrepeatuntil.png")
+class_name BTRepeatUntil
+extends BTDecorator
+
+
+## The repeat until node is a [i]decorator[/i] node that loop its child execution until child execution result
+## is as excepted. It is possible to specifies the maximum number of loop execution allowed to obtain the desired
+## result. If desired result is obtained before the loop execution limit, the repeat until node returns the
+## obtained result. If not, its returns a [i]failure[/i].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Expected child result to stop the loop.
+@export_enum("SUCCESS:0", "RUNNING:1", "FAILURE:2") var stop_condition:int = 0
+
+## Maximum number of child execution to obtain the desired result. If value is [code]0[/code], there is
+## [b]no limit[/b] to the number of times the loop can run (⚠️ be careful to not create an infinite loop).
+## If value is more than zero, its represents the maximum number of loop execution.
+@export_range(0, 999999) var max_iteration:int = 0
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var result:int
+ var not_stopped:bool = true
+ var iteration_count:int = 0
+ while not_stopped:
+ result = _children[0]._execute(actor, blackboard)
+ if stop_condition == BTTickResult.SUCCESS and result == BTTickResult.SUCCESS:
+ not_stopped = false
+ if stop_condition == BTTickResult.RUNNING and result == BTTickResult.RUNNING:
+ not_stopped = false
+ if stop_condition == BTTickResult.FAILURE and result == BTTickResult.FAILURE:
+ not_stopped = false
+
+ if max_iteration > 0:
+ iteration_count += 1
+ if not not_stopped and iteration_count > max_iteration:
+ not_stopped = false
+
+ return result
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTSuccess.gd b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTSuccess.gd
new file mode 100644
index 0000000..8c9fe7a
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Decorators/BTSuccess.gd
@@ -0,0 +1,41 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btsuccess.png")
+class_name BTSuccess
+extends BTDecorator
+
+
+## The success node is a [i]decorator[/i] node that always returns [i]success[/i] on child execution.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ _children[0]._execute(actor, blackboard)
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTAction.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTAction.gd
new file mode 100644
index 0000000..3637f54
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTAction.gd
@@ -0,0 +1,38 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btaction.png")
+class_name BTAction
+extends BTLeaf
+
+
+## The action node is a [i]leaf[/i] node. Its purpose is to return [i]success[/i] when an action is completed,
+## [i]failure[/i] if its fails to execute, and [i]running[/i] if the action is occuring but is not completed yet.
+## [b][u]Users must subclass this node to implements their own actions.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardDelete.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardDelete.gd
new file mode 100644
index 0000000..dc0fd44
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardDelete.gd
@@ -0,0 +1,64 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboarddelete.png")
+class_name BTActionBlackboardDelete
+extends BTLeaf
+
+
+## The blackboard delete action node is a [i]leaf[/i] node. It allows to erase a key from the tree blackboard.
+## This node operates in the blackboard [i]default namespace[/i].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Name of the key that must be erased from blackboard, in [i]default namespace[/i]
+@export var blackboard_key:String = "" :
+ set(value):
+ blackboard_key = value
+ update_configuration_warnings()
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _blackboard_key_is_set():
+ warnings.append("Blackboard key must be set")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ blackboard.delete_data(blackboard_key)
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ var is_valid:bool = super.is_valid()
+ if is_valid:
+ is_valid = _blackboard_key_is_set()
+ return is_valid
+
+func _blackboard_key_is_set() -> bool:
+ return blackboard_key != null and not blackboard_key.is_empty()
+
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardSet.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardSet.gd
new file mode 100644
index 0000000..be1ab5c
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardSet.gd
@@ -0,0 +1,98 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionblackboardset.png")
+class_name BTActionBlackboardSet
+extends BTLeaf
+
+
+## The blackboard set action node is a [i]leaf[/i] node. It allows to set a value in the blackboard.
+## Its execution always returns [i]success[/i]. This node operates in the blackboard [i]default namespace[/i].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Name of the key that must be set, in [i]default namespace[/i]
+@export var blackboard_key:String = "" :
+ set(value):
+ blackboard_key = value
+ update_configuration_warnings()
+
+## An expression representing the value to associated to the given key. The expression will be evaluated by
+## Godot Engine during child execution. It should be simple. See [url=https://docs.godotengine.org/en/latest/classes/class_expression.html]Godot Expression[/url]
+## for details. In expression, user has access to two predefined variables:[br]
+## - [code]actor[/code]: the node the tree is describing action for,[br]
+## - [code]blackboard[/code]: the tree blackboard,[br]
+## - [code]delta[/code]: the [i]_process[/i] or [i]_physics_process[/i] delta value, as a [code]float[/code].
+@export_multiline var expression:String = "" :
+ set(value):
+ if value != expression:
+ expression = value
+ _update_expression()
+ update_configuration_warnings()
+
+## Indicates if the value must be overwritten if it already exists or not
+@export var can_overwrite_value:bool = false
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _expression:BTExpression = BTExpression.new()
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _blackboard_key_is_set():
+ warnings.append("Blackboard key must be set")
+ if not _expression_key_is_set():
+ warnings.append("Expression must be set")
+ if not _expression_is_valid():
+ warnings.append("Expression is not valid")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ if can_overwrite_value or not blackboard.has_data(blackboard_key):
+ var value:Variant = _expression.evaluate(actor, blackboard)
+ blackboard.set_data(blackboard_key, value)
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ var is_valid:bool = super.is_valid()
+ if is_valid:
+ is_valid = _blackboard_key_is_set()
+ if is_valid:
+ is_valid = _expression_key_is_set()
+ return is_valid
+
+func _blackboard_key_is_set() -> bool:
+ return blackboard_key != null and not blackboard_key.is_empty()
+
+func _expression_key_is_set() -> bool:
+ return expression != null and not expression.is_empty()
+
+func _expression_is_valid() -> bool:
+ return _expression.is_valid()
+
+func _update_expression() -> void:
+ _expression.expression = expression
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionCallable.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionCallable.gd
new file mode 100644
index 0000000..5606ea9
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionCallable.gd
@@ -0,0 +1,123 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btactioncallable.png")
+class_name BTActionCallable
+extends BTLeaf
+
+
+## The callable action node is a [i]leaf[/i] node. At each tick, the node calls a function from an object that has
+## been parametrized. It can also pass arguments to this function. Its result depends of the specified function
+## result:
+## [br]
+## - If specified function returns a [code]bool[/code], then the tick result is [i]success[i] is boolean
+## is [code]true[/code], [i]failure[/i] otherwise,[br]
+## - If the specified function returns an [code]int[/code], it is interpreted as the enum values [code]SUCCESS[/code],
+## [code]RUNNING[/code] or [code]FAILURE[/code] from [code]BTTickResult[/code] object. If another value is returned
+## by specified function, behavior is undefined,[br]
+## - If specified function returns nothing ([code]void[/code] or [code]null[/code] result), then [i]success[/i]
+## is returned.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Path to the node that contains the function to call
+@export var method_owner_path:NodePath:
+ set(value):
+ method_owner_path = value
+ _update_method_owner_from_path()
+ update_configuration_warnings()
+
+## Name of the function to call in the [i]method owner node[/i]
+@export var method_name:String = "":
+ set(value):
+ method_name = value
+ update_configuration_warnings()
+
+## Array of arguments to pass when calling the function. Arguments are expressions that will
+## be evaluated by Godot Engine at runtime to produce the desired value. See [url=https://docs.godotengine.org/en/latest/classes/class_expression.html]Godot Expression[/url]
+## for details. In expression, user has access to two predefined variables:[br]
+## - [code]actor[/code]: the node the tree is describing action for,[br]
+## - [code]blackboard[/code]: the tree blackboard,[br]
+## - [code]delta[/code]: the [i]_process[/i] or [i]_physics_process[/i] delta value, as a [code]float[/code].[br]
+## Number and types of arguments must match function prototype, or an error will occurs at runtime.
+@export var method_arguments:Array[String] = []
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _method_owner:Node
+var _cached_method_arguments:Array[String] = []
+var _argument_expression:Array[BTExpression] = []
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ _update_method_owner_from_path()
+ _update_argument_expressions()
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _check_method_owner_validity():
+ warnings.append("Method owner must be set")
+ if not _check_method_name_validity():
+ warnings.append("Method name must be set")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ _update_argument_expressions()
+ var arguments:Array[Variant] = _argument_expression.map(func(expr):return expr.evaluate(actor, blackboard))
+ var result:Variant = _method_owner.callv(method_name, arguments)
+ if result is bool:
+ return BTTickResult.SUCCESS if result else BTTickResult.FAILURE
+ if result is int:
+ return result
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _check_method_owner_validity() and _check_method_name_validity()
+
+func _check_method_owner_validity() -> bool:
+ var is_valid:bool = method_owner_path != null and not method_owner_path.is_empty()
+ if is_valid:
+ _update_method_owner_from_path()
+ is_valid = _method_owner != null and is_instance_valid(_method_owner)
+ return is_valid
+
+func _check_method_name_validity() -> bool:
+ return method_name != null and not method_name.is_empty()
+
+func _update_method_owner_from_path() -> void:
+ _method_owner = get_node_or_null(method_owner_path)
+ if not is_instance_valid(_method_owner) and is_inside_tree():
+ # Fallback : si le chemin donné n'était pas relatif à la scene courante, on le check en absolu
+ _method_owner = get_tree().current_scene.get_node_or_null(method_owner_path)
+
+func _update_argument_expressions() -> void:
+ if _cached_method_arguments != method_arguments:
+ _cached_method_arguments = Array(method_arguments)
+ _argument_expression.clear()
+ for expr in _cached_method_arguments:
+ var btexpression:BTExpression = BTExpression.new()
+ btexpression.expression = expr
+ _argument_expression.append(btexpression)
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionWait.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionWait.gd
new file mode 100644
index 0000000..e2f58de
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionWait.gd
@@ -0,0 +1,62 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btactionwait.png")
+class_name BTActionWait
+extends BTLeaf
+
+
+## The wait action node is a [i]leaf[/i] node. Its execution returns [i]running[/i] during the specified wait time,
+## then returns *success* when specified time is elapsed. After succeeded, the wait time is rearmed for next
+## tree execution.
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Number of milliseconds to wait before returning [i]success[/i]
+@export var wait_time_ms:int = 1_000
+
+## Indicates if a random deviation should be applied to the wait time. [code]0[/code] means there is no
+## deviation et the wait time will be strictyl respected. Random deviation may change after each node rearm.
+@export var random_deviation_ms:int = 0
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _current_time_ms:float = 0
+var _time_to_reach_ms:int
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ _current_time_ms += blackboard.get_delta() * 1_000
+ if _current_time_ms <= _time_to_reach_ms:
+ return BTTickResult.RUNNING
+ return BTTickResult.SUCCESS
+
+func start(blackboard:BTBlackboard) -> void:
+ _current_time_ms = 0
+ _time_to_reach_ms = wait_time_ms
+ if random_deviation_ms != 0:
+ _time_to_reach_ms += randi_range(0, random_deviation_ms)
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTCondition.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTCondition.gd
new file mode 100644
index 0000000..2be6861
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTCondition.gd
@@ -0,0 +1,38 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btcondition.png")
+class_name BTCondition
+extends BTLeaf
+
+
+## The condition node is a [i]leaf[/i] node. Its purpose is to return [i]success[/i] when a condition is meet,
+## [i]failure[/i] otherwise. This node should never return [i]running[/i].
+## [b][u]Users must subclass this node to implements their own conditions.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardKeyExists.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardKeyExists.gd
new file mode 100644
index 0000000..e744b3c
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardKeyExists.gd
@@ -0,0 +1,66 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardkeyexists.png")
+class_name BTConditionBlackboardKeyExists
+extends BTLeaf
+
+
+## The blackboard key exists condition node is a [i]leaf[/i] node. It returns [i]success[/i] if a certain key
+## is present in the tree blackboard during its execution, [i]failure[/i] otherwise. This node operates in the blackboard
+## [i]default namespace[/i].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Name of the key that must exists in the blackboard, in [i]default namespace[/i]
+@export var blackboard_key:String = "" :
+ set(value):
+ blackboard_key = value
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _blackboard_key_is_set():
+ warnings.append("Blackboard key must be set")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var result:int = BTTickResult.FAILURE
+ if blackboard.has_data(blackboard_key):
+ result = BTTickResult.SUCCESS
+ return result
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ var is_valid:bool = super.is_valid()
+ if is_valid:
+ is_valid = _blackboard_key_is_set()
+ return is_valid
+
+func _blackboard_key_is_set() -> bool:
+ return blackboard_key != null and not blackboard_key.is_empty()
+
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardValuesComparison.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardValuesComparison.gd
new file mode 100644
index 0000000..de943f4
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionBlackboardValuesComparison.gd
@@ -0,0 +1,105 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditionblackboardvaluescomparison.png")
+class_name BTConditionBlackboardValuesComparison
+extends BTLeaf
+
+
+## The blackboard values comparison condition node is a [i]leaf[/i] node. It returns [i]success[/i] both values
+## represented by specified keys returns true when compared using the given operator. This node operates in the blackboard
+## [i]default namespace[/i].
+
+
+enum Operator {
+ EQUAL = 1,
+ NOT_EQUAL = 2,
+ LOWER = 3,
+ LOWER_OR_EQUAL = 4,
+ GREATER = 5,
+ GREATER_OR_EQUAL = 6
+}
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Name of the key that old the first value to compare, in [i]default namespace[/i]
+@export var first_operand_blackboard_key:String = "" :
+ set(value):
+ first_operand_blackboard_key = value
+ update_configuration_warnings()
+
+## Operator used to compare values
+@export_enum("EQUAL:1", "NOT_EQUAL:2", "LOWER:3", "LOWER_OR_EQUAL:4", "GREATER:5", "GREATER_OR_EQUAL:6") var operator:int = 0
+
+## Name of the key that old the second value to compare, [i]default namespace[/i]
+@export var second_operand_blackboard_key:String = "" :
+ set(value):
+ second_operand_blackboard_key = value
+ update_configuration_warnings()
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _parsed_compared_value:Variant
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _blackboard_keys_are_set():
+ warnings.append("Blackboard keys must be set")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ var result:int = BTTickResult.FAILURE
+
+ var first_operand:Variant = blackboard.get_data(first_operand_blackboard_key)
+ var second_operand:Variant = blackboard.get_data(second_operand_blackboard_key)
+ if first_operand != null and second_operand != null:
+ var compare_result:bool = false
+ match(operator):
+ Operator.EQUAL:
+ compare_result = first_operand == second_operand
+ Operator.NOT_EQUAL:
+ compare_result = first_operand != second_operand
+ Operator.LOWER:
+ compare_result = first_operand < second_operand
+ Operator.LOWER_OR_EQUAL:
+ compare_result = first_operand <= second_operand
+ Operator.GREATER:
+ compare_result = first_operand > second_operand
+ Operator.GREATER_OR_EQUAL:
+ compare_result = first_operand >= second_operand
+ if compare_result:
+ return BTTickResult.SUCCESS
+ return result
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ var is_valid:bool = super.is_valid()
+ if is_valid:
+ is_valid = _blackboard_keys_are_set()
+ return is_valid
+
+func _blackboard_keys_are_set() -> bool:
+ return first_operand_blackboard_key != null and not first_operand_blackboard_key.is_empty() and second_operand_blackboard_key != null and not second_operand_blackboard_key.is_empty()
+
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionCallable.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionCallable.gd
new file mode 100644
index 0000000..236c576
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionCallable.gd
@@ -0,0 +1,112 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btconditioncallable.png")
+class_name BTConditionCallable
+extends BTLeaf
+
+
+## The callable condition node is a [i]leaf[/i] node. The node calls a function from an object that has been
+## parametrized to check for a condition. It can also pass arguments to this function. Its result is the function
+## result, meaning that specified function must returns a [code]bool[/code] value. This nodes returns
+## [i]success[/i] if function call returned [code]true[/code], and [i]failure[/i] if function call returned [code]false[/code].
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+## Path to the node that contains the function to call
+@export var method_owner_path:NodePath:
+ set(value):
+ method_owner_path = value
+ _update_method_owner_from_path()
+ update_configuration_warnings()
+
+## Name of the function to call in the [i]method owner node[i]
+@export var method_name:String = "":
+ set(value):
+ method_name = value
+ update_configuration_warnings()
+
+## Array of arguments to pass when calling the function. Arguments are expressions that will
+## be evaluated by Godot Engine at runtime to produce the desired value. See [url=https://docs.godotengine.org/en/latest/classes/class_expression.html]Godot Expression[/url]
+## for details. In expression, user has access to two predefined variables:[br]
+## - [code]actor[/code]: the node the tree is describing action for,[br]
+## - [code]blackboard[/code]: the tree blackboard,[br]
+## - [code]delta[/code]: the [i]_process[/i] or [i]_physics_process[/i] delta value, as a [code]float[/code].[br]
+## Number and types of arguments must match function prototype, or an error will occurs at runtime.
+@export var method_arguments:Array[String] = []
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _method_owner:Node
+var _cached_method_arguments:Array[String] = []
+var _argument_expression:Array[BTExpression] = []
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ _update_method_owner_from_path()
+ _update_argument_expressions()
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ warnings.append_array(super._get_configuration_warnings())
+ if not _check_method_owner_validity():
+ warnings.append("Method owner must be set")
+ if not _check_method_name_validity():
+ warnings.append("Method name must be set")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func tick(actor:Node, blackboard:BTBlackboard) -> int:
+ _update_argument_expressions()
+ var arguments:Array[Variant] = _argument_expression.map(func(expr):return expr.evaluate(actor, blackboard))
+ var result:bool = _method_owner.callv(method_name, arguments)
+ return BTTickResult.SUCCESS if result else BTTickResult.FAILURE
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _check_method_owner_validity() and _check_method_name_validity()
+
+func _check_method_owner_validity() -> bool:
+ var is_valid:bool = method_owner_path != null and not method_owner_path.is_empty()
+ if is_valid:
+ _update_method_owner_from_path()
+ is_valid = _method_owner != null and is_instance_valid(_method_owner)
+ return is_valid
+
+func _check_method_name_validity() -> bool:
+ return method_name != null and not method_name.is_empty()
+
+func _update_method_owner_from_path() -> void:
+ _method_owner = get_node_or_null(method_owner_path)
+ if not is_instance_valid(_method_owner) and is_inside_tree():
+ # Fallback : si le chemin donné n'était pas relatif à la scene courante, on le check en absolu
+ _method_owner = get_tree().current_scene.get_node_or_null(method_owner_path)
+
+func _update_argument_expressions() -> void:
+ if _cached_method_arguments != method_arguments:
+ _cached_method_arguments = Array(method_arguments)
+ _argument_expression.clear()
+ for expr in _cached_method_arguments:
+ var btexpression:BTExpression = BTExpression.new()
+ btexpression.expression = expr
+ _argument_expression.append(btexpression)
diff --git a/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTLeaf.gd b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTLeaf.gd
new file mode 100644
index 0000000..618805d
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Nodes/Leaves/BTLeaf.gd
@@ -0,0 +1,55 @@
+@tool
+@icon("res://addons/yet_another_behavior_tree/src/Assets/Icons/btleaf.png")
+class_name BTLeaf
+extends BTNode
+
+
+## Base object for all behavior tree leaves.
+## Leaf nodes, as their name implies, do not have any child. They represents basic unit of work of your
+## AI, which can be separated into two notions: conditions and actions.
+## [b][u]This node should never be used directly.[/u][/b]
+
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+ if not _has_no_child():
+ warnings.append("A leaf must not have child")
+ return warnings
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func is_leaf() -> bool:
+ return true
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _has_no_child()
+
+func _has_no_child() -> bool:
+ return get_child_count() == 0
+
diff --git a/addons/yet_another_behavior_tree/src/Result/BTTickResult.gd b/addons/yet_another_behavior_tree/src/Result/BTTickResult.gd
new file mode 100644
index 0000000..5c160d0
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Result/BTTickResult.gd
@@ -0,0 +1,36 @@
+extends RefCounted
+class_name BTTickResult
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+enum {
+ SUCCESS = 0,
+ RUNNING = 1,
+ FAILURE = 2
+}
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
diff --git a/addons/yet_another_behavior_tree/src/Utils/BTExpression.gd b/addons/yet_another_behavior_tree/src/Utils/BTExpression.gd
new file mode 100644
index 0000000..9e17b93
--- /dev/null
+++ b/addons/yet_another_behavior_tree/src/Utils/BTExpression.gd
@@ -0,0 +1,64 @@
+extends RefCounted
+class_name BTExpression
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+var expression:String = "":
+ set(value):
+ if value != expression:
+ expression = value
+ _expression = _parse_expression(expression)
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _expression:Expression
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func is_valid() -> bool:
+ return _expression != null
+
+func evaluate(actor:Node, blackboard:BTBlackboard) -> Variant:
+ var arguments:Array[Variant] = [actor, blackboard, blackboard.get_delta()]
+ return _execute_expression(arguments)
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func _parse_expression(string_expr:String) -> Expression:
+ var expr:Expression = Expression.new()
+ var parse_code:int = expr.parse(string_expr, ["actor", "blackboard", "delta"])
+ if parse_code != OK:
+ push_error("Unable to parse expression '%s' : %s" % [string_expr, expr.get_error_text()])
+ return null
+ return expr
+
+func _execute_expression(arguments:Array[Variant]) -> Variant:
+ var result:Variant = null
+ if _expression == null:
+ _expression = _parse_expression(expression)
+ if _expression != null:
+ result = _expression.execute(arguments, self, true)
+ if _expression.has_execute_failed():
+ result = null
+ push_error("Unable to execute expression '%s' : %s" % [expression, _expression.get_error_text()])
+ return result
diff --git a/addons/yet_another_behavior_tree/yet_another_behavior_tree.gd b/addons/yet_another_behavior_tree/yet_another_behavior_tree.gd
new file mode 100644
index 0000000..0813f65
--- /dev/null
+++ b/addons/yet_another_behavior_tree/yet_another_behavior_tree.gd
@@ -0,0 +1,12 @@
+@tool
+extends EditorPlugin
+
+
+func _enter_tree() -> void:
+ # Initialization of the plugin goes here.
+ pass
+
+
+func _exit_tree() -> void:
+ # Clean-up of the plugin goes here.
+ pass
diff --git a/documentation/assets/.gdignore b/documentation/assets/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/documentation/assets/bt_illustration.png b/documentation/assets/bt_illustration.png
new file mode 100644
index 0000000..19d2ce9
Binary files /dev/null and b/documentation/assets/bt_illustration.png differ
diff --git a/documentation/assets/nodes/btactionblackboarddelete.png b/documentation/assets/nodes/btactionblackboarddelete.png
new file mode 100644
index 0000000..9f4ced0
Binary files /dev/null and b/documentation/assets/nodes/btactionblackboarddelete.png differ
diff --git a/documentation/assets/nodes/btactionblackboardset.png b/documentation/assets/nodes/btactionblackboardset.png
new file mode 100644
index 0000000..dcecf07
Binary files /dev/null and b/documentation/assets/nodes/btactionblackboardset.png differ
diff --git a/documentation/assets/nodes/btactioncallable.png b/documentation/assets/nodes/btactioncallable.png
new file mode 100644
index 0000000..ea4da8b
Binary files /dev/null and b/documentation/assets/nodes/btactioncallable.png differ
diff --git a/documentation/assets/nodes/btactionwait.png b/documentation/assets/nodes/btactionwait.png
new file mode 100644
index 0000000..2baa0ec
Binary files /dev/null and b/documentation/assets/nodes/btactionwait.png differ
diff --git a/documentation/assets/nodes/btblackboard.png b/documentation/assets/nodes/btblackboard.png
new file mode 100644
index 0000000..b9ae07f
Binary files /dev/null and b/documentation/assets/nodes/btblackboard.png differ
diff --git a/documentation/assets/nodes/btconditionblackboardkeyexists.png b/documentation/assets/nodes/btconditionblackboardkeyexists.png
new file mode 100644
index 0000000..4cb0106
Binary files /dev/null and b/documentation/assets/nodes/btconditionblackboardkeyexists.png differ
diff --git a/documentation/assets/nodes/btconditionblackboardvaluescomparison.png b/documentation/assets/nodes/btconditionblackboardvaluescomparison.png
new file mode 100644
index 0000000..9f02ffd
Binary files /dev/null and b/documentation/assets/nodes/btconditionblackboardvaluescomparison.png differ
diff --git a/documentation/assets/nodes/btconditioncallable.png b/documentation/assets/nodes/btconditioncallable.png
new file mode 100644
index 0000000..5020b89
Binary files /dev/null and b/documentation/assets/nodes/btconditioncallable.png differ
diff --git a/documentation/assets/nodes/btlimiter.png b/documentation/assets/nodes/btlimiter.png
new file mode 100644
index 0000000..5a80d36
Binary files /dev/null and b/documentation/assets/nodes/btlimiter.png differ
diff --git a/documentation/assets/nodes/btrandom.png b/documentation/assets/nodes/btrandom.png
new file mode 100644
index 0000000..3ef7b2b
Binary files /dev/null and b/documentation/assets/nodes/btrandom.png differ
diff --git a/documentation/assets/nodes/btrandomselector.png b/documentation/assets/nodes/btrandomselector.png
new file mode 100644
index 0000000..0193d06
Binary files /dev/null and b/documentation/assets/nodes/btrandomselector.png differ
diff --git a/documentation/assets/nodes/btrepeatuntil.png b/documentation/assets/nodes/btrepeatuntil.png
new file mode 100644
index 0000000..0a13996
Binary files /dev/null and b/documentation/assets/nodes/btrepeatuntil.png differ
diff --git a/documentation/assets/nodes/btroot.png b/documentation/assets/nodes/btroot.png
new file mode 100644
index 0000000..0edbb65
Binary files /dev/null and b/documentation/assets/nodes/btroot.png differ
diff --git a/documentation/assets/nodes/btroot.png.import b/documentation/assets/nodes/btroot.png.import
new file mode 100644
index 0000000..0d0ecaf
--- /dev/null
+++ b/documentation/assets/nodes/btroot.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bd4srq2ktcjbt"
+path="res://.godot/imported/btroot.png-06d458b63a88908557ef569967b6d585.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://documentation/assets/nodes/btroot.png"
+dest_files=["res://.godot/imported/btroot.png-06d458b63a88908557ef569967b6d585.ctex"]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/bptc_ldr=0
+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/documentation/assets/nodes/btselector.png b/documentation/assets/nodes/btselector.png
new file mode 100644
index 0000000..fd8f820
Binary files /dev/null and b/documentation/assets/nodes/btselector.png differ
diff --git a/documentation/assets/nodes/btsequence.png b/documentation/assets/nodes/btsequence.png
new file mode 100644
index 0000000..fd203ba
Binary files /dev/null and b/documentation/assets/nodes/btsequence.png differ
diff --git a/examples/blackboard_sharing/README.MD b/examples/blackboard_sharing/README.MD
new file mode 100644
index 0000000..fe0d594
--- /dev/null
+++ b/examples/blackboard_sharing/README.MD
@@ -0,0 +1,12 @@
+# 🗣️ Sharing blackboard : the talking game !
+
+This example demonstrates how to share a single blackboard in multiple behavior trees.
+
+Sharing a blackboard can be necessary if you want to easily share data between trees, for example, to synchronize multiple NPCs.
+
+The blackboard can be instantiate in a Scene Tree via the editor, or in a script (as in this example).
+
+## Technical elements
+
+- `main.tscn` : scene to run. The main scene instantiates a unique blackboard and give it to all player trees,
+- `player.tscn` : a player in the talking game. They all share the same blackboard to known who is the next to talk.
\ No newline at end of file
diff --git a/examples/blackboard_sharing/main.gd b/examples/blackboard_sharing/main.gd
new file mode 100644
index 0000000..4ea5349
--- /dev/null
+++ b/examples/blackboard_sharing/main.gd
@@ -0,0 +1,42 @@
+extends Node2D
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+var _shared_blackboard:BTBlackboard
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ _shared_blackboard = BTBlackboard.new()
+ _shared_blackboard.set_data("next_talking", "Alice")
+ _shared_blackboard.set_data("players", [])
+
+ $"PlayerAlice/BTRoot/".blackboard = _shared_blackboard
+ $"PlayerBob/BTRoot/".blackboard = _shared_blackboard
+ $"PlayerCharles/BTRoot/".blackboard = _shared_blackboard
+ $"PlayerEmily/BTRoot/".blackboard = _shared_blackboard
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
diff --git a/examples/blackboard_sharing/main.tscn b/examples/blackboard_sharing/main.tscn
new file mode 100644
index 0000000..8317600
--- /dev/null
+++ b/examples/blackboard_sharing/main.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=3 format=3 uid="uid://d1s5iyoblr4cm"]
+
+[ext_resource type="Script" path="res://examples/blackboard_sharing/main.gd" id="1_g7q7h"]
+[ext_resource type="PackedScene" uid="uid://7k8r0e84lcr3" path="res://examples/blackboard_sharing/player.tscn" id="2_y55mt"]
+
+[node name="World" type="Node2D"]
+script = ExtResource("1_g7q7h")
+
+[node name="PlayerAlice" parent="." instance=ExtResource("2_y55mt")]
+position = Vector2(509, 181)
+player_name = "Alice"
+
+[node name="PlayerBob" parent="." instance=ExtResource("2_y55mt")]
+position = Vector2(515, 477)
+player_name = "Bob"
+
+[node name="PlayerCharles" parent="." instance=ExtResource("2_y55mt")]
+position = Vector2(309, 324)
+player_name = "Charles"
+
+[node name="PlayerEmily" parent="." instance=ExtResource("2_y55mt")]
+position = Vector2(733, 322)
+player_name = "Emily"
diff --git a/examples/blackboard_sharing/player.gd b/examples/blackboard_sharing/player.gd
new file mode 100644
index 0000000..b65b39b
--- /dev/null
+++ b/examples/blackboard_sharing/player.gd
@@ -0,0 +1,63 @@
+extends Node2D
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+@export var player_name:String = "":
+ set(value):
+ player_name = value
+ if _name_label:
+ _name_label.text = player_name
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+@onready var _text_label:Label = $GUI/Text
+@onready var _name_label:Label = $GUI/Name
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _ready() -> void:
+ _name_label.text = player_name
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func declare_presence(blackboard:BTBlackboard) -> int:
+ blackboard.get_data("players").append(player_name)
+ return BTTickResult.SUCCESS
+
+func is_my_turn_to_talk(next_talking:String) -> bool:
+ return player_name == next_talking
+
+func talk() -> int:
+ _text_label.visible = true
+ get_tree().create_timer(1).timeout.connect(func():_text_label.visible = false)
+ return BTTickResult.SUCCESS
+
+func choose_next_player_talking(blackboard:BTBlackboard) -> int:
+ blackboard.set_data("next_talking", _pick_next_player(blackboard.get_data("players")))
+ return BTTickResult.SUCCESS
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func _pick_next_player(players:Array) -> String:
+ var result:String = players.pick_random()
+ while result == player_name:
+ result = players.pick_random()
+ return result
diff --git a/examples/blackboard_sharing/player.tscn b/examples/blackboard_sharing/player.tscn
new file mode 100644
index 0000000..6b53ca3
--- /dev/null
+++ b/examples/blackboard_sharing/player.tscn
@@ -0,0 +1,123 @@
+[gd_scene load_steps=11 format=3 uid="uid://7k8r0e84lcr3"]
+
+[ext_resource type="Script" path="res://examples/blackboard_sharing/player.gd" id="1_1ti0g"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/BTRoot.gd" id="2_sdd0w"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Composite/BTSelector.gd" id="3_2v3m1"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Composite/BTSequence.gd" id="4_fjw5l"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Decorators/BTSuccess.gd" id="4_qnbqx"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionCallable.gd" id="5_8tfvp"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Decorators/BTLimiter.gd" id="5_d3jao"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionCallable.gd" id="6_i6lan"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionWait.gd" id="10_1e6f0"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Decorators/BTInverter.gd" id="11_fvy66"]
+
+[node name="Player" type="Node2D"]
+script = ExtResource("1_1ti0g")
+
+[node name="Shape" type="Node2D" parent="."]
+
+[node name="Ahahahah" type="Polygon2D" parent="Shape"]
+color = Color(0.188235, 0.490196, 0.505882, 1)
+polygon = PackedVector2Array(-27, -55, 22, -55, 22, -25, 10, -18, 24, 17, -30, 17, -30, 17, -30, 17, -13, -18, -13, -18, -13, -18, -27, -26, -27, -26, -27, -26)
+
+[node name="GUI" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 0
+offset_left = -61.0
+offset_top = -84.0
+offset_right = 61.0
+offset_bottom = -58.0
+
+[node name="Text" type="Label" parent="GUI"]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+text = "My turn !"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Name" type="Label" parent="GUI"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 104.0
+offset_right = 98.0
+offset_bottom = 127.0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="BTRoot" type="Node" parent="."]
+script = ExtResource("2_sdd0w")
+actor_path = NodePath("..")
+
+[node name="Do" type="Node" parent="BTRoot"]
+script = ExtResource("4_fjw5l")
+
+[node name="Init" type="Node" parent="BTRoot/Do"]
+script = ExtResource("4_fjw5l")
+save_progression = true
+
+[node name="Declare I am playing" type="Node" parent="BTRoot/Do/Init"]
+script = ExtResource("4_qnbqx")
+
+[node name="Do once" type="Node" parent="BTRoot/Do/Init/Declare I am playing"]
+script = ExtResource("5_d3jao")
+
+[node name="I\'m here !" type="Node" parent="BTRoot/Do/Init/Declare I am playing/Do once"]
+script = ExtResource("6_i6lan")
+method_owner_path = NodePath("../../../../../..")
+method_name = "declare_presence"
+method_arguments = Array[String](["blackboard"])
+
+[node name="Invert" type="Node" parent="BTRoot/Do/Init"]
+script = ExtResource("11_fvy66")
+
+[node name="Do once" type="Node" parent="BTRoot/Do/Init/Invert"]
+script = ExtResource("5_d3jao")
+
+[node name="Wait 1s" type="Node" parent="BTRoot/Do/Init/Invert/Do once"]
+script = ExtResource("10_1e6f0")
+
+[node name="One Between" type="Node" parent="BTRoot/Do"]
+script = ExtResource("3_2v3m1")
+save_progression = true
+
+[node name="I must talk" type="Node" parent="BTRoot/Do/One Between"]
+script = ExtResource("4_fjw5l")
+save_progression = true
+
+[node name="Is my Turn ?" type="Node" parent="BTRoot/Do/One Between/I must talk"]
+script = ExtResource("5_8tfvp")
+method_owner_path = NodePath("../../../../..")
+method_name = "is_my_turn_to_talk"
+method_arguments = Array[String](["blackboard.get_data(\"next_talking\")"])
+
+[node name="Talk" type="Node" parent="BTRoot/Do/One Between/I must talk"]
+script = ExtResource("6_i6lan")
+method_owner_path = NodePath("../../../../..")
+method_name = "talk"
+
+[node name="Wait a little bit" type="Node" parent="BTRoot/Do/One Between/I must talk"]
+script = ExtResource("10_1e6f0")
+random_deviation_ms = 300
+
+[node name="Set Next Player To Talk" type="Node" parent="BTRoot/Do/One Between/I must talk"]
+script = ExtResource("6_i6lan")
+method_owner_path = NodePath("../../../../..")
+method_name = "choose_next_player_talking"
+method_arguments = Array[String](["blackboard"])
+
+[node name="I must listen" type="Node" parent="BTRoot/Do/One Between"]
+script = ExtResource("4_fjw5l")
+
+[node name="Not my turn" type="Node" parent="BTRoot/Do/One Between/I must listen"]
+script = ExtResource("11_fvy66")
+
+[node name="Is my Turn ?" type="Node" parent="BTRoot/Do/One Between/I must listen/Not my turn"]
+script = ExtResource("5_8tfvp")
+method_owner_path = NodePath("../../../../../..")
+method_name = "is_my_turn_to_talk"
+method_arguments = Array[String](["blackboard.get_data(\"next_talking\")"])
diff --git a/examples/simple_ai_logic/README.MD b/examples/simple_ai_logic/README.MD
new file mode 100644
index 0000000..5b56443
--- /dev/null
+++ b/examples/simple_ai_logic/README.MD
@@ -0,0 +1,12 @@
+# 🪵 Simple AI logic : the lumberjack !
+
+This example is about a lumberjack that needs to cut logs. He can carry a certain quantity of logs before going back to the nearest warehouse to drop logs. When logs are dropped, he moves back to the tree he was cutting (or, the nearest tree if the tree is cut), and cut logs again.
+
+The aim of this example is to show how to implement a simple AI logic, composed of differents phases, using a behavior tree. This is just a proposal, there are several ways to create our lumberjack !
+
+In lumberjack behavior tree, you can notice there is no custom tree node (action or condition) : only based ones are used. To execute functional actions, like dropping logs, the `BTActionCallable` is used. It allows to call a method in a node. For simple behavior trees, it allows to contain all logic into the same script.
+
+## Technical elements
+
+- `main.tscn` : scene to run,
+- `lumberjack.tscn` : the lumberjack. The behavior tree is in this scene.
\ No newline at end of file
diff --git a/examples/simple_ai_logic/env/tree.gd b/examples/simple_ai_logic/env/tree.gd
new file mode 100644
index 0000000..9a5ebcc
--- /dev/null
+++ b/examples/simple_ai_logic/env/tree.gd
@@ -0,0 +1,49 @@
+extends Node2D
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+@export var max_log_count:int = 3
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+@onready var remaining_logs:int = max_log_count
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _draw():
+ var poly = $ShapeFull/Leaves.polygon
+ for i in range(1 , poly.size()):
+ draw_line(poly[i-1] , poly[i], Color.BLACK , 1)
+ draw_line(poly[poly.size() - 1] , poly[0], Color.BLACK , 1)
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func has_logs() -> bool:
+ return remaining_logs > 0
+
+func cut_log() -> void:
+ if remaining_logs > 0:
+ remaining_logs -= 1
+ if remaining_logs == 0:
+ queue_free()
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
diff --git a/examples/simple_ai_logic/env/tree.tscn b/examples/simple_ai_logic/env/tree.tscn
new file mode 100644
index 0000000..b5c2f3c
--- /dev/null
+++ b/examples/simple_ai_logic/env/tree.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=5 format=3]
+
+[ext_resource type="Script" path="res://examples/simple_ai_logic/env/tree.gd" id="1_r2ta2"]
+
+[sub_resource type="Gradient" id="Gradient_aa3nm"]
+colors = PackedColorArray(0.408848, 0.73338, 3.85046e-07, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_2o5uu"]
+frequency = 0.025
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_0hflj"]
+color_ramp = SubResource("Gradient_aa3nm")
+noise = SubResource("FastNoiseLite_2o5uu")
+
+[node name="Tree" type="Node2D" groups=["tree"]]
+script = ExtResource("1_r2ta2")
+
+[node name="ShapeFull" type="Node2D" parent="."]
+
+[node name="Base" type="Polygon2D" parent="ShapeFull"]
+color = Color(0.270588, 0.141176, 0, 1)
+polygon = PackedVector2Array(-15, 7, -15, 7, 11, 7, 11, 7, 11, 7, 1, -6, 1, -6, 1, -6, 6, -38, -4, -38, -4, -38, -4, -38)
+
+[node name="Leaves" type="Polygon2D" parent="ShapeFull"]
+color = Color(0.121569, 0.301961, 0.0470588, 1)
+texture = SubResource("NoiseTexture2D_0hflj")
+polygon = PackedVector2Array(-3, -22, -3, -22, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -11, -26, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -18, -32, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -39, -23, -47, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -18, -60, -21, -76, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -22, -86, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, -7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 7, -95, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 19, -91, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 25, -84, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -69, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 29, -57, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 30, -49, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 33, -43, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 34, -36, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 31, -31, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 20, -25, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21, 7, -21)
diff --git a/examples/simple_ai_logic/env/warehouse.tscn b/examples/simple_ai_logic/env/warehouse.tscn
new file mode 100644
index 0000000..1be61ba
--- /dev/null
+++ b/examples/simple_ai_logic/env/warehouse.tscn
@@ -0,0 +1,9 @@
+[gd_scene format=3 uid="uid://i4372roabk7a"]
+
+[node name="Warehouse" type="Node2D" groups=["warehouse"]]
+
+[node name="Shape" type="Polygon2D" parent="."]
+position = Vector2(0, 7)
+scale = Vector2(0.28841, 0.28841)
+color = Color(0.270588, 0.145098, 0.0117647, 1)
+polygon = PackedVector2Array(-110.953, 0, 232.308, 0, 232.308, -225.374, 138.691, -298.187, -180.299, -218.439, -110.953, -218.439)
diff --git a/examples/simple_ai_logic/lumberjack.gd b/examples/simple_ai_logic/lumberjack.gd
new file mode 100644
index 0000000..a8bf751
--- /dev/null
+++ b/examples/simple_ai_logic/lumberjack.gd
@@ -0,0 +1,102 @@
+extends Node2D
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+@export_category("Logger")
+
+@export var max_log_capacity:int = 2
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+var logs_count:int = 0
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+@onready var _btroot:BTRoot = $AI
+
+var _cutting_log:bool = false
+var _cutting_tree:Node2D
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+func on_state_entered() :
+ _btroot.on_running.connect(func(s):print(s))
+ _btroot.on_idle.connect(func():print("idle..."))
+ _btroot.enabled = true
+
+func can_carry_more_logs() -> bool:
+ return logs_count < max_log_capacity
+
+func has_no_log() -> bool:
+ return logs_count == 0
+
+func has_remaining_trees() -> bool:
+ return not get_tree().get_nodes_in_group("tree").is_empty()
+
+func get_nearest_tree() -> Node2D:
+ var nearest_tree:Node2D
+ var nearest_distance_to_tree:float = 99999.0
+ for tree in get_tree().get_nodes_in_group("tree"):
+ var distance_to_tree:float = global_position.distance_to(tree.global_position)
+ if distance_to_tree < nearest_distance_to_tree:
+ nearest_distance_to_tree = distance_to_tree
+ nearest_tree = tree
+ return nearest_tree
+
+func get_nearest_warehouse() -> Node2D:
+ var nearest_warehouse:Node2D
+ var nearest_distance_to_warehouse:float = 99999.0
+ for warehouse in get_tree().get_nodes_in_group("warehouse"):
+ var distance_to_warehouse:float = global_position.distance_to(warehouse.global_position)
+ if distance_to_warehouse < nearest_distance_to_warehouse:
+ nearest_distance_to_warehouse = distance_to_warehouse
+ nearest_warehouse = warehouse
+ return nearest_warehouse
+
+func tree_is_valid(tree:Node2D) -> bool:
+ return is_instance_valid(tree) and tree.has_logs()
+
+func cut_log(tree:Node2D) -> int:
+ if not _cutting_log:
+ _cutting_log = true
+ _cutting_tree = tree
+ get_tree().create_timer(1).timeout.connect(_on_log_cut, CONNECT_ONE_SHOT)
+ return BTTickResult.RUNNING if _cutting_log else BTTickResult.FAILURE
+
+func drop_logs_to_warehouse() -> void:
+ logs_count = 0
+
+func move_to(delta:float, node:Node2D) -> int:
+ if global_position.distance_to(node.global_position) > 10:
+ global_position = global_position.move_toward(node.global_position, delta * 300)
+ return BTTickResult.RUNNING
+ else:
+ return BTTickResult.SUCCESS
+
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
+func _on_log_cut() -> void:
+ if is_instance_valid(_cutting_tree):
+ _cutting_tree.cut_log()
+ _cutting_tree = null
+ logs_count += 1
+ _cutting_log = false
diff --git a/examples/simple_ai_logic/lumberjack.tscn b/examples/simple_ai_logic/lumberjack.tscn
new file mode 100644
index 0000000..3a44330
--- /dev/null
+++ b/examples/simple_ai_logic/lumberjack.tscn
@@ -0,0 +1,167 @@
+[gd_scene load_steps=11 format=3 uid="uid://bl8kvanpmae2g"]
+
+[ext_resource type="Script" path="res://examples/simple_ai_logic/lumberjack.gd" id="1_4c8nf"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/BTRoot.gd" id="2_hoy6d"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Composite/BTSelector.gd" id="3_5vl0m"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Composite/BTSequence.gd" id="4_c83di"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Decorators/BTInverter.gd" id="5_iaq0m"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTConditionCallable.gd" id="6_wov0v"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardSet.gd" id="7_f8o6y"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionCallable.gd" id="8_mjt8i"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Decorators/BTFailure.gd" id="9_ojjv5"]
+[ext_resource type="Script" path="res://addons/yet_another_behavior_tree/src/Nodes/Leaves/BTActionBlackboardDelete.gd" id="10_7bq5a"]
+
+[node name="LumberJack" type="Node2D"]
+script = ExtResource("1_4c8nf")
+
+[node name="Shape" type="Node2D" parent="."]
+
+[node name="Polygon2D" type="Polygon2D" parent="Shape"]
+color = Color(0.54902, 0.0862745, 0.117647, 1)
+polygon = PackedVector2Array(-2, -5, -2, -2, -1, -2, -1, 0, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, 4, 5, 1, 0, 1, -2, 2, -2, 2, -5)
+
+[node name="AI" type="Node" parent="."]
+script = ExtResource("2_hoy6d")
+actor_path = NodePath("..")
+
+[node name="One Between" type="Node" parent="AI"]
+script = ExtResource("3_5vl0m")
+
+[node name="Idle To Warehouse" type="Node" parent="AI/One Between"]
+script = ExtResource("4_c83di")
+
+[node name="No Remaining Tree" type="Node" parent="AI/One Between/Idle To Warehouse"]
+script = ExtResource("5_iaq0m")
+
+[node name="At Least One Tree" type="Node" parent="AI/One Between/Idle To Warehouse/No Remaining Tree"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../..")
+method_name = "has_remaining_trees"
+
+[node name="Has No Log" type="Node" parent="AI/One Between/Idle To Warehouse"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../..")
+method_name = "has_no_log"
+
+[node name="Move To Nearest Warehouse" type="Node" parent="AI/One Between/Idle To Warehouse"]
+script = ExtResource("4_c83di")
+
+[node name="Get Nearest Warehouse" type="Node" parent="AI/One Between/Idle To Warehouse/Move To Nearest Warehouse"]
+script = ExtResource("7_f8o6y")
+blackboard_key = "nearest_warehouse"
+expression = "actor.get_nearest_warehouse()"
+can_overwrite_value = true
+
+[node name="Move To Warehouse" type="Node" parent="AI/One Between/Idle To Warehouse/Move To Nearest Warehouse"]
+script = ExtResource("8_mjt8i")
+method_owner_path = NodePath("../../../../..")
+method_name = "move_to"
+method_arguments = Array[String](["delta", "blackboard.get_data(\"nearest_warehouse\")"])
+
+[node name="Put Logs To Warehouse" type="Node" parent="AI/One Between"]
+script = ExtResource("4_c83di")
+
+[node name="One Between" type="Node" parent="AI/One Between/Put Logs To Warehouse"]
+script = ExtResource("3_5vl0m")
+
+[node name="Can NOT Carry More Logs" type="Node" parent="AI/One Between/Put Logs To Warehouse/One Between"]
+script = ExtResource("5_iaq0m")
+
+[node name="Can Cary More Logs" type="Node" parent="AI/One Between/Put Logs To Warehouse/One Between/Can NOT Carry More Logs"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../../..")
+method_name = "can_carry_more_logs"
+
+[node name="No Remaining Tree" type="Node" parent="AI/One Between/Put Logs To Warehouse/One Between"]
+script = ExtResource("5_iaq0m")
+
+[node name="At Least One Tree" type="Node" parent="AI/One Between/Put Logs To Warehouse/One Between/No Remaining Tree"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../../..")
+method_name = "has_remaining_trees"
+
+[node name="Move To Nearest Warehouse" type="Node" parent="AI/One Between/Put Logs To Warehouse"]
+script = ExtResource("4_c83di")
+
+[node name="Get Nearest Warehouse" type="Node" parent="AI/One Between/Put Logs To Warehouse/Move To Nearest Warehouse"]
+script = ExtResource("7_f8o6y")
+blackboard_key = "nearest_warehouse"
+expression = "actor.get_nearest_warehouse()"
+can_overwrite_value = true
+
+[node name="Move To Warehouse" type="Node" parent="AI/One Between/Put Logs To Warehouse/Move To Nearest Warehouse"]
+script = ExtResource("8_mjt8i")
+method_owner_path = NodePath("../../../../..")
+method_name = "move_to"
+method_arguments = Array[String](["delta", "blackboard.get_data(\"nearest_warehouse\")"])
+
+[node name="Drop Logs" type="Node" parent="AI/One Between/Put Logs To Warehouse"]
+script = ExtResource("8_mjt8i")
+method_owner_path = NodePath("../../../..")
+method_name = "drop_logs_to_warehouse"
+
+[node name="Get Log" type="Node" parent="AI/One Between"]
+script = ExtResource("4_c83di")
+
+[node name="Can Carry More Logs" type="Node" parent="AI/One Between/Get Log"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../..")
+method_name = "can_carry_more_logs"
+
+[node name="Move To Nearest Tree" type="Node" parent="AI/One Between/Get Log"]
+script = ExtResource("4_c83di")
+
+[node name="Has Remaning Trees" type="Node" parent="AI/One Between/Get Log/Move To Nearest Tree"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../..")
+method_name = "has_remaining_trees"
+
+[node name="Get Nearest Tree" type="Node" parent="AI/One Between/Get Log/Move To Nearest Tree"]
+script = ExtResource("7_f8o6y")
+blackboard_key = "nearest_tree"
+expression = "actor.get_nearest_tree()"
+
+[node name="Stop If Nearest Tree If Invalid" type="Node" parent="AI/One Between/Get Log"]
+script = ExtResource("4_c83di")
+
+[node name="OR" type="Node" parent="AI/One Between/Get Log/Stop If Nearest Tree If Invalid"]
+script = ExtResource("3_5vl0m")
+
+[node name="Tree Is Valid" type="Node" parent="AI/One Between/Get Log/Stop If Nearest Tree If Invalid/OR"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../../..")
+method_name = "tree_is_valid"
+method_arguments = Array[String](["blackboard.get_data(\"nearest_tree\")"])
+
+[node name="Stop Because Tree Invalid" type="Node" parent="AI/One Between/Get Log/Stop If Nearest Tree If Invalid/OR"]
+script = ExtResource("9_ojjv5")
+
+[node name="Delete Nearest Tree Ref" type="Node" parent="AI/One Between/Get Log/Stop If Nearest Tree If Invalid/OR/Stop Because Tree Invalid"]
+script = ExtResource("10_7bq5a")
+blackboard_key = "nearest_tree"
+
+[node name="Move To Tree" type="Node" parent="AI/One Between/Get Log"]
+script = ExtResource("8_mjt8i")
+method_owner_path = NodePath("../../../..")
+method_name = "move_to"
+method_arguments = Array[String](["delta", "blackboard.get_data(\"nearest_tree\")"])
+
+[node name="Cut Tree" type="Node" parent="AI/One Between/Get Log"]
+script = ExtResource("4_c83di")
+
+[node name="Nearest Tree Is Valid" type="Node" parent="AI/One Between/Get Log/Cut Tree"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../..")
+method_name = "tree_is_valid"
+method_arguments = Array[String](["blackboard.get_data(\"nearest_tree\")"])
+
+[node name="Can Carry More Logs" type="Node" parent="AI/One Between/Get Log/Cut Tree"]
+script = ExtResource("6_wov0v")
+method_owner_path = NodePath("../../../../..")
+method_name = "can_carry_more_logs"
+
+[node name="Cut Log From Tree" type="Node" parent="AI/One Between/Get Log/Cut Tree"]
+script = ExtResource("8_mjt8i")
+method_owner_path = NodePath("../../../../..")
+method_name = "cut_log"
+method_arguments = Array[String](["blackboard.get_data(\"nearest_tree\")"])
diff --git a/examples/simple_ai_logic/main.gd b/examples/simple_ai_logic/main.gd
new file mode 100644
index 0000000..905a50a
--- /dev/null
+++ b/examples/simple_ai_logic/main.gd
@@ -0,0 +1,36 @@
+extends Node2D
+
+#------------------------------------------
+# Signaux
+#------------------------------------------
+
+#------------------------------------------
+# Exports
+#------------------------------------------
+
+#------------------------------------------
+# Variables publiques
+#------------------------------------------
+
+#------------------------------------------
+# Variables privées
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions Godot redéfinies
+#------------------------------------------
+
+func _process(_delta:float) -> void:
+ if Input.is_action_just_released("ui_accept"):
+ var new_tree:Node2D = preload("res://examples/simple_ai_logic/env/tree.tscn").instantiate()
+ new_tree.global_position = get_global_mouse_position()
+ $Trees.add_child(new_tree)
+
+#------------------------------------------
+# Fonctions publiques
+#------------------------------------------
+
+#------------------------------------------
+# Fonctions privées
+#------------------------------------------
+
diff --git a/examples/simple_ai_logic/main.tscn b/examples/simple_ai_logic/main.tscn
new file mode 100644
index 0000000..bf2ee68
--- /dev/null
+++ b/examples/simple_ai_logic/main.tscn
@@ -0,0 +1,104 @@
+[gd_scene load_steps=8 format=3 uid="uid://ccttcdvcigdoe"]
+
+[ext_resource type="Script" path="res://examples/simple_ai_logic/main.gd" id="1_23dkn"]
+[ext_resource type="PackedScene" uid="uid://i4372roabk7a" path="res://examples/simple_ai_logic/env/warehouse.tscn" id="2_hh1py"]
+[ext_resource type="PackedScene" path="res://examples/simple_ai_logic/env/tree.tscn" id="3_qyub5"]
+[ext_resource type="PackedScene" uid="uid://bl8kvanpmae2g" path="res://examples/simple_ai_logic/lumberjack.tscn" id="4_782kh"]
+
+[sub_resource type="Gradient" id="Gradient_rssdc"]
+colors = PackedColorArray(0.787759, 0.787759, 0.787759, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_ql20u"]
+noise_type = 2
+frequency = 0.005
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_181qe"]
+width = 2050
+height = 2050
+color_ramp = SubResource("Gradient_rssdc")
+noise = SubResource("FastNoiseLite_ql20u")
+
+[node name="World" type="Node2D"]
+script = ExtResource("1_23dkn")
+
+[node name="Background" type="Polygon2D" parent="."]
+color = Color(0.466667, 0.670588, 0, 1)
+texture = SubResource("NoiseTexture2D_181qe")
+polygon = PackedVector2Array(-7, -7, -7, -7, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, -7, 651, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1160, 655, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7, 1159, -7)
+
+[node name="Warehouses" type="Node2D" parent="."]
+
+[node name="Warehouse1" parent="Warehouses" instance=ExtResource("2_hh1py")]
+position = Vector2(805, 229)
+
+[node name="Warehouse2" parent="Warehouses" instance=ExtResource("2_hh1py")]
+position = Vector2(268, 556)
+
+[node name="Trees" type="Node2D" parent="."]
+y_sort_enabled = true
+
+[node name="Tree" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(132, 126)
+
+[node name="Tree2" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(46, 69)
+
+[node name="Tree3" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(186, 110)
+
+[node name="Tree4" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(78, 126)
+
+[node name="Tree5" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(140, 185)
+
+[node name="Tree6" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(83, 215)
+
+[node name="Tree7" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(581, 604)
+
+[node name="Tree8" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(465, 473)
+
+[node name="Tree9" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(503, 523)
+
+[node name="Tree10" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(541, 495)
+
+[node name="Tree11" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(1044, 124)
+
+[node name="Tree12" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(1074, 235)
+
+[node name="Tree13" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(976, 178)
+
+[node name="Tree14" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(1035, 188)
+
+[node name="Tree15" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(1091, 155)
+
+[node name="Tree16" parent="Trees" instance=ExtResource("3_qyub5")]
+position = Vector2(998, 101)
+
+[node name="GUI" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 0
+offset_right = 40.0
+offset_bottom = 40.0
+
+[node name="ControlDesc" type="Label" parent="GUI"]
+layout_mode = 0
+offset_left = 23.0
+offset_top = 605.0
+offset_right = 371.0
+offset_bottom = 631.0
+text = "SPACE : plant a tree at mouse location"
+
+[node name="LumberJack" parent="." instance=ExtResource("4_782kh")]
+position = Vector2(685, 300)
+scale = Vector2(3.5, 3.5)
diff --git a/project.godot b/project.godot
index 08cfe7d..6f2f76c 100644
--- a/project.godot
+++ b/project.godot
@@ -20,7 +20,7 @@ config/windows_native_icon="res://icon.ico"
[editor_plugins]
-enabled=PackedStringArray("res://addons/simple-state/plugin.cfg", "res://addons/simplelicense/plugin.cfg")
+enabled=PackedStringArray("res://addons/simplelicense/plugin.cfg", "res://addons/fontawesome/plugin.cfg", "res://addons/SpritesheetGenerator/plugin.cfg", "res://addons/yet_another_behavior_tree/plugin.cfg")
[input]
diff --git a/script_templates/.gdignore b/script_templates/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/script_templates/BTNode/default.gd b/script_templates/BTNode/default.gd
new file mode 100644
index 0000000..880773c
--- /dev/null
+++ b/script_templates/BTNode/default.gd
@@ -0,0 +1,4 @@
+extends _BASE_
+
+func tick(actor:Node2D, blackboard:BTBlackboard) -> int:
+ return BTTickResult.SUCCESS