From f2da71a00339e6c181de4cba36dec854289eb051 Mon Sep 17 00:00:00 2001
From: chendian <->
Date: Mon, 30 Oct 2023 00:34:13 +0800
Subject: [PATCH] ai
---
addons/beehave/LICENSE | 21 ++
addons/beehave/blackboard.gd | 33 +++
addons/beehave/debug/debugger.gd | 91 ++++++
addons/beehave/debug/debugger_messages.gd | 31 +++
addons/beehave/debug/debugger_tab.gd | 107 ++++++++
addons/beehave/debug/frames.gd | 33 +++
addons/beehave/debug/global_debugger.gd | 38 +++
addons/beehave/debug/graph_edit.gd | 259 ++++++++++++++++++
addons/beehave/debug/graph_node.gd | 145 ++++++++++
.../beehave/debug/icons/horizontal_layout.svg | 1 +
.../debug/icons/horizontal_layout.svg.import | 38 +++
addons/beehave/debug/icons/port_bottom.svg | 1 +
.../debug/icons/port_bottom.svg.import | 38 +++
addons/beehave/debug/icons/port_left.svg | 1 +
.../beehave/debug/icons/port_left.svg.import | 38 +++
addons/beehave/debug/icons/port_right.svg | 1 +
.../beehave/debug/icons/port_right.svg.import | 38 +++
addons/beehave/debug/icons/port_top.svg | 1 +
.../beehave/debug/icons/port_top.svg.import | 38 +++
.../beehave/debug/icons/vertical_layout.svg | 1 +
.../debug/icons/vertical_layout.svg.import | 38 +++
addons/beehave/debug/tree_node.gd | 255 +++++++++++++++++
addons/beehave/icons/action.svg | 38 +++
addons/beehave/icons/action.svg.import | 38 +++
addons/beehave/icons/blackboard.svg | 38 +++
addons/beehave/icons/blackboard.svg.import | 38 +++
addons/beehave/icons/category_bt.svg | 38 +++
addons/beehave/icons/category_bt.svg.import | 38 +++
addons/beehave/icons/category_composite.svg | 38 +++
.../icons/category_composite.svg.import | 38 +++
addons/beehave/icons/category_decorator.svg | 38 +++
.../icons/category_decorator.svg.import | 38 +++
addons/beehave/icons/category_leaf.svg | 38 +++
addons/beehave/icons/category_leaf.svg.import | 38 +++
addons/beehave/icons/condition.svg | 38 +++
addons/beehave/icons/condition.svg.import | 38 +++
addons/beehave/icons/failer.svg | 38 +++
addons/beehave/icons/failer.svg.import | 38 +++
addons/beehave/icons/inverter.svg | 38 +++
addons/beehave/icons/inverter.svg.import | 38 +++
addons/beehave/icons/limiter.svg | 38 +++
addons/beehave/icons/limiter.svg.import | 38 +++
addons/beehave/icons/selector.svg | 38 +++
addons/beehave/icons/selector.svg.import | 38 +++
addons/beehave/icons/selector_random.svg | 35 +++
.../beehave/icons/selector_random.svg.import | 38 +++
addons/beehave/icons/selector_reactive.svg | 45 +++
.../icons/selector_reactive.svg.import | 38 +++
addons/beehave/icons/sequence.svg | 38 +++
addons/beehave/icons/sequence.svg.import | 38 +++
addons/beehave/icons/sequence_random.svg | 38 +++
.../beehave/icons/sequence_random.svg.import | 38 +++
addons/beehave/icons/sequence_reactive.svg | 60 ++++
.../icons/sequence_reactive.svg.import | 38 +++
addons/beehave/icons/succeeder.svg | 38 +++
addons/beehave/icons/succeeder.svg.import | 38 +++
addons/beehave/icons/tree.svg | 38 +++
addons/beehave/icons/tree.svg.import | 38 +++
.../beehave/metrics/beehave_global_metrics.gd | 54 ++++
addons/beehave/nodes/beehave_node.gd | 49 ++++
addons/beehave/nodes/beehave_tree.gd | 224 +++++++++++++++
addons/beehave/nodes/composites/composite.gd | 40 +++
.../nodes/composites/randomized_composite.gd | 152 ++++++++++
addons/beehave/nodes/composites/selector.gd | 69 +++++
.../nodes/composites/selector_random.gd | 80 ++++++
.../nodes/composites/selector_reactive.gd | 45 +++
addons/beehave/nodes/composites/sequence.gd | 76 +++++
.../nodes/composites/sequence_random.gd | 92 +++++++
.../nodes/composites/sequence_reactive.gd | 62 +++++
.../beehave/nodes/composites/sequence_star.gd | 58 ++++
addons/beehave/nodes/decorators/decorator.gd | 41 +++
addons/beehave/nodes/decorators/failer.gd | 34 +++
addons/beehave/nodes/decorators/inverter.gd | 42 +++
addons/beehave/nodes/decorators/limiter.gd | 41 +++
addons/beehave/nodes/decorators/succeeder.gd | 34 +++
.../beehave/nodes/decorators/time_limiter.gd | 46 ++++
addons/beehave/nodes/leaves/action.gd | 13 +
.../nodes/leaves/blackboard_compare.gd | 61 +++++
.../beehave/nodes/leaves/blackboard_erase.gd | 25 ++
addons/beehave/nodes/leaves/blackboard_has.gd | 23 ++
addons/beehave/nodes/leaves/blackboard_set.gd | 34 +++
addons/beehave/nodes/leaves/condition.gd | 11 +
addons/beehave/nodes/leaves/leaf.gd | 46 ++++
addons/beehave/plugin.cfg | 7 +
addons/beehave/plugin.gd | 24 ++
addons/beehave/utils/utils.gd | 22 ++
.../main_screen/selection_actions.tscn | 4 +-
.../saved_state.json | 7 +-
.../typed_editors/dock_array.tscn | 4 +-
.../typed_editors/dock_dict.tscn | 4 +-
.../typed_editors/dock_enum_array.tscn | 4 +-
config/character_move/normal.tres | 4 +-
.../hero01_long_air_attack03.tres | 2 +-
config/player_skill/hero01_long_attack03.tres | 2 +-
config/skill/hero01.tres | 12 -
config/skill/monster01_attack01.tres | 14 +
project.godot | 6 +-
render/mesh/slash1.obj.import | 1 -
render/mesh/slash2.obj.import | 1 -
render/process_material/slash_normal.tres | 10 +-
render/texture/{ => shape}/explodeDecal.png | Bin
.../{ => shape}/explodeDecal.png.import | 6 +-
render/texture/{ => shape}/readiness_hero.png | Bin
.../{ => shape}/readiness_hero.png.import | 6 +-
.../texture/{ => shape}/readiness_monster.png | Bin
.../{ => shape}/readiness_monster.png.import | 6 +-
render/texture/{ => shape}/swordDecal.png | Bin
.../texture/{ => shape}/swordDecal.png.import | 6 +-
.../skill_animation/hero01_long_attack01.tres | 2 +-
.../skill_animation/hero01_long_attack02.tres | 2 +-
.../skill_animation/hero01_long_attack03.tres | 2 +-
.../skill_animation/hero01_long_attack04.tres | 2 +-
.../skill_animation/hero01_long_flash.tres | 2 +-
.../skill_animation/hero01_long_skill01.tres | 2 +-
.../skill_animation/monster01_attack01.tres | 43 +++
.../animation_library.tres | 6 +-
scene/ai/action/find_target.tscn | 6 +
scene/ai/action/move_to_target.tscn | 6 +
scene/character/character.tscn | 1 +
scene/character/monster.tscn | 25 +-
scene/effect/afterimage/normal.tscn | 1 +
scene/effect/decal/readiness_hero.tscn | 2 +-
scene/effect/decal/readiness_monster.tscn | 2 +-
.../particle/particle_slash_normal.tscn | 1 -
scene/launcher.tscn | 15 +-
script/_global/setting.gd | 2 +-
script/ai/action.gd | 21 ++
script/ai/action/action_find_target.gd | 6 +
script/ai/action_with_target.gd | 11 +
.../action_move_to_target.gd | 12 +
script/character/battle.gd | 5 +-
script/character/character.gd | 2 +
script/character/effect.gd | 8 +-
script/character/monster/ai.gd | 8 +
script/character/move.gd | 2 +-
script/character/status.gd | 1 +
script/character/view.gd | 8 +-
script/manager/character_manager.gd | 7 +
138 files changed, 4382 insertions(+), 76 deletions(-)
create mode 100644 addons/beehave/LICENSE
create mode 100644 addons/beehave/blackboard.gd
create mode 100644 addons/beehave/debug/debugger.gd
create mode 100644 addons/beehave/debug/debugger_messages.gd
create mode 100644 addons/beehave/debug/debugger_tab.gd
create mode 100644 addons/beehave/debug/frames.gd
create mode 100644 addons/beehave/debug/global_debugger.gd
create mode 100644 addons/beehave/debug/graph_edit.gd
create mode 100644 addons/beehave/debug/graph_node.gd
create mode 100644 addons/beehave/debug/icons/horizontal_layout.svg
create mode 100644 addons/beehave/debug/icons/horizontal_layout.svg.import
create mode 100644 addons/beehave/debug/icons/port_bottom.svg
create mode 100644 addons/beehave/debug/icons/port_bottom.svg.import
create mode 100644 addons/beehave/debug/icons/port_left.svg
create mode 100644 addons/beehave/debug/icons/port_left.svg.import
create mode 100644 addons/beehave/debug/icons/port_right.svg
create mode 100644 addons/beehave/debug/icons/port_right.svg.import
create mode 100644 addons/beehave/debug/icons/port_top.svg
create mode 100644 addons/beehave/debug/icons/port_top.svg.import
create mode 100644 addons/beehave/debug/icons/vertical_layout.svg
create mode 100644 addons/beehave/debug/icons/vertical_layout.svg.import
create mode 100644 addons/beehave/debug/tree_node.gd
create mode 100644 addons/beehave/icons/action.svg
create mode 100644 addons/beehave/icons/action.svg.import
create mode 100644 addons/beehave/icons/blackboard.svg
create mode 100644 addons/beehave/icons/blackboard.svg.import
create mode 100644 addons/beehave/icons/category_bt.svg
create mode 100644 addons/beehave/icons/category_bt.svg.import
create mode 100644 addons/beehave/icons/category_composite.svg
create mode 100644 addons/beehave/icons/category_composite.svg.import
create mode 100644 addons/beehave/icons/category_decorator.svg
create mode 100644 addons/beehave/icons/category_decorator.svg.import
create mode 100644 addons/beehave/icons/category_leaf.svg
create mode 100644 addons/beehave/icons/category_leaf.svg.import
create mode 100644 addons/beehave/icons/condition.svg
create mode 100644 addons/beehave/icons/condition.svg.import
create mode 100644 addons/beehave/icons/failer.svg
create mode 100644 addons/beehave/icons/failer.svg.import
create mode 100644 addons/beehave/icons/inverter.svg
create mode 100644 addons/beehave/icons/inverter.svg.import
create mode 100644 addons/beehave/icons/limiter.svg
create mode 100644 addons/beehave/icons/limiter.svg.import
create mode 100644 addons/beehave/icons/selector.svg
create mode 100644 addons/beehave/icons/selector.svg.import
create mode 100644 addons/beehave/icons/selector_random.svg
create mode 100644 addons/beehave/icons/selector_random.svg.import
create mode 100644 addons/beehave/icons/selector_reactive.svg
create mode 100644 addons/beehave/icons/selector_reactive.svg.import
create mode 100644 addons/beehave/icons/sequence.svg
create mode 100644 addons/beehave/icons/sequence.svg.import
create mode 100644 addons/beehave/icons/sequence_random.svg
create mode 100644 addons/beehave/icons/sequence_random.svg.import
create mode 100644 addons/beehave/icons/sequence_reactive.svg
create mode 100644 addons/beehave/icons/sequence_reactive.svg.import
create mode 100644 addons/beehave/icons/succeeder.svg
create mode 100644 addons/beehave/icons/succeeder.svg.import
create mode 100644 addons/beehave/icons/tree.svg
create mode 100644 addons/beehave/icons/tree.svg.import
create mode 100644 addons/beehave/metrics/beehave_global_metrics.gd
create mode 100644 addons/beehave/nodes/beehave_node.gd
create mode 100644 addons/beehave/nodes/beehave_tree.gd
create mode 100644 addons/beehave/nodes/composites/composite.gd
create mode 100644 addons/beehave/nodes/composites/randomized_composite.gd
create mode 100644 addons/beehave/nodes/composites/selector.gd
create mode 100644 addons/beehave/nodes/composites/selector_random.gd
create mode 100644 addons/beehave/nodes/composites/selector_reactive.gd
create mode 100644 addons/beehave/nodes/composites/sequence.gd
create mode 100644 addons/beehave/nodes/composites/sequence_random.gd
create mode 100644 addons/beehave/nodes/composites/sequence_reactive.gd
create mode 100644 addons/beehave/nodes/composites/sequence_star.gd
create mode 100644 addons/beehave/nodes/decorators/decorator.gd
create mode 100644 addons/beehave/nodes/decorators/failer.gd
create mode 100644 addons/beehave/nodes/decorators/inverter.gd
create mode 100644 addons/beehave/nodes/decorators/limiter.gd
create mode 100644 addons/beehave/nodes/decorators/succeeder.gd
create mode 100644 addons/beehave/nodes/decorators/time_limiter.gd
create mode 100644 addons/beehave/nodes/leaves/action.gd
create mode 100644 addons/beehave/nodes/leaves/blackboard_compare.gd
create mode 100644 addons/beehave/nodes/leaves/blackboard_erase.gd
create mode 100644 addons/beehave/nodes/leaves/blackboard_has.gd
create mode 100644 addons/beehave/nodes/leaves/blackboard_set.gd
create mode 100644 addons/beehave/nodes/leaves/condition.gd
create mode 100644 addons/beehave/nodes/leaves/leaf.gd
create mode 100644 addons/beehave/plugin.cfg
create mode 100644 addons/beehave/plugin.gd
create mode 100644 addons/beehave/utils/utils.gd
delete mode 100644 config/skill/hero01.tres
create mode 100644 config/skill/monster01_attack01.tres
rename render/texture/{ => shape}/explodeDecal.png (100%)
rename render/texture/{ => shape}/explodeDecal.png.import (70%)
rename render/texture/{ => shape}/readiness_hero.png (100%)
rename render/texture/{ => shape}/readiness_hero.png.import (69%)
rename render/texture/{ => shape}/readiness_monster.png (100%)
rename render/texture/{ => shape}/readiness_monster.png.import (69%)
rename render/texture/{ => shape}/swordDecal.png (100%)
rename render/texture/{ => shape}/swordDecal.png.import (70%)
create mode 100644 resource/skill_animation/monster01_attack01.tres
create mode 100644 scene/ai/action/find_target.tscn
create mode 100644 scene/ai/action/move_to_target.tscn
create mode 100644 script/ai/action.gd
create mode 100644 script/ai/action/action_find_target.gd
create mode 100644 script/ai/action_with_target.gd
create mode 100644 script/ai/action_with_target/action_move_to_target.gd
create mode 100644 script/character/monster/ai.gd
diff --git a/addons/beehave/LICENSE b/addons/beehave/LICENSE
new file mode 100644
index 0000000..caabbff
--- /dev/null
+++ b/addons/beehave/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 bitbrain
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/beehave/blackboard.gd b/addons/beehave/blackboard.gd
new file mode 100644
index 0000000..5655e89
--- /dev/null
+++ b/addons/beehave/blackboard.gd
@@ -0,0 +1,33 @@
+## The blackboard is an object that can be used to store and access data between
+## multiple nodes of the behavior tree.
+@icon("icons/blackboard.svg")
+class_name Blackboard extends Node
+
+var blackboard: Dictionary = {}
+
+func keys() -> Array[String]:
+ var keys: Array[String]
+ keys.assign(blackboard.keys().duplicate())
+ return keys
+
+
+func set_value(key: Variant, value: Variant, blackboard_name: String = 'default') -> void:
+ if not blackboard.has(blackboard_name):
+ blackboard[blackboard_name] = {}
+
+ blackboard[blackboard_name][key] = value
+
+
+func get_value(key: Variant, default_value: Variant = null, blackboard_name: String = 'default') -> Variant:
+ if has_value(key, blackboard_name):
+ return blackboard[blackboard_name].get(key, default_value)
+ return default_value
+
+
+func has_value(key: Variant, blackboard_name: String = 'default') -> bool:
+ return blackboard.has(blackboard_name) and blackboard[blackboard_name].has(key) and blackboard[blackboard_name][key] != null
+
+
+func erase_value(key: Variant, blackboard_name: String = 'default') -> void:
+ if blackboard.has(blackboard_name):
+ blackboard[blackboard_name][key] = null
diff --git a/addons/beehave/debug/debugger.gd b/addons/beehave/debug/debugger.gd
new file mode 100644
index 0000000..1ccd097
--- /dev/null
+++ b/addons/beehave/debug/debugger.gd
@@ -0,0 +1,91 @@
+@tool
+extends EditorDebuggerPlugin
+
+const DebuggerTab := preload("debugger_tab.gd")
+var debugger_tab := DebuggerTab.new()
+var floating_window: Window
+var session: EditorDebuggerSession
+
+
+func _has_capture(prefix: String) -> bool:
+ return prefix == "beehave"
+
+
+func _capture(message: String, data: Array, session_id: int) -> bool:
+ # in case the behavior tree has invalid setup this might be null
+ if debugger_tab == null:
+ return false
+
+ if message == "beehave:register_tree":
+ debugger_tab.register_tree(data[0])
+ return true
+ if message == "beehave:unregister_tree":
+ debugger_tab.unregister_tree(data[0])
+ return true
+ if message == "beehave:process_tick":
+ debugger_tab.graph.process_tick(data[0], data[1])
+ return true
+ if message == "beehave:process_begin":
+ debugger_tab.graph.process_begin(data[0])
+ return true
+ if message == "beehave:process_end":
+ debugger_tab.graph.process_end(data[0])
+ return true
+ return false
+
+
+func _setup_session(session_id: int) -> void:
+ session = get_session(session_id)
+ session.started.connect(debugger_tab.start)
+ session.stopped.connect(debugger_tab.stop)
+
+ debugger_tab.name = "🐝 Beehave"
+ debugger_tab.make_floating.connect(_on_make_floating)
+ debugger_tab.session = session
+ session.add_session_tab(debugger_tab)
+
+
+func _on_make_floating() -> void:
+ var plugin := BeehaveUtils.get_plugin()
+ if not plugin:
+ return
+ if floating_window:
+ _on_window_close_requested()
+ return
+
+ var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale()
+ var editor_interface: EditorInterface = plugin.get_editor_interface()
+ var editor_main_screen = editor_interface.get_editor_main_screen()
+ debugger_tab.get_parent().remove_child(debugger_tab)
+
+ floating_window = Window.new()
+
+ var panel := Panel.new()
+ panel.add_theme_stylebox_override("panel", editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles"))
+ panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ floating_window.add_child(panel)
+
+ var margin := MarginContainer.new()
+ margin.add_child(debugger_tab)
+ margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ margin.add_theme_constant_override("margin_right", border_size.x)
+ margin.add_theme_constant_override("margin_left", border_size.x)
+ margin.add_theme_constant_override("margin_top", border_size.y)
+ margin.add_theme_constant_override("margin_bottom", border_size.y)
+ panel.add_child(margin)
+
+ floating_window.title = "🐝 Beehave"
+ floating_window.wrap_controls = true
+ floating_window.min_size = Vector2i(600, 350)
+ floating_window.size = debugger_tab.size
+ floating_window.position = editor_main_screen.global_position
+ floating_window.transient = true
+ floating_window.close_requested.connect(_on_window_close_requested)
+ editor_interface.get_base_control().add_child(floating_window)
+
+
+func _on_window_close_requested() -> void:
+ debugger_tab.get_parent().remove_child(debugger_tab)
+ session.add_session_tab(debugger_tab)
+ floating_window.queue_free()
+ floating_window = null
diff --git a/addons/beehave/debug/debugger_messages.gd b/addons/beehave/debug/debugger_messages.gd
new file mode 100644
index 0000000..3fb49fb
--- /dev/null
+++ b/addons/beehave/debug/debugger_messages.gd
@@ -0,0 +1,31 @@
+class_name BeehaveDebuggerMessages
+
+
+static func can_send_message() -> bool:
+ return not Engine.is_editor_hint() and OS.has_feature("editor")
+
+
+static func register_tree(beehave_tree: Dictionary) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:register_tree", [beehave_tree])
+
+
+static func unregister_tree(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:unregister_tree", [instance_id])
+
+
+static func process_tick(instance_id: int, status: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_tick", [instance_id, status])
+
+
+static func process_begin(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_begin", [instance_id])
+
+
+static func process_end(instance_id: int) -> void:
+ if can_send_message():
+ EngineDebugger.send_message("beehave:process_end", [instance_id])
+
diff --git a/addons/beehave/debug/debugger_tab.gd b/addons/beehave/debug/debugger_tab.gd
new file mode 100644
index 0000000..3987d77
--- /dev/null
+++ b/addons/beehave/debug/debugger_tab.gd
@@ -0,0 +1,107 @@
+@tool
+extends PanelContainer
+
+signal make_floating()
+
+const BeehaveGraphEdit := preload("graph_edit.gd")
+const TREE_ICON := preload("../icons/tree.svg")
+
+var container: HSplitContainer
+var item_list: ItemList
+var graph: BeehaveGraphEdit
+var message: Label
+
+var active_trees: Dictionary
+var active_tree_id: int = -1
+var session: EditorDebuggerSession
+
+
+func _ready() -> void:
+ container = HSplitContainer.new()
+ add_child(container)
+
+ item_list = ItemList.new()
+ item_list.custom_minimum_size = Vector2(200, 0)
+ item_list.item_selected.connect(_on_item_selected)
+ container.add_child(item_list)
+
+ graph = BeehaveGraphEdit.new()
+ container.add_child(graph)
+
+ message = Label.new()
+ message.text = "Run Project for debugging"
+ message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ message.set_anchors_preset(Control.PRESET_CENTER)
+ add_child(message)
+
+ var button := Button.new()
+ button.flat = true
+ button.icon = get_theme_icon(&"ExternalLink", &"EditorIcons")
+ button.pressed.connect(func(): make_floating.emit())
+ button.tooltip_text = "Make floating"
+ button.focus_mode = Control.FOCUS_NONE
+ graph.get_zoom_hbox().add_child(button)
+
+ var toggle_button := Button.new()
+ toggle_button.flat = true
+ toggle_button.icon = get_theme_icon(&"Back", &"EditorIcons")
+ toggle_button.pressed.connect(_on_toggle_button_pressed.bind(toggle_button))
+ toggle_button.tooltip_text = "Toggle Panel"
+ toggle_button.focus_mode = Control.FOCUS_NONE
+ graph.get_zoom_hbox().add_child(toggle_button)
+ graph.get_zoom_hbox().move_child(toggle_button, 0)
+
+ stop()
+ visibility_changed.connect(_on_visibility_changed)
+
+
+func start() -> void:
+ container.visible = true
+ message.visible = false
+
+
+func stop() -> void:
+ container.visible = false
+ message.visible = true
+
+ active_trees.clear()
+ item_list.clear()
+ graph.beehave_tree = {}
+
+
+func register_tree(data: Dictionary) -> void:
+ var idx := item_list.add_item(data.name, TREE_ICON)
+ item_list.set_item_tooltip(idx, data.path)
+ item_list.set_item_metadata(idx, data.id)
+ active_trees[data.id] = data
+
+
+func unregister_tree(instance_id: int) -> void:
+ var id := str(instance_id)
+ for i in item_list.item_count:
+ if item_list.get_item_metadata(i) == id:
+ item_list.remove_item(i)
+ break
+
+ active_trees.erase(id)
+
+ if graph.beehave_tree.get("id", "") == id:
+ graph.beehave_tree = {}
+
+
+func _on_toggle_button_pressed(toggle_button: Button) -> void:
+ item_list.visible = !item_list.visible
+ toggle_button.icon = get_theme_icon(&"Back" if item_list.visible else &"Forward", &"EditorIcons")
+
+
+func _on_item_selected(idx: int) -> void:
+ var id: StringName = item_list.get_item_metadata(idx)
+ graph.beehave_tree = active_trees.get(id, {})
+
+ active_tree_id = id.to_int()
+ session.send_message("beehave:activate_tree", [active_tree_id])
+
+
+func _on_visibility_changed() -> void:
+ session.send_message("beehave:visibility_changed", [visible and is_visible_in_tree()])
diff --git a/addons/beehave/debug/frames.gd b/addons/beehave/debug/frames.gd
new file mode 100644
index 0000000..0fb6d2a
--- /dev/null
+++ b/addons/beehave/debug/frames.gd
@@ -0,0 +1,33 @@
+@tool
+extends RefCounted
+
+const SUCCESS_COLOR := Color("#009944c8")
+const NORMAL_COLOR := Color("#15181e")
+const FAILURE_COLOR := Color("#cf000f80")
+const RUNNING_COLOR := Color("#ffcc00c8")
+
+var empty: StyleBoxEmpty
+var normal: StyleBoxFlat
+var success: StyleBoxFlat
+var failure: StyleBoxFlat
+var running: StyleBoxFlat
+
+
+func _init() -> void:
+ var plugin := BeehaveUtils.get_plugin()
+ if not plugin:
+ return
+
+ var editor_scale := BeehaveUtils.get_editor_scale()
+
+ empty = StyleBoxEmpty.new()
+
+ normal = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"frame", &"GraphNode").duplicate()
+
+ success = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"selected_frame", &"GraphNode").duplicate()
+ failure = success.duplicate()
+ running = success.duplicate()
+
+ success.border_color = SUCCESS_COLOR
+ failure.border_color = FAILURE_COLOR
+ running.border_color = RUNNING_COLOR
diff --git a/addons/beehave/debug/global_debugger.gd b/addons/beehave/debug/global_debugger.gd
new file mode 100644
index 0000000..27c72c2
--- /dev/null
+++ b/addons/beehave/debug/global_debugger.gd
@@ -0,0 +1,38 @@
+extends Node
+
+var _registered_trees: Dictionary
+var _active_tree: BeehaveTree
+
+
+func _enter_tree() -> void:
+ EngineDebugger.register_message_capture("beehave", _on_debug_message)
+
+
+func _on_debug_message(message: String, data: Array) -> bool:
+ if message == "activate_tree":
+ _set_active_tree(data[0])
+ return true
+ if message == "visibility_changed":
+ if _active_tree:
+ _active_tree._can_send_message = data[0]
+ return true
+ return false
+
+
+func _set_active_tree(tree_id: int) -> void:
+ var tree: BeehaveTree = _registered_trees.get(tree_id, null)
+ if not tree:
+ return
+
+ if _active_tree:
+ _active_tree._can_send_message = false
+ _active_tree = tree
+ _active_tree._can_send_message = true
+
+
+func register_tree(tree: BeehaveTree) -> void:
+ _registered_trees[tree.get_instance_id()] = tree
+
+
+func unregister_tree(tree: BeehaveTree) -> void:
+ _registered_trees.erase(tree.get_instance_id())
diff --git a/addons/beehave/debug/graph_edit.gd b/addons/beehave/debug/graph_edit.gd
new file mode 100644
index 0000000..afdcd1e
--- /dev/null
+++ b/addons/beehave/debug/graph_edit.gd
@@ -0,0 +1,259 @@
+@tool
+extends GraphEdit
+
+const BeehaveGraphNode := preload("graph_node.gd")
+
+const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
+const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
+
+const PROGRESS_SHIFT: int = 50
+const INACTIVE_COLOR: Color = Color("#898989aa")
+const ACTIVE_COLOR: Color = Color("#ffcc00c8")
+const SUCCESS_COLOR: Color = Color("#009944c8")
+
+
+var updating_graph: bool = false
+var arraging_nodes: bool = false
+var beehave_tree: Dictionary:
+ set(value):
+ if beehave_tree == value:
+ return
+ beehave_tree = value
+ active_nodes.clear()
+ _update_graph()
+
+var horizontal_layout: bool = false:
+ set(value):
+ if updating_graph or arraging_nodes:
+ return
+ if horizontal_layout == value:
+ return
+ horizontal_layout = value
+ _update_layout_button()
+ _update_graph()
+
+
+var active_nodes: Array[String]
+var progress: int = 0
+var layout_button: Button
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(100, 300)
+ arrange_nodes_button_hidden = true
+ minimap_enabled = false
+
+ layout_button = Button.new()
+ layout_button.flat = true
+ layout_button.focus_mode = Control.FOCUS_NONE
+ layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
+ get_zoom_hbox().add_child(layout_button)
+ _update_layout_button()
+
+
+func _update_graph() -> void:
+ if updating_graph:
+ return
+
+ updating_graph = true
+
+ clear_connections()
+
+ for child in get_children():
+ remove_child(child)
+ child.queue_free()
+
+ if not beehave_tree.is_empty():
+ _add_nodes(beehave_tree)
+ _connect_nodes(beehave_tree)
+ _arrange_nodes.call_deferred(beehave_tree)
+
+ updating_graph = false
+
+
+func _add_nodes(node: Dictionary) -> void:
+ if node.is_empty():
+ return
+ var gnode := BeehaveGraphNode.new(horizontal_layout)
+ add_child(gnode)
+ gnode.title_text = node.name
+ gnode.name = node.id
+ gnode.icon = _get_icon(node.type.back())
+
+ if node.type.has(&"BeehaveTree"):
+ gnode.set_slots(false, true)
+ elif node.type.has(&"Leaf"):
+ gnode.set_slots(true, false)
+ elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
+ gnode.set_slots(true, true)
+
+ for child in node.get("children", []):
+ _add_nodes(child)
+
+
+func _connect_nodes(node: Dictionary) -> void:
+ for child in node.get("children", []):
+ connect_node(node.id, 0, child.id, 0)
+ _connect_nodes(child)
+
+
+func _arrange_nodes(node: Dictionary) -> void:
+ if arraging_nodes:
+ return
+
+ arraging_nodes = true
+
+ var tree_node := _create_tree_nodes(node)
+ tree_node.update_positions(horizontal_layout)
+ _place_nodes(tree_node)
+
+ arraging_nodes = false
+
+
+func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
+ var tree_node := TreeNode.new(get_node(node.id), root)
+ for child in node.get("children", []):
+ var child_node := _create_tree_nodes(child, tree_node)
+ tree_node.children.push_back(child_node)
+ return tree_node
+
+
+func _place_nodes(node: TreeNode) -> void:
+ node.item.position_offset = Vector2(node.x, node.y)
+ for child in node.children:
+ _place_nodes(child)
+
+
+func _get_icon(type: StringName) -> Texture2D:
+ var classes := ProjectSettings.get_global_class_list()
+ for c in classes:
+ if c["class"] == type:
+ var icon_path := c.get("icon", String())
+ if not icon_path.is_empty():
+ return load(icon_path)
+ return null
+
+
+func get_status(status: int) -> String:
+ if status == 0:
+ return "SUCCESS"
+ elif status == 1:
+ return "FAILURE"
+ return "RUNNING"
+
+
+func process_begin(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in get_children():
+ child.set_meta("status", -1)
+
+
+func process_tick(instance_id: int, status: int) -> void:
+ var node := get_node_or_null(str(instance_id))
+ if node:
+ node.text = "Status: %s" % get_status(status)
+ node.set_status(status)
+ node.set_meta("status", status)
+ if status == 0 or status == 2:
+ if not active_nodes.has(node.name):
+ active_nodes.push_back(node.name)
+
+
+func process_end(instance_id: int) -> void:
+ if not _is_same_tree(instance_id):
+ return
+
+ for child in get_children():
+ var status := child.get_meta("status", -1)
+ match status:
+ 0:
+ active_nodes.erase(child.name)
+ child.set_color(SUCCESS_COLOR)
+ 1:
+ active_nodes.erase(child.name)
+ child.set_color(INACTIVE_COLOR)
+ 2:
+ child.set_color(ACTIVE_COLOR)
+ _:
+ child.text = " "
+ child.set_status(status)
+ child.set_color(INACTIVE_COLOR)
+
+
+func _is_same_tree(instance_id: int) -> bool:
+ return str(instance_id) == beehave_tree.get("id", "")
+
+
+func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
+ var points: PackedVector2Array
+
+ from_position = from_position.round()
+ to_position = to_position.round()
+
+ points.push_back(from_position)
+
+ var mid_position := ((to_position + from_position) / 2).round()
+ if horizontal_layout:
+ points.push_back(Vector2(mid_position.x, from_position.y))
+ points.push_back(Vector2(mid_position.x, to_position.y))
+ else:
+ points.push_back(Vector2(from_position.x, mid_position.y))
+ points.push_back(Vector2(to_position.x, mid_position.y))
+
+ points.push_back(to_position)
+
+ return points
+
+
+func _process(delta: float) -> void:
+ if not active_nodes.is_empty():
+ progress += 10 if delta >= 0.05 else 1
+ if progress >= 1000:
+ progress = 0
+ queue_redraw()
+
+
+func _draw() -> void:
+ if active_nodes.is_empty():
+ return
+
+ var circle_size: float = max(3, 6 * zoom)
+ var progress_shift: float = PROGRESS_SHIFT * zoom
+
+ var connections := get_connection_list()
+ for c in connections:
+ if not c.from in active_nodes or not c.to in active_nodes:
+ continue
+ var from := get_node(String(c.from))
+ var to := get_node(String(c.to))
+
+ if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
+ return
+
+ var line := _get_connection_line(from.position + from.get_connection_output_position(c.from_port), to.position + to.get_connection_input_position(c.to_port))
+
+ var curve = Curve2D.new()
+ for l in line:
+ curve.add_point(l)
+
+ var max_steps := int(curve.get_baked_length())
+ var current_shift := progress % max_steps
+ var p := curve.sample_baked(current_shift)
+ draw_circle(p, circle_size, ACTIVE_COLOR)
+
+ var shift := current_shift - progress_shift
+ while shift >= 0:
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift -= progress_shift
+
+ shift = current_shift + progress_shift
+ while shift <= curve.get_baked_length():
+ draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
+ shift += progress_shift
+
+
+func _update_layout_button() -> void:
+ layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
+ layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
diff --git a/addons/beehave/debug/graph_node.gd b/addons/beehave/debug/graph_node.gd
new file mode 100644
index 0000000..6b262af
--- /dev/null
+++ b/addons/beehave/debug/graph_node.gd
@@ -0,0 +1,145 @@
+@tool
+extends GraphNode
+
+const DEFAULT_COLOR := Color("#dad4cb")
+
+const PORT_TOP_ICON := preload("icons/port_top.svg")
+const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
+const PORT_LEFT_ICON := preload("icons/port_left.svg")
+const PORT_RIGHT_ICON := preload("icons/port_right.svg")
+
+
+@export var title_text: String:
+ set(value):
+ title_text = value
+ if title_label:
+ title_label.text = value
+
+@export var text: String:
+ set(value):
+ text = value
+ if label:
+ label.text = " " if text.is_empty() else text
+
+@export var icon: Texture2D:
+ set(value):
+ icon = value
+ if icon_rect:
+ icon_rect.texture = value
+
+var layout_size: float:
+ get:
+ return size.y if horizontal else size.x
+
+
+var panel: PanelContainer
+var icon_rect: TextureRect
+var title_label: Label
+var container: VBoxContainer
+var label: Label
+
+var frames: RefCounted = BeehaveUtils.get_frames()
+var horizontal: bool = false
+
+
+func _init(horizontal: bool = false) -> void:
+ self.horizontal = horizontal
+
+
+func _ready() -> void:
+ custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
+ draggable = false
+
+ add_theme_stylebox_override("frame", frames.empty)
+ add_theme_stylebox_override("selected_frame", frames.empty)
+ add_theme_color_override("close_color", Color.TRANSPARENT)
+ add_theme_icon_override("close", ImageTexture.new())
+
+ # For top port
+ add_child(Control.new())
+
+ panel = PanelContainer.new()
+ panel.mouse_filter = Control.MOUSE_FILTER_PASS
+ panel.add_theme_stylebox_override("panel", frames.normal)
+ add_child(panel)
+
+ var vbox_container := VBoxContainer.new()
+ panel.add_child(vbox_container)
+
+ var title_size := 24 * BeehaveUtils.get_editor_scale()
+ var margin_container := MarginContainer.new()
+ margin_container.add_theme_constant_override("margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale())
+ margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
+ vbox_container.add_child(margin_container)
+
+ var title_container := HBoxContainer.new()
+ title_container.add_child(Control.new())
+ title_container.mouse_filter = Control.MOUSE_FILTER_PASS
+ title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ margin_container.add_child(title_container)
+
+ icon_rect = TextureRect.new()
+ icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ title_container.add_child(icon_rect)
+
+ title_label = Label.new()
+ title_label.add_theme_color_override("font_color", DEFAULT_COLOR)
+ title_label.add_theme_font_override("font", get_theme_font("title_font"))
+ title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ title_label.text = title_text
+ title_container.add_child(title_label)
+
+ title_container.add_child(Control.new())
+
+ container = VBoxContainer.new()
+ container.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ panel.add_child(container)
+
+ label = Label.new()
+ label.text = " " if text.is_empty() else text
+ container.add_child(label)
+
+ # For bottom port
+ add_child(Control.new())
+
+ minimum_size_changed.connect(_on_size_changed)
+ _on_size_changed.call_deferred()
+
+
+func set_status(status: int) -> void:
+ panel.add_theme_stylebox_override("panel", _get_stylebox(status))
+
+
+func _get_stylebox(status: int) -> StyleBox:
+ match status:
+ 0: return frames.success
+ 1: return frames.failure
+ 2: return frames.running
+ _: return frames.normal
+
+
+func set_slots(left_enabled: bool, right_enabled: bool) -> void:
+ if horizontal:
+ set_slot(1, left_enabled, 0, Color.WHITE, right_enabled, 0, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON)
+ else:
+ set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null)
+ set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON)
+
+
+func set_color(color: Color) -> void:
+ set_input_color(color)
+ set_output_color(color)
+
+
+func set_input_color(color: Color) -> void:
+ set_slot_color_left(1 if horizontal else 0, color)
+
+
+func set_output_color(color: Color) -> void:
+ set_slot_color_right(1 if horizontal else 2, color)
+
+
+func _on_size_changed():
+ add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0))
diff --git a/addons/beehave/debug/icons/horizontal_layout.svg b/addons/beehave/debug/icons/horizontal_layout.svg
new file mode 100644
index 0000000..235dc62
--- /dev/null
+++ b/addons/beehave/debug/icons/horizontal_layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/horizontal_layout.svg.import b/addons/beehave/debug/icons/horizontal_layout.svg.import
new file mode 100644
index 0000000..539e518
--- /dev/null
+++ b/addons/beehave/debug/icons/horizontal_layout.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bah77esichnyx"
+path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/horizontal_layout.svg"
+dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_bottom.svg b/addons/beehave/debug/icons/port_bottom.svg
new file mode 100644
index 0000000..3ce70e7
--- /dev/null
+++ b/addons/beehave/debug/icons/port_bottom.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_bottom.svg.import b/addons/beehave/debug/icons/port_bottom.svg.import
new file mode 100644
index 0000000..8845c5b
--- /dev/null
+++ b/addons/beehave/debug/icons/port_bottom.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://da3b236rjbqns"
+path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_bottom.svg"
+dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_left.svg b/addons/beehave/debug/icons/port_left.svg
new file mode 100644
index 0000000..c1f6717
--- /dev/null
+++ b/addons/beehave/debug/icons/port_left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_left.svg.import b/addons/beehave/debug/icons/port_left.svg.import
new file mode 100644
index 0000000..7ea9827
--- /dev/null
+++ b/addons/beehave/debug/icons/port_left.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bnufc8p6spdtn"
+path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_left.svg"
+dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_right.svg b/addons/beehave/debug/icons/port_right.svg
new file mode 100644
index 0000000..2560af5
--- /dev/null
+++ b/addons/beehave/debug/icons/port_right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_right.svg.import b/addons/beehave/debug/icons/port_right.svg.import
new file mode 100644
index 0000000..20931cd
--- /dev/null
+++ b/addons/beehave/debug/icons/port_right.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbmd6vk23ympm"
+path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_right.svg"
+dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/port_top.svg b/addons/beehave/debug/icons/port_top.svg
new file mode 100644
index 0000000..d3b99e1
--- /dev/null
+++ b/addons/beehave/debug/icons/port_top.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/port_top.svg.import b/addons/beehave/debug/icons/port_top.svg.import
new file mode 100644
index 0000000..dec7820
--- /dev/null
+++ b/addons/beehave/debug/icons/port_top.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bw8wmxdfom8eh"
+path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/port_top.svg"
+dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/icons/vertical_layout.svg b/addons/beehave/debug/icons/vertical_layout.svg
new file mode 100644
index 0000000..d59ffb0
--- /dev/null
+++ b/addons/beehave/debug/icons/vertical_layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/beehave/debug/icons/vertical_layout.svg.import b/addons/beehave/debug/icons/vertical_layout.svg.import
new file mode 100644
index 0000000..8ddcfca
--- /dev/null
+++ b/addons/beehave/debug/icons/vertical_layout.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bpyxu6i1dx5qh"
+path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/debug/icons/vertical_layout.svg"
+dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/debug/tree_node.gd b/addons/beehave/debug/tree_node.gd
new file mode 100644
index 0000000..6c104d8
--- /dev/null
+++ b/addons/beehave/debug/tree_node.gd
@@ -0,0 +1,255 @@
+class_name TreeNode
+extends RefCounted
+
+# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/
+
+const SIBLING_DISTANCE: float = 20.0
+const LEVEL_DISTANCE: float = 40.0
+
+var x: float
+var y: float
+var mod: float
+var parent: TreeNode
+var children: Array[TreeNode]
+
+var item: GraphNode
+
+
+func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void:
+ parent = p_parent
+ item = p_item
+
+
+func is_leaf() -> bool:
+ return children.is_empty()
+
+
+func is_most_left() -> bool:
+ if not parent:
+ return true
+ return parent.children.front() == self
+
+
+func is_most_right() -> bool:
+ if not parent:
+ return true
+ return parent.children.back() == self
+
+
+func get_previous_sibling() -> TreeNode:
+ if not parent or is_most_left():
+ return null
+ return parent.children[parent.children.find(self) - 1]
+
+
+func get_next_sibling() -> TreeNode:
+ if not parent or is_most_right():
+ return null
+ return parent.children[parent.children.find(self) + 1]
+
+
+func get_most_left_sibling() -> TreeNode:
+ if not parent:
+ return null
+
+ if is_most_left():
+ return self
+
+ return parent.children.front()
+
+
+func get_most_left_child() -> TreeNode:
+ if children.is_empty():
+ return null
+ return children.front()
+
+
+func get_most_right_child() -> TreeNode:
+ if children.is_empty():
+ return null
+ return children.back()
+
+
+func update_positions(horizontally: bool = false) -> void:
+ _initialize_nodes(self, 0)
+ _calculate_initial_x(self)
+
+ _check_all_children_on_screen(self)
+ _calculate_final_positions(self, 0)
+
+ if horizontally:
+ _swap_x_y(self)
+ _calculate_x(self, 0)
+ else:
+ _calculate_y(self, 0)
+
+
+func _initialize_nodes(node: TreeNode, depth: int) -> void:
+ node.x = -1
+ node.y = depth
+ node.mod = 0
+
+ for child in node.children:
+ _initialize_nodes(child, depth + 1)
+
+
+func _calculate_initial_x(node: TreeNode) -> void:
+ for child in node.children:
+ _calculate_initial_x(child)
+ if node.is_leaf():
+ if not node.is_most_left():
+ node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE
+ else:
+ node.x = 0
+ else:
+ var mid: float
+ if node.children.size() == 1:
+ var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2
+ mid = node.children.front().x + offset
+ else:
+ var left_child := node.get_most_left_child()
+ var right_child := node.get_most_right_child()
+ mid = (left_child.x + right_child.x + right_child.item.layout_size - node.item.layout_size) / 2
+
+ if node.is_most_left():
+ node.x = mid
+ else:
+ node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE
+ node.mod = node.x - mid
+
+ if not node.is_leaf() and not node.is_most_left():
+ _check_for_conflicts(node)
+
+
+func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void:
+ node.x += mod_sum
+ mod_sum += node.mod
+
+ for child in node.children:
+ _calculate_final_positions(child, mod_sum)
+
+
+func _check_all_children_on_screen(node: TreeNode) -> void:
+ var node_contour: Dictionary = {}
+ _get_left_contour(node, 0, node_contour)
+
+ var shift_amount: float = 0
+ for y in node_contour.keys():
+ if node_contour[y] + shift_amount < 0:
+ shift_amount = (node_contour[y] * -1)
+
+ if shift_amount > 0:
+ node.x += shift_amount
+ node.mod += shift_amount
+
+
+func _check_for_conflicts(node: TreeNode) -> void:
+ var min_distance := SIBLING_DISTANCE
+ var shift_value: float = 0
+ var shift_sibling: TreeNode = null
+
+ var node_contour: Dictionary = {}# { int, float }
+ _get_left_contour(node, 0, node_contour)
+
+ var sibling := node.get_most_left_sibling()
+ while sibling != null and sibling != node:
+ var sibling_contour: Dictionary = {}
+ _get_right_contour(sibling, 0, sibling_contour)
+
+ for level in range(node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1):
+ var distance: float = node_contour[level] - sibling_contour[level]
+ if distance + shift_value < min_distance:
+ shift_value = min_distance - distance
+ shift_sibling = sibling
+
+ sibling = sibling.get_next_sibling()
+
+ if shift_value > 0:
+ node.x += shift_value
+ node.mod += shift_value
+ _center_nodes_between(shift_sibling, node)
+
+
+func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void:
+ var left_index := left_node.parent.children.find(left_node)
+ var right_index := left_node.parent.children.find(right_node)
+
+ var num_nodes_between: int = (right_index - left_index) - 1
+ if num_nodes_between > 0:
+ # The extra distance that needs to be split into num_nodes_between + 1
+ # in order to find the new node spacing so that nodes are equally spaced
+ var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size
+ # Subtract sizes on nodes in between
+ for i in range(left_index + 1, right_index):
+ distance_to_allocate -= left_node.parent.children[i].item.layout_size
+ # Divide space equally
+ var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1)
+
+ var prev_node := left_node
+ var middle_node := left_node.get_next_sibling()
+ while middle_node != right_node:
+ var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes
+ var offset := desire_x - middle_node.x
+ middle_node.x += offset
+ middle_node.mod += offset
+ prev_node = middle_node
+ middle_node = middle_node.get_next_sibling()
+
+
+func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
+ var node_left: float = node.x + mod_sum
+ var depth := int(node.y)
+ if not values.has(depth):
+ values[depth] = node_left
+ else:
+ values[depth] = min(values[depth], node_left)
+
+ mod_sum += node.mod
+ for child in node.children:
+ _get_left_contour(child, mod_sum, values)
+
+
+func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
+ var node_right: float = node.x + mod_sum + node.item.layout_size
+ var depth := int(node.y)
+ if not values.has(depth):
+ values[depth] = node_right
+ else:
+ values[depth] = max(values[depth], node_right)
+
+ mod_sum += node.mod
+ for child in node.children:
+ _get_right_contour(child, mod_sum, values)
+
+
+func _swap_x_y(node: TreeNode) -> void:
+ for child in node.children:
+ _swap_x_y(child)
+
+ var temp := node.x
+ node.x = node.y
+ node.y = temp
+
+
+func _calculate_x(node: TreeNode, offset: int) -> void:
+ node.x = offset
+ var sibling := node.get_most_left_sibling()
+ var max_size: int = node.item.size.x
+ while sibling != null:
+ max_size = max(sibling.item.size.x, max_size)
+ sibling = sibling.get_next_sibling()
+
+ for child in node.children:
+ _calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
+
+
+func _calculate_y(node: TreeNode, offset: int) -> void:
+ node.y = offset
+ var sibling := node.get_most_left_sibling()
+ var max_size: int = node.item.size.y
+ while sibling != null:
+ max_size = max(sibling.item.size.y, max_size)
+ sibling = sibling.get_next_sibling()
+
+ for child in node.children:
+ _calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
diff --git a/addons/beehave/icons/action.svg b/addons/beehave/icons/action.svg
new file mode 100644
index 0000000..3916c89
--- /dev/null
+++ b/addons/beehave/icons/action.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/action.svg.import b/addons/beehave/icons/action.svg.import
new file mode 100644
index 0000000..cf8a612
--- /dev/null
+++ b/addons/beehave/icons/action.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btrq8e0kyxthg"
+path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/action.svg"
+dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/blackboard.svg b/addons/beehave/icons/blackboard.svg
new file mode 100644
index 0000000..e4948a5
--- /dev/null
+++ b/addons/beehave/icons/blackboard.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/blackboard.svg.import b/addons/beehave/icons/blackboard.svg.import
new file mode 100644
index 0000000..4650058
--- /dev/null
+++ b/addons/beehave/icons/blackboard.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dw7rom0hiff6c"
+path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/blackboard.svg"
+dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_bt.svg b/addons/beehave/icons/category_bt.svg
new file mode 100644
index 0000000..8be61ae
--- /dev/null
+++ b/addons/beehave/icons/category_bt.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_bt.svg.import b/addons/beehave/icons/category_bt.svg.import
new file mode 100644
index 0000000..c11e4f2
--- /dev/null
+++ b/addons/beehave/icons/category_bt.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qpdd6ue7x82h"
+path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_bt.svg"
+dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_composite.svg b/addons/beehave/icons/category_composite.svg
new file mode 100644
index 0000000..aa8b866
--- /dev/null
+++ b/addons/beehave/icons/category_composite.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_composite.svg.import b/addons/beehave/icons/category_composite.svg.import
new file mode 100644
index 0000000..0496273
--- /dev/null
+++ b/addons/beehave/icons/category_composite.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://863s568sneja"
+path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_composite.svg"
+dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_decorator.svg b/addons/beehave/icons/category_decorator.svg
new file mode 100644
index 0000000..165e3d6
--- /dev/null
+++ b/addons/beehave/icons/category_decorator.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_decorator.svg.import b/addons/beehave/icons/category_decorator.svg.import
new file mode 100644
index 0000000..492f32e
--- /dev/null
+++ b/addons/beehave/icons/category_decorator.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c2ie8m4ddawlb"
+path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_decorator.svg"
+dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/category_leaf.svg b/addons/beehave/icons/category_leaf.svg
new file mode 100644
index 0000000..1482fe6
--- /dev/null
+++ b/addons/beehave/icons/category_leaf.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/category_leaf.svg.import b/addons/beehave/icons/category_leaf.svg.import
new file mode 100644
index 0000000..4ef9604
--- /dev/null
+++ b/addons/beehave/icons/category_leaf.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://eq0sp4g3s75r"
+path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/category_leaf.svg"
+dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/condition.svg b/addons/beehave/icons/condition.svg
new file mode 100644
index 0000000..37b2c7a
--- /dev/null
+++ b/addons/beehave/icons/condition.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/condition.svg.import b/addons/beehave/icons/condition.svg.import
new file mode 100644
index 0000000..ef59099
--- /dev/null
+++ b/addons/beehave/icons/condition.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ck4toqx0nggiu"
+path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/condition.svg"
+dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/failer.svg b/addons/beehave/icons/failer.svg
new file mode 100644
index 0000000..968f7e1
--- /dev/null
+++ b/addons/beehave/icons/failer.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/failer.svg.import b/addons/beehave/icons/failer.svg.import
new file mode 100644
index 0000000..989b556
--- /dev/null
+++ b/addons/beehave/icons/failer.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2fj7htaqvcud"
+path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/failer.svg"
+dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/inverter.svg b/addons/beehave/icons/inverter.svg
new file mode 100644
index 0000000..d4e791e
--- /dev/null
+++ b/addons/beehave/icons/inverter.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/inverter.svg.import b/addons/beehave/icons/inverter.svg.import
new file mode 100644
index 0000000..e9050a8
--- /dev/null
+++ b/addons/beehave/icons/inverter.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cffmoc3og8hux"
+path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/inverter.svg"
+dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/limiter.svg b/addons/beehave/icons/limiter.svg
new file mode 100644
index 0000000..7b3fa1d
--- /dev/null
+++ b/addons/beehave/icons/limiter.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/limiter.svg.import b/addons/beehave/icons/limiter.svg.import
new file mode 100644
index 0000000..7b56b08
--- /dev/null
+++ b/addons/beehave/icons/limiter.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c7akxvsg0f2by"
+path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/limiter.svg"
+dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/selector.svg b/addons/beehave/icons/selector.svg
new file mode 100644
index 0000000..0ae3b7a
--- /dev/null
+++ b/addons/beehave/icons/selector.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/selector.svg.import b/addons/beehave/icons/selector.svg.import
new file mode 100644
index 0000000..ef7326d
--- /dev/null
+++ b/addons/beehave/icons/selector.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b2c5d20doh4sp"
+path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector.svg"
+dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/selector_random.svg b/addons/beehave/icons/selector_random.svg
new file mode 100644
index 0000000..6f631e9
--- /dev/null
+++ b/addons/beehave/icons/selector_random.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/addons/beehave/icons/selector_random.svg.import b/addons/beehave/icons/selector_random.svg.import
new file mode 100644
index 0000000..6306f76
--- /dev/null
+++ b/addons/beehave/icons/selector_random.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmnkcmk7bkdjd"
+path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector_random.svg"
+dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/selector_reactive.svg b/addons/beehave/icons/selector_reactive.svg
new file mode 100644
index 0000000..6db005f
--- /dev/null
+++ b/addons/beehave/icons/selector_reactive.svg
@@ -0,0 +1,45 @@
+
+
diff --git a/addons/beehave/icons/selector_reactive.svg.import b/addons/beehave/icons/selector_reactive.svg.import
new file mode 100644
index 0000000..12a8c5b
--- /dev/null
+++ b/addons/beehave/icons/selector_reactive.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crkbov0h8sb8l"
+path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/selector_reactive.svg"
+dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence.svg b/addons/beehave/icons/sequence.svg
new file mode 100644
index 0000000..3ebedd9
--- /dev/null
+++ b/addons/beehave/icons/sequence.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/sequence.svg.import b/addons/beehave/icons/sequence.svg.import
new file mode 100644
index 0000000..5dadbe2
--- /dev/null
+++ b/addons/beehave/icons/sequence.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5gw354thiofm"
+path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence.svg"
+dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence_random.svg b/addons/beehave/icons/sequence_random.svg
new file mode 100644
index 0000000..34e4a12
--- /dev/null
+++ b/addons/beehave/icons/sequence_random.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/sequence_random.svg.import b/addons/beehave/icons/sequence_random.svg.import
new file mode 100644
index 0000000..3907462
--- /dev/null
+++ b/addons/beehave/icons/sequence_random.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bat8ptdw5qt1d"
+path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence_random.svg"
+dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/sequence_reactive.svg b/addons/beehave/icons/sequence_reactive.svg
new file mode 100644
index 0000000..33d219b
--- /dev/null
+++ b/addons/beehave/icons/sequence_reactive.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/addons/beehave/icons/sequence_reactive.svg.import b/addons/beehave/icons/sequence_reactive.svg.import
new file mode 100644
index 0000000..ab0fa25
--- /dev/null
+++ b/addons/beehave/icons/sequence_reactive.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rmiu1slwfkh7"
+path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/sequence_reactive.svg"
+dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/succeeder.svg b/addons/beehave/icons/succeeder.svg
new file mode 100644
index 0000000..10f5912
--- /dev/null
+++ b/addons/beehave/icons/succeeder.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/succeeder.svg.import b/addons/beehave/icons/succeeder.svg.import
new file mode 100644
index 0000000..0cb7334
--- /dev/null
+++ b/addons/beehave/icons/succeeder.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dl6wo332kglbe"
+path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/succeeder.svg"
+dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/icons/tree.svg b/addons/beehave/icons/tree.svg
new file mode 100644
index 0000000..6c85ea1
--- /dev/null
+++ b/addons/beehave/icons/tree.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/addons/beehave/icons/tree.svg.import b/addons/beehave/icons/tree.svg.import
new file mode 100644
index 0000000..9ac0308
--- /dev/null
+++ b/addons/beehave/icons/tree.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deryyg2hbmaaw"
+path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/beehave/icons/tree.svg"
+dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/beehave/metrics/beehave_global_metrics.gd b/addons/beehave/metrics/beehave_global_metrics.gd
new file mode 100644
index 0000000..9a2ba4a
--- /dev/null
+++ b/addons/beehave/metrics/beehave_global_metrics.gd
@@ -0,0 +1,54 @@
+extends Node
+
+var _tree_count: int = 0
+var _active_tree_count: int = 0
+var _registered_trees: Array[BeehaveTree] = []
+
+
+func _enter_tree() -> void:
+ Performance.add_custom_monitor("beehave/total_trees", _get_total_trees)
+ Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees)
+
+
+func register_tree(tree: BeehaveTree) -> void:
+ if _registered_trees.has(tree):
+ return
+
+ _registered_trees.append(tree)
+ _tree_count += 1
+
+ if tree.enabled:
+ _active_tree_count += 1
+
+ tree.tree_enabled.connect(_on_tree_enabled)
+ tree.tree_disabled.connect(_on_tree_disabled)
+
+
+func unregister_tree(tree: BeehaveTree) -> void:
+ if not _registered_trees.has(tree):
+ return
+
+ _registered_trees.erase(tree)
+ _tree_count -= 1
+
+ if tree.enabled:
+ _active_tree_count -= 1
+
+ tree.tree_enabled.disconnect(_on_tree_enabled)
+ tree.tree_disabled.disconnect(_on_tree_disabled)
+
+
+func _get_total_trees() -> int:
+ return _tree_count
+
+
+func _get_total_enabled_trees() -> int:
+ return _active_tree_count
+
+
+func _on_tree_enabled() -> void:
+ _active_tree_count += 1
+
+
+func _on_tree_disabled() -> void:
+ _active_tree_count -= 1
diff --git a/addons/beehave/nodes/beehave_node.gd b/addons/beehave/nodes/beehave_node.gd
new file mode 100644
index 0000000..0c94b8c
--- /dev/null
+++ b/addons/beehave/nodes/beehave_node.gd
@@ -0,0 +1,49 @@
+## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or
+## `RUNNING` when ticked.
+@tool
+class_name BeehaveNode extends Node
+
+enum {
+ SUCCESS,
+ FAILURE,
+ RUNNING
+}
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+
+ if get_children().any(func(x): return not (x is BeehaveNode)):
+ warnings.append("All children of this node should inherit from BeehaveNode class.")
+
+ return warnings
+
+
+## Executes this node and returns a status code.
+## This method must be overwritten.
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ return SUCCESS
+
+
+## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS.
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+## Called before the first time it ticks by the parent.
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+## Called after the last time it ticks and returns
+## [code]SUCCESS[/code] or [code]FAILURE[/code].
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ pass
+
+
+func get_class_name() -> Array[StringName]:
+ return [&"BeehaveNode"]
+
+
+func can_send_message(blackboard: Blackboard) -> bool:
+ return blackboard.get_value("can_send_message", false)
diff --git a/addons/beehave/nodes/beehave_tree.gd b/addons/beehave/nodes/beehave_tree.gd
new file mode 100644
index 0000000..7a8d145
--- /dev/null
+++ b/addons/beehave/nodes/beehave_tree.gd
@@ -0,0 +1,224 @@
+## Controls the flow of execution of the entire behavior tree.
+@tool
+@icon("../icons/tree.svg")
+class_name BeehaveTree extends Node
+
+enum {
+ SUCCESS,
+ FAILURE,
+ RUNNING
+}
+
+signal tree_enabled
+signal tree_disabled
+
+## Wether this behavior tree should be enabled or not.
+@export var enabled: bool = true:
+ set(value):
+ enabled = value
+ set_physics_process(enabled)
+
+ if value:
+ tree_enabled.emit()
+ else:
+ interrupt()
+ tree_disabled.emit()
+
+ get:
+ return enabled
+
+## An optional node path this behavior tree should apply to.
+@export_node_path var actor_node_path : NodePath
+
+## Custom blackboard node. An internal blackboard will be used
+## if no blackboard is provided explicitly.
+@export var blackboard:Blackboard:
+ set(b):
+ blackboard = b
+ if blackboard and _internal_blackboard:
+ remove_child(_internal_blackboard)
+ _internal_blackboard.free()
+ _internal_blackboard = null
+ elif not blackboard and not _internal_blackboard:
+ _internal_blackboard = Blackboard.new()
+ add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
+ get:
+ return blackboard if blackboard else _internal_blackboard
+
+## When enabled, this tree is tracked individually
+## as a custom monitor.
+@export var custom_monitor = false:
+ set(b):
+ custom_monitor = b
+ if custom_monitor and _process_time_metric_name != '':
+ Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
+ BeehaveGlobalMetrics.register_tree(self)
+ else:
+ if _process_time_metric_name != '':
+ # Remove tree metric from the engine
+ Performance.remove_custom_monitor(_process_time_metric_name)
+ BeehaveGlobalMetrics.unregister_tree(self)
+
+ BeehaveDebuggerMessages.unregister_tree(get_instance_id())
+
+var actor : Node
+var status : int = -1
+
+var _internal_blackboard: Blackboard
+var _process_time_metric_name : String
+var _process_time_metric_value : float = 0.0
+var _can_send_message: bool = false
+
+func _ready() -> void:
+ if Engine.is_editor_hint():
+ return
+
+ if self.get_child_count() > 0 and not self.get_child(0) is BeehaveNode:
+ push_warning("Beehave error: Root %s should have only one child of type BeehaveNode (NodePath: %s)" % [self.name, self.get_path()])
+ disable()
+ return
+
+ if not blackboard:
+ _internal_blackboard = Blackboard.new()
+ add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
+
+ actor = get_parent()
+ if actor_node_path:
+ actor = get_node(actor_node_path)
+
+ # Get the name of the parent node name for metric
+ var parent_name = actor.name
+ _process_time_metric_name = "beehave [microseconds]/process_time_%s-%s" % [parent_name, get_instance_id()]
+
+ # Register custom metric to the engine
+ if custom_monitor:
+ Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
+ BeehaveGlobalMetrics.register_tree(self)
+
+ set_physics_process(enabled)
+ BeehaveGlobalDebugger.register_tree(self)
+ BeehaveDebuggerMessages.register_tree(_get_debugger_data(self))
+
+
+func _physics_process(delta: float) -> void:
+ if Engine.is_editor_hint():
+ return
+
+ # Start timing for metric
+ var start_time = Time.get_ticks_usec()
+
+ blackboard.set_value("can_send_message", _can_send_message)
+
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_begin(get_instance_id())
+
+ if self.get_child_count() == 1:
+ tick()
+
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_end(get_instance_id())
+
+ # Check the cost for this frame and save it for metric report
+ _process_time_metric_value = Time.get_ticks_usec() - start_time
+
+
+func tick() -> int:
+ var child := self.get_child(0)
+ if status != RUNNING:
+ child.before_run(actor, blackboard)
+
+ status = child.tick(actor, blackboard)
+ if _can_send_message:
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status)
+ BeehaveDebuggerMessages.process_tick(get_instance_id(), status)
+
+ # Clear running action if nothing is running
+ if status != RUNNING:
+ blackboard.set_value("running_action", null, str(actor.get_instance_id()))
+ child.after_run(actor, blackboard)
+
+ return status
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings:PackedStringArray = []
+
+ if get_children().any(func(x): return not (x is BeehaveNode)):
+ warnings.append("All children of this node should inherit from BeehaveNode class.")
+
+ if get_child_count() != 1:
+ warnings.append("BeehaveTree should have exactly one child node.")
+
+ return warnings
+
+
+## Returns the currently running action
+func get_running_action() -> ActionLeaf:
+ return blackboard.get_value("running_action", null, str(actor.get_instance_id()))
+
+
+## Returns the last condition that was executed
+func get_last_condition() -> ConditionLeaf:
+ return blackboard.get_value("last_condition", null, str(actor.get_instance_id()))
+
+
+## Returns the status of the last executed condition
+func get_last_condition_status() -> String:
+ if blackboard.has_value("last_condition_status", str(actor.get_instance_id())):
+ var status = blackboard.get_value("last_condition_status", null, str(actor.get_instance_id()))
+ if status == SUCCESS:
+ return "SUCCESS"
+ elif status == FAILURE:
+ return "FAILURE"
+ else:
+ return "RUNNING"
+ return ""
+
+## interrupts this tree if anything was running
+func interrupt() -> void:
+ if self.get_child_count() != 0:
+ var first_child = self.get_child(0)
+ if "interrupt" in first_child:
+ first_child.interrupt(actor, blackboard)
+
+
+## Enables this tree.
+func enable() -> void:
+ self.enabled = true
+
+
+## Disables this tree.
+func disable() -> void:
+ self.enabled = false
+
+
+func _exit_tree() -> void:
+ if custom_monitor:
+ if _process_time_metric_name != '':
+ # Remove tree metric from the engine
+ Performance.remove_custom_monitor(_process_time_metric_name)
+ BeehaveGlobalMetrics.unregister_tree(self)
+
+ BeehaveDebuggerMessages.unregister_tree(get_instance_id())
+
+
+# Called by the engine to profile this tree
+func _get_process_time_metric_value() -> int:
+ return _process_time_metric_value
+
+
+func _get_debugger_data(node: Node) -> Dictionary:
+ if not node is BeehaveTree and not node is BeehaveNode:
+ return {}
+ var data := { path = node.get_path(), name = node.name, type = node.get_class_name(), id = str(node.get_instance_id()) }
+ if node.get_child_count() > 0:
+ data.children = []
+ for child in node.get_children():
+ var child_data := _get_debugger_data(child)
+ if not child_data.is_empty():
+ data.children.push_back(child_data)
+ return data
+
+
+func get_class_name() -> Array[StringName]:
+ return [&"BeehaveTree"]
diff --git a/addons/beehave/nodes/composites/composite.gd b/addons/beehave/nodes/composites/composite.gd
new file mode 100644
index 0000000..b959eea
--- /dev/null
+++ b/addons/beehave/nodes/composites/composite.gd
@@ -0,0 +1,40 @@
+## A Composite node controls the flow of execution of its children in a specific manner.
+@tool
+@icon("../../icons/category_composite.svg")
+class_name Composite extends BeehaveNode
+
+
+var running_child: BeehaveNode = null
+
+
+func _ready():
+ if Engine.is_editor_hint():
+ return
+
+ if self.get_child_count() < 1:
+ push_warning("BehaviorTree Error: Composite %s should have at least one child (NodePath: %s)" % [self.name, self.get_path()])
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = super._get_configuration_warnings()
+
+ if get_children().filter(func(x): return x is BeehaveNode).size() < 2:
+ warnings.append("Any composite node should have at least two children. Otherwise it is not useful.")
+
+ return warnings
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = null
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ running_child = null
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Composite")
+ return classes
diff --git a/addons/beehave/nodes/composites/randomized_composite.gd b/addons/beehave/nodes/composites/randomized_composite.gd
new file mode 100644
index 0000000..0eb62c6
--- /dev/null
+++ b/addons/beehave/nodes/composites/randomized_composite.gd
@@ -0,0 +1,152 @@
+@tool
+class_name RandomizedComposite extends Composite
+
+const WEIGHTS_PREFIX = "Weights/"
+
+## Sets a predicable seed
+@export var random_seed: int = 0:
+ set(rs):
+ random_seed = rs
+ if random_seed != 0:
+ seed(random_seed)
+ else:
+ randomize()
+
+## Wether to use weights for every child or not.
+@export var use_weights: bool:
+ set(value):
+ use_weights = value
+ if use_weights:
+ _update_weights(get_children())
+ _connect_children_changing_signals()
+ notify_property_list_changed()
+
+var _weights: Dictionary
+
+
+func _ready():
+ _connect_children_changing_signals()
+
+
+func _connect_children_changing_signals():
+ if not child_entered_tree.is_connected(_on_child_entered_tree):
+ child_entered_tree.connect(_on_child_entered_tree)
+
+ if not child_exiting_tree.is_connected(_on_child_exiting_tree):
+ child_exiting_tree.connect(_on_child_exiting_tree)
+
+
+func get_shuffled_children() -> Array[Node]:
+ var children_bag: Array[Node] = get_children().duplicate()
+ if use_weights:
+ var weights: Array[int]
+ weights.assign(children_bag.map(func (child): return _weights[child.name]))
+ children_bag.assign(_weighted_shuffle(children_bag, weights))
+ else:
+ children_bag.shuffle()
+ return children_bag
+
+
+## Returns a shuffled version of a given array using the supplied array of weights.
+## Think of weights as the chance of a given item being the first in the array.
+func _weighted_shuffle(items: Array, weights: Array[int]) -> Array:
+ if len(items) != len(weights):
+ push_error("items and weights size mismatch: expected %d weights, got %d instead." % [len(items), len(weights)])
+ return items
+
+ # This method is based on the weighted random sampling algorithm
+ # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)).
+
+ # For each index, it will calculate random_value^(1/weight).
+ var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])]
+ var random_distribuition = range(len(items)).map(chance_calc)
+
+ # Now we just have to order by the calculated value, descending.
+ random_distribuition.sort_custom(func(a, b): return a[1] > b[1])
+
+ return random_distribuition.map(func(dist): return items[dist[0]])
+
+
+func _get_property_list():
+ var properties = []
+
+ if use_weights:
+ for key in _weights.keys():
+ properties.append({
+ "name": WEIGHTS_PREFIX + key,
+ "type": TYPE_INT,
+ "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
+ "hint": PROPERTY_HINT_RANGE,
+ "hint_string": "1,100"
+ })
+
+ return properties
+
+
+func _set(property: StringName, value: Variant) -> bool:
+ if property.begins_with(WEIGHTS_PREFIX):
+ var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
+ _weights[weight_name] = value
+ return true
+
+ return false
+
+
+func _get(property: StringName):
+ if property.begins_with(WEIGHTS_PREFIX):
+ var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
+ return _weights[weight_name]
+
+ return null
+
+
+func _update_weights(children: Array[Node]) -> void:
+ var new_weights = {}
+ for c in children:
+ if _weights.has(c.name):
+ new_weights[c.name] = _weights[c.name]
+ else:
+ new_weights[c.name] = 1
+ _weights = new_weights
+ notify_property_list_changed()
+
+
+func _on_child_entered_tree(node: Node):
+ _update_weights(get_children())
+
+ var renamed_callable = _on_child_renamed.bind(node.name, node)
+ if not node.renamed.is_connected(renamed_callable):
+ node.renamed.connect(renamed_callable)
+
+
+func _on_child_exiting_tree(node: Node):
+ var renamed_callable = _on_child_renamed.bind(node.name, node)
+ if node.renamed.is_connected(renamed_callable):
+ node.renamed.disconnect(renamed_callable)
+
+ var children = get_children()
+ children.erase(node)
+ _update_weights(children)
+
+
+func _on_child_renamed(old_name: String, renamed_child: Node):
+ if old_name == renamed_child.name:
+ return # No need to update the weights.
+
+ # Disconnect signal with old name...
+ renamed_child.renamed\
+ .disconnect(_on_child_renamed.bind(old_name, renamed_child))
+ # ...and connect with the new name.
+ renamed_child.renamed\
+ .connect(_on_child_renamed.bind(renamed_child.name, renamed_child))
+
+ var original_weight = _weights[old_name]
+ _weights.erase(old_name)
+ _weights[renamed_child.name] = original_weight
+ notify_property_list_changed()
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"RandomizedComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector.gd b/addons/beehave/nodes/composites/selector.gd
new file mode 100644
index 0000000..eca71ac
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector.gd
@@ -0,0 +1,69 @@
+## Selector nodes will attempt to execute each of its children until one of
+## them return `SUCCESS`. If all children return `FAILURE`, this node will also
+## return `FAILURE`.
+## If a child returns `RUNNING` it will tick again.
+@tool
+@icon("../../icons/selector.svg")
+class_name SelectorComposite extends Composite
+
+
+var last_execution_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < last_execution_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _cleanup_running_task(c, actor, blackboard)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ _cleanup_running_task(c, actor, blackboard)
+ last_execution_index += 1
+ c.after_run(actor, blackboard)
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ last_execution_index = 0
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ last_execution_index = 0
+ super(actor, blackboard)
+
+
+## Changes `running_action` and `running_child` after the node finishes executing.
+func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
+ var blackboard_name = str(actor.get_instance_id())
+ if finished_action == running_child:
+ running_child = null
+ if finished_action == blackboard.get_value("running_action", null, blackboard_name):
+ blackboard.set_value("running_action", null, blackboard_name)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector_random.gd b/addons/beehave/nodes/composites/selector_random.gd
new file mode 100644
index 0000000..b780b5f
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector_random.gd
@@ -0,0 +1,80 @@
+## This node will attempt to execute all of its children just like a
+## [code]SelectorStar[/code] would, with the exception that the children
+## will be executed in a random order.
+@tool
+@icon("../../icons/selector_random.svg")
+class_name SelectorRandomComposite extends RandomizedComposite
+
+## A shuffled list of the children that will be executed in reverse order.
+var _children_bag: Array[Node] = []
+var c: Node
+
+func _ready() -> void:
+ super()
+ if random_seed == 0:
+ randomize()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if _children_bag.is_empty():
+ _reset()
+
+ # We need to traverse the array in reverse since we will be manipulating it.
+ for i in _get_reversed_indexes():
+ c = _children_bag[i]
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _get_reversed_indexes() -> Array[int]:
+ var reversed: Array[int]
+ reversed.assign(range(_children_bag.size()))
+ reversed.reverse()
+ return reversed
+
+
+func _reset() -> void:
+ var new_order = get_shuffled_children()
+ _children_bag = new_order.duplicate()
+ _children_bag.reverse() # It needs to run the children in reverse order.
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorRandomComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/selector_reactive.gd b/addons/beehave/nodes/composites/selector_reactive.gd
new file mode 100644
index 0000000..869ef8a
--- /dev/null
+++ b/addons/beehave/nodes/composites/selector_reactive.gd
@@ -0,0 +1,45 @@
+## Selector Reactive nodes will attempt to execute each of its children until one of
+## them return `SUCCESS`. If all children return `FAILURE`, this node will also
+## return `FAILURE`.
+## If a child returns `RUNNING` it will restart.
+@tool
+@icon("../../icons/selector_reactive.svg")
+class_name SelectorReactiveComposite extends Composite
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ # Interrupt any child that was RUNNING before.
+ if c != running_child:
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ FAILURE:
+ c.after_run(actor, blackboard)
+ RUNNING:
+ if c != running_child:
+ interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return FAILURE
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SelectorReactiveComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence.gd b/addons/beehave/nodes/composites/sequence.gd
new file mode 100644
index 0000000..11b5d79
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence.gd
@@ -0,0 +1,76 @@
+## Sequence nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and restart.
+## In case a child returns `RUNNING` this node will tick again.
+@tool
+@icon("../../icons/sequence.svg")
+class_name SequenceComposite extends Composite
+
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _cleanup_running_task(c, actor, blackboard)
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ _cleanup_running_task(c, actor, blackboard)
+ # Interrupt any child that was RUNNING before.
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ if c != running_child:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ successful_index = 0
+
+
+## Changes `running_action` and `running_child` after the node finishes executing.
+func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
+ var blackboard_name = str(actor.get_instance_id())
+ if finished_action == running_child:
+ running_child = null
+ if finished_action == blackboard.get_value("running_action", null, blackboard_name):
+ blackboard.set_value("running_action", null, blackboard_name)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_random.gd b/addons/beehave/nodes/composites/sequence_random.gd
new file mode 100644
index 0000000..ab8eb78
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_random.gd
@@ -0,0 +1,92 @@
+## This node will attempt to execute all of its children just like a
+## [code]SequenceStar[/code] would, with the exception that the children
+## will be executed in a random order.
+@tool
+@icon("../../icons/sequence_random.svg")
+class_name SequenceRandomComposite extends RandomizedComposite
+
+# Emitted whenever the children are shuffled.
+signal reset(new_order: Array[Node])
+
+## Whether the sequence should start where it left off after a previous failure.
+@export var resume_on_failure: bool = false
+## Whether the sequence should start where it left off after a previous interruption.
+@export var resume_on_interrupt: bool = false
+
+## A shuffled list of the children that will be executed in reverse order.
+var _children_bag: Array[Node] = []
+var c: Node
+
+
+func _ready() -> void:
+ super()
+ if random_seed == 0:
+ randomize()
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if _children_bag.is_empty():
+ _reset()
+
+ # We need to traverse the array in reverse since we will be manipulating it.
+ for i in _get_reversed_indexes():
+ c = _children_bag[i]
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ FAILURE:
+ _children_bag.erase(c)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+
+ return SUCCESS
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ if not resume_on_failure:
+ _reset()
+ super(actor, blackboard)
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if not resume_on_interrupt:
+ _reset()
+ super(actor, blackboard)
+
+
+func _get_reversed_indexes() -> Array[int]:
+ var reversed: Array[int]
+ reversed.assign(range(_children_bag.size()))
+ reversed.reverse()
+ return reversed
+
+
+func _reset() -> void:
+ var new_order = get_shuffled_children()
+ _children_bag = new_order.duplicate()
+ _children_bag.reverse() # It needs to run the children in reverse order.
+ reset.emit(new_order)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceRandomComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_reactive.gd b/addons/beehave/nodes/composites/sequence_reactive.gd
new file mode 100644
index 0000000..50e8b90
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_reactive.gd
@@ -0,0 +1,62 @@
+## Reactive Sequence nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and restart.
+## In case a child returns `RUNNING` this node will restart.
+@tool
+@icon("../../icons/sequence_reactive.svg")
+class_name SequenceReactiveComposite extends Composite
+
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ # Interrupt any child that was RUNNING before.
+ interrupt(actor, blackboard)
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ _reset()
+ if running_child != c:
+ interrupt(actor, blackboard)
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+func _reset() -> void:
+ successful_index = 0
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceReactiveComposite")
+ return classes
diff --git a/addons/beehave/nodes/composites/sequence_star.gd b/addons/beehave/nodes/composites/sequence_star.gd
new file mode 100644
index 0000000..42ceb07
--- /dev/null
+++ b/addons/beehave/nodes/composites/sequence_star.gd
@@ -0,0 +1,58 @@
+## Sequence Star nodes will attempt to execute all of its children and report
+## `SUCCESS` in case all of the children report a `SUCCESS` status code.
+## If at least one child reports a `FAILURE` status code, this node will also
+## return `FAILURE` and tick again.
+## In case a child returns `RUNNING` this node will restart.
+@tool
+@icon("../../icons/sequence_reactive.svg")
+class_name SequenceStarComposite extends Composite
+
+
+var successful_index: int = 0
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ for c in get_children():
+ if c.get_index() < successful_index:
+ continue
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ successful_index += 1
+ c.after_run(actor, blackboard)
+ FAILURE:
+ c.after_run(actor, blackboard)
+ return FAILURE
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _reset()
+ return SUCCESS
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ _reset()
+ super(actor, blackboard)
+
+
+func _reset() -> void:
+ successful_index = 0
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"SequenceStarComposite")
+ return classes
diff --git a/addons/beehave/nodes/decorators/decorator.gd b/addons/beehave/nodes/decorators/decorator.gd
new file mode 100644
index 0000000..8cc3944
--- /dev/null
+++ b/addons/beehave/nodes/decorators/decorator.gd
@@ -0,0 +1,41 @@
+## Decorator nodes are used to transform the result received by its child.
+## Must only have one child.
+@tool
+@icon("../../icons/category_decorator.svg")
+class_name Decorator extends BeehaveNode
+
+
+var running_child: BeehaveNode = null
+
+
+func _ready():
+ if Engine.is_editor_hint():
+ return
+
+ if self.get_child_count() != 1:
+ push_warning("Beehave Error: Decorator %s should have only one child (NodePath: %s)" % [self.name, self.get_path()])
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = super._get_configuration_warnings()
+
+ if get_child_count() != 1:
+ warnings.append("Decorator should have exactly one child node.")
+
+ return warnings
+
+
+func interrupt(actor: Node, blackboard: Blackboard) -> void:
+ if running_child != null:
+ running_child.interrupt(actor, blackboard)
+ running_child = null
+
+
+func after_run(actor: Node, blackboard: Blackboard) -> void:
+ running_child = null
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Decorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/failer.gd b/addons/beehave/nodes/decorators/failer.gd
new file mode 100644
index 0000000..4a818ed
--- /dev/null
+++ b/addons/beehave/nodes/decorators/failer.gd
@@ -0,0 +1,34 @@
+## A Failer node will always return a `FAILURE` status code.
+@tool
+@icon("../../icons/failer.svg")
+class_name AlwaysFailDecorator extends Decorator
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ else:
+ c.after_run(actor, blackboard)
+ return FAILURE
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"AlwaysFailDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/inverter.gd b/addons/beehave/nodes/decorators/inverter.gd
new file mode 100644
index 0000000..16e3f36
--- /dev/null
+++ b/addons/beehave/nodes/decorators/inverter.gd
@@ -0,0 +1,42 @@
+## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
+## code or `SUCCESS` in case its child returns a `FAILURE` status code.
+@tool
+@icon("../../icons/inverter.svg")
+class_name InverterDecorator extends Decorator
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ match response:
+ SUCCESS:
+ c.after_run(actor, blackboard)
+ return FAILURE
+ FAILURE:
+ c.after_run(actor, blackboard)
+ return SUCCESS
+ RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ _:
+ push_error("This should be unreachable")
+ return -1
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"InverterDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/limiter.gd b/addons/beehave/nodes/decorators/limiter.gd
new file mode 100644
index 0000000..72e7356
--- /dev/null
+++ b/addons/beehave/nodes/decorators/limiter.gd
@@ -0,0 +1,41 @@
+## The limiter will execute its child `x` amount of times. When the number of
+## maximum ticks is reached, it will return a `FAILURE` status code.
+@tool
+@icon("../../icons/limiter.svg")
+class_name LimiterDecorator extends Decorator
+
+@onready var cache_key = 'limiter_%s' % self.get_instance_id()
+
+@export var max_count : float = 0
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var child = self.get_child(0)
+ var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id()))
+
+ if current_count == 0:
+ child.before_run(actor, blackboard)
+
+ if current_count < max_count:
+ blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id()))
+ var response = child.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
+
+ if child is ConditionLeaf:
+ blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if child is ActionLeaf and response == RUNNING:
+ running_child = child
+ blackboard.set_value("running_action", child, str(actor.get_instance_id()))
+
+ return response
+ else:
+ child.after_run(actor, blackboard)
+ return FAILURE
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"LimiterDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/succeeder.gd b/addons/beehave/nodes/decorators/succeeder.gd
new file mode 100644
index 0000000..9d9664b
--- /dev/null
+++ b/addons/beehave/nodes/decorators/succeeder.gd
@@ -0,0 +1,34 @@
+## A succeeder node will always return a `SUCCESS` status code.
+@tool
+@icon("../../icons/succeeder.svg")
+class_name AlwaysSucceedDecorator extends Decorator
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var c = get_child(0)
+
+ if c != running_child:
+ c.before_run(actor, blackboard)
+
+ var response = c.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
+
+ if c is ConditionLeaf:
+ blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = c
+ if c is ActionLeaf:
+ blackboard.set_value("running_action", c, str(actor.get_instance_id()))
+ return RUNNING
+ else:
+ c.after_run(actor, blackboard)
+ return SUCCESS
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"AlwaysSucceedDecorator")
+ return classes
diff --git a/addons/beehave/nodes/decorators/time_limiter.gd b/addons/beehave/nodes/decorators/time_limiter.gd
new file mode 100644
index 0000000..cedb397
--- /dev/null
+++ b/addons/beehave/nodes/decorators/time_limiter.gd
@@ -0,0 +1,46 @@
+## The Time Limit Decorator will give its child a set amount of time to finish
+## before interrupting it and return a `FAILURE` status code. The timer is reset
+## every time before the node runs.
+@tool
+@icon("../../icons/limiter.svg")
+class_name TimeLimiterDecorator extends Decorator
+
+@export var wait_time: = 0.0
+
+var time_left: = 0.0
+
+@onready var child: BeehaveNode = get_child(0)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ if time_left < wait_time:
+ time_left += get_physics_process_delta_time()
+ var response = child.tick(actor, blackboard)
+ if can_send_message(blackboard):
+ BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
+
+ if child is ConditionLeaf:
+ blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
+ blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
+
+ if response == RUNNING:
+ running_child = child
+ if child is ActionLeaf:
+ blackboard.set_value("running_action", child, str(actor.get_instance_id()))
+
+ return response
+ else:
+ child.after_run(actor, blackboard)
+ interrupt(actor, blackboard)
+ return FAILURE
+
+
+func before_run(actor: Node, blackboard: Blackboard) -> void:
+ time_left = 0.0
+ child.before_run(actor, blackboard)
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"TimeLimiterDecorator")
+ return classes
diff --git a/addons/beehave/nodes/leaves/action.gd b/addons/beehave/nodes/leaves/action.gd
new file mode 100644
index 0000000..003bdb4
--- /dev/null
+++ b/addons/beehave/nodes/leaves/action.gd
@@ -0,0 +1,13 @@
+## Actions are leaf nodes that define a task to be performed by an actor.
+## Their execution can be long running, potentially being called across multiple
+## frame executions. In this case, the node should return `RUNNING` until the
+## action is completed.
+@tool
+@icon("../../icons/action.svg")
+class_name ActionLeaf extends Leaf
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"ActionLeaf")
+ return classes
diff --git a/addons/beehave/nodes/leaves/blackboard_compare.gd b/addons/beehave/nodes/leaves/blackboard_compare.gd
new file mode 100644
index 0000000..84fb43f
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_compare.gd
@@ -0,0 +1,61 @@
+## Compares two values using the specified comparison operator.
+## Returns [code]FAILURE[/code] if any of the expression fails or the
+## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code].
+@tool
+class_name BlackboardCompareCondition extends ConditionLeaf
+
+
+enum Operators {
+ EQUAL,
+ NOT_EQUAL,
+ GREATER,
+ LESS,
+ GREATER_EQUAL,
+ LESS_EQUAL,
+}
+
+
+## Expression represetning left operand.
+## This value can be any valid GDScript expression.
+## In order to use the existing blackboard keys for comparison,
+## use get_value("key_name") e.g. get_value("direction").length()
+@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = ""
+## Comparison operator.
+@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0
+## Expression represetning right operand.
+## This value can be any valid GDScript expression.
+## In order to use the existing blackboard keys for comparison,
+## use get_value("key_name") e.g. get_value("direction").length()
+@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = ""
+
+
+@onready var _left_expression: Expression = _parse_expression(left_operand)
+@onready var _right_expression: Expression = _parse_expression(right_operand)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var left: Variant = _left_expression.execute([], blackboard)
+
+ if _left_expression.has_execute_failed():
+ return FAILURE
+
+ var right: Variant = _right_expression.execute([], blackboard)
+
+ if _right_expression.has_execute_failed():
+ return FAILURE
+
+ var result: bool = false
+
+ match operator:
+ Operators.EQUAL: result = left == right
+ Operators.NOT_EQUAL: result = left != right
+ Operators.GREATER: result = left > right
+ Operators.LESS: result = left < right
+ Operators.GREATER_EQUAL: result = left >= right
+ Operators.LESS_EQUAL: result = left <= right
+
+ return SUCCESS if result else FAILURE
+
+
+func _get_expression_sources() -> Array[String]:
+ return [left_operand, right_operand]
diff --git a/addons/beehave/nodes/leaves/blackboard_erase.gd b/addons/beehave/nodes/leaves/blackboard_erase.gd
new file mode 100644
index 0000000..89508bd
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_erase.gd
@@ -0,0 +1,25 @@
+## Erases the specified key from the blackboard.
+## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
+@tool
+class_name BlackboardEraseAction extends ActionLeaf
+
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+
+@onready var _key_expression: Expression = _parse_expression(key)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ blackboard.erase_value(key_value)
+
+ return SUCCESS
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key]
diff --git a/addons/beehave/nodes/leaves/blackboard_has.gd b/addons/beehave/nodes/leaves/blackboard_has.gd
new file mode 100644
index 0000000..868fddf
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_has.gd
@@ -0,0 +1,23 @@
+## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist.
+## Returns [code]SUCCESS[/code] if blackboard has the specified key.
+@tool
+class_name BlackboardHasCondition extends ConditionLeaf
+
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+
+@onready var _key_expression: Expression = _parse_expression(key)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ return SUCCESS if blackboard.has_value(key_value) else FAILURE
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key]
diff --git a/addons/beehave/nodes/leaves/blackboard_set.gd b/addons/beehave/nodes/leaves/blackboard_set.gd
new file mode 100644
index 0000000..d0d6455
--- /dev/null
+++ b/addons/beehave/nodes/leaves/blackboard_set.gd
@@ -0,0 +1,34 @@
+## Sets the specified key to the specified value.
+## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
+@tool
+class_name BlackboardSetAction extends ActionLeaf
+
+
+## Expression representing a blackboard key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
+## Expression representing a blackboard value to assign to the specified key.
+@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = ""
+
+
+@onready var _key_expression: Expression = _parse_expression(key)
+@onready var _value_expression: Expression = _parse_expression(value)
+
+
+func tick(actor: Node, blackboard: Blackboard) -> int:
+ var key_value: Variant = _key_expression.execute([], blackboard)
+
+ if _key_expression.has_execute_failed():
+ return FAILURE
+
+ var value_value: Variant = _value_expression.execute([], blackboard)
+
+ if _value_expression.has_execute_failed():
+ return FAILURE
+
+ blackboard.set_value(key_value, value_value)
+
+ return SUCCESS
+
+
+func _get_expression_sources() -> Array[String]:
+ return [key, value]
diff --git a/addons/beehave/nodes/leaves/condition.gd b/addons/beehave/nodes/leaves/condition.gd
new file mode 100644
index 0000000..55ec6f9
--- /dev/null
+++ b/addons/beehave/nodes/leaves/condition.gd
@@ -0,0 +1,11 @@
+## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
+## a single simple condition. They should never return `RUNNING`.
+@tool
+@icon("../../icons/condition.svg")
+class_name ConditionLeaf extends Leaf
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"ConditionLeaf")
+ return classes
diff --git a/addons/beehave/nodes/leaves/leaf.gd b/addons/beehave/nodes/leaves/leaf.gd
new file mode 100644
index 0000000..b8c63b8
--- /dev/null
+++ b/addons/beehave/nodes/leaves/leaf.gd
@@ -0,0 +1,46 @@
+## Base class for all leaf nodes of the tree.
+@tool
+@icon("../../icons/category_leaf.svg")
+class_name Leaf extends BeehaveNode
+
+
+const EXPRESSION_PLACEHOLDER: String = "Insert an expression..."
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+
+ var children: Array[Node] = get_children()
+
+ if children.any(func(x): return x is BeehaveNode):
+ warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.")
+
+ for source in _get_expression_sources():
+ var error_text: String = _parse_expression(source).get_error_text()
+ if not error_text.is_empty():
+ warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text])
+
+ return warnings
+
+
+func _parse_expression(source: String) -> Expression:
+ var result: Expression = Expression.new()
+ var error: int = result.parse(source)
+
+ if not Engine.is_editor_hint() and error != OK:
+ push_error(
+ "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" %\
+ [source, result.get_error_text()]
+ )
+
+ return result
+
+
+func _get_expression_sources() -> Array[String]: # virtual
+ return []
+
+
+func get_class_name() -> Array[StringName]:
+ var classes := super()
+ classes.push_back(&"Leaf")
+ return classes
diff --git a/addons/beehave/plugin.cfg b/addons/beehave/plugin.cfg
new file mode 100644
index 0000000..8b61a2f
--- /dev/null
+++ b/addons/beehave/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Beehave"
+description="🐝 Behavior Tree addon for Godot Engine"
+author="bitbrain"
+version="2.7.6"
+script="plugin.gd"
diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd
new file mode 100644
index 0000000..3c54627
--- /dev/null
+++ b/addons/beehave/plugin.gd
@@ -0,0 +1,24 @@
+@tool
+extends EditorPlugin
+
+const BeehaveEditorDebugger := preload("debug/debugger.gd")
+var editor_debugger: BeehaveEditorDebugger
+var frames: RefCounted
+
+func _init():
+ name = "BeehavePlugin"
+ add_autoload_singleton("BeehaveGlobalMetrics", "res://addons/beehave/metrics/beehave_global_metrics.gd")
+ add_autoload_singleton("BeehaveGlobalDebugger", "res://addons/beehave/debug/global_debugger.gd")
+ print("Beehave initialized!")
+
+
+func _enter_tree() -> void:
+ editor_debugger = BeehaveEditorDebugger.new()
+ frames = preload("debug/frames.gd").new()
+ add_debugger_plugin(editor_debugger)
+
+
+func _exit_tree() -> void:
+ remove_debugger_plugin(editor_debugger)
+ editor_debugger.free()
+ frames.free()
diff --git a/addons/beehave/utils/utils.gd b/addons/beehave/utils/utils.gd
new file mode 100644
index 0000000..32e3a03
--- /dev/null
+++ b/addons/beehave/utils/utils.gd
@@ -0,0 +1,22 @@
+@tool
+class_name BeehaveUtils
+
+
+static func get_plugin() -> EditorPlugin:
+ var tree: SceneTree = Engine.get_main_loop()
+ return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin")
+
+
+static func get_editor_scale() -> float:
+ var plugin := get_plugin()
+ if plugin:
+ return plugin.get_editor_interface().get_editor_scale()
+ return 1.0
+
+
+static func get_frames() -> RefCounted:
+ var plugin := get_plugin()
+ if plugin:
+ return plugin.frames
+ push_error("Can't find Beehave Plugin")
+ return null
diff --git a/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn b/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn
index 6206cbb..745980d 100644
--- a/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn
+++ b/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn
@@ -4,7 +4,7 @@
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.gd" id="1_qv6ov"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_a4ihj"]
-[sub_resource type="Image" id="Image_rfojr"]
+[sub_resource type="Image" id="Image_f27j1"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
"format": "LumAlpha8",
@@ -14,7 +14,7 @@ data = {
}
[sub_resource type="ImageTexture" id="2"]
-image = SubResource("Image_rfojr")
+image = SubResource("Image_f27j1")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ydxuk"]
content_margin_left = 4.0
diff --git a/addons/resources_spreadsheet_view/saved_state.json b/addons/resources_spreadsheet_view/saved_state.json
index cd0ffe9..2958890 100644
--- a/addons/resources_spreadsheet_view/saved_state.json
+++ b/addons/resources_spreadsheet_view/saved_state.json
@@ -8,6 +8,10 @@
"resource_local_to_scene": true,
"resource_name": true
},
+ "res://config/character_move/": {
+ "resource_local_to_scene": true,
+ "resource_name": true
+ },
"res://config/player_skill/": {
"animation_name": true,
"has_animation": true,
@@ -24,7 +28,8 @@
"res://example/Items/items/",
"res://config/character/",
"res://config/player_skill/",
- "res://config/attack/"
+ "res://config/attack/",
+ "res://config/character_move/"
],
"table_functions": {
"filter": [
diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn
index afdada7..cff00e8 100644
--- a/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn
+++ b/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn
@@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.gd" id="1"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2"]
-[sub_resource type="Image" id="Image_tduhc"]
+[sub_resource type="Image" id="Image_lpd5n"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
@@ -13,7 +13,7 @@ data = {
}
[sub_resource type="ImageTexture" id="ImageTexture_3oshq"]
-image = SubResource("Image_tduhc")
+image = SubResource("Image_lpd5n")
[node name="EditArray" type="VBoxContainer"]
anchors_preset = 10
diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn
index 705e6d9..84f097a 100644
--- a/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn
+++ b/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn
@@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_dict.gd" id="1_2yivi"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_yck0k"]
-[sub_resource type="Image" id="Image_nywml"]
+[sub_resource type="Image" id="Image_wuya0"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
@@ -13,7 +13,7 @@ data = {
}
[sub_resource type="ImageTexture" id="ImageTexture_3oshq"]
-image = SubResource("Image_nywml")
+image = SubResource("Image_wuya0")
[node name="EditArray" type="VBoxContainer"]
anchors_preset = 10
diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn
index 32b9979..f918fe0 100644
--- a/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn
+++ b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn
@@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd" id="1_n3flg"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_mda1e"]
-[sub_resource type="Image" id="Image_x8wr6"]
+[sub_resource type="Image" id="Image_s5ag4"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
@@ -13,7 +13,7 @@ data = {
}
[sub_resource type="ImageTexture" id="ImageTexture_3oshq"]
-image = SubResource("Image_x8wr6")
+image = SubResource("Image_s5ag4")
[node name="EditEnumArray" type="VBoxContainer"]
anchors_preset = 10
diff --git a/config/character_move/normal.tres b/config/character_move/normal.tres
index 225369e..db13f31 100644
--- a/config/character_move/normal.tres
+++ b/config/character_move/normal.tres
@@ -4,6 +4,6 @@
[resource]
script = ExtResource("1_r4l81")
-speed = 1.5
+speed = 3.0
gravity_scale = 1.0
-jump_velocity = 3.0
+jump_velocity = 4.0
diff --git a/config/player_skill/hero01_long_air_attack03.tres b/config/player_skill/hero01_long_air_attack03.tres
index f18d677..05cbdf0 100644
--- a/config/player_skill/hero01_long_air_attack03.tres
+++ b/config/player_skill/hero01_long_air_attack03.tres
@@ -16,6 +16,6 @@ action = "attack_light"
name = ""
skill_animation = ExtResource("3_1erk8")
attack_list = Array[Resource("res://script/config/attack_cfg.gd")]([ExtResource("1_x3v4o")])
-has_animation = false
+refresh_animation = false
sprite_frams = ExtResource("4_wrd60")
animation_name = "long_air_attack03"
diff --git a/config/player_skill/hero01_long_attack03.tres b/config/player_skill/hero01_long_attack03.tres
index 73fdcdf..788312c 100644
--- a/config/player_skill/hero01_long_attack03.tres
+++ b/config/player_skill/hero01_long_attack03.tres
@@ -16,6 +16,6 @@ action = "attack_light"
name = ""
skill_animation = ExtResource("2_ugt3f")
attack_list = Array[Resource("res://script/config/attack_cfg.gd")]([ExtResource("1_7ai5j")])
-has_animation = false
+refresh_animation = false
sprite_frams = ExtResource("3_sr2og")
animation_name = "long_attack03"
diff --git a/config/skill/hero01.tres b/config/skill/hero01.tres
deleted file mode 100644
index 22f3ffe..0000000
--- a/config/skill/hero01.tres
+++ /dev/null
@@ -1,12 +0,0 @@
-[gd_resource type="Resource" script_class="SkillCfg" load_steps=3 format=3 uid="uid://p7rrgcllpisi"]
-
-[ext_resource type="Script" path="res://script/config/skill_cfg.gd" id="1_jpm54"]
-[ext_resource type="SpriteFrames" uid="uid://2cb8lknel0ih" path="res://resource/animation/character/basic_move.aseprite" id="2_2pfl0"]
-
-[resource]
-script = ExtResource("1_jpm54")
-name = ""
-attack_list = Array[Resource("res://script/config/attack_cfg.gd")]([])
-has_animation = false
-sprite_frams = ExtResource("2_2pfl0")
-animation_name = ""
diff --git a/config/skill/monster01_attack01.tres b/config/skill/monster01_attack01.tres
new file mode 100644
index 0000000..23a2739
--- /dev/null
+++ b/config/skill/monster01_attack01.tres
@@ -0,0 +1,14 @@
+[gd_resource type="Resource" script_class="SkillCfg" load_steps=4 format=3 uid="uid://bohydsbv7kxu3"]
+
+[ext_resource type="Script" path="res://script/config/skill_cfg.gd" id="1_r8y6y"]
+[ext_resource type="Animation" uid="uid://b8ypa7uw0uam5" path="res://resource/skill_animation/monster01_attack01.tres" id="2_hmrt4"]
+[ext_resource type="SpriteFrames" uid="uid://bs74u0yvluhky" path="res://resource/animation/character/monster01_attack.aseprite" id="3_4nfis"]
+
+[resource]
+script = ExtResource("1_r8y6y")
+name = ""
+skill_animation = ExtResource("2_hmrt4")
+attack_list = Array[Resource("res://script/config/attack_cfg.gd")]([])
+refresh_animation = false
+sprite_frams = ExtResource("3_4nfis")
+animation_name = "attack01"
diff --git a/project.godot b/project.godot
index 826b6ef..e7d2f07 100644
--- a/project.godot
+++ b/project.godot
@@ -22,7 +22,7 @@ resources_spreadsheet_view/context_menu_on_leftclick=false
config/name="Touhou Gd"
run/main_scene="res://scene/launcher.tscn"
-config/features=PackedStringArray("4.2", "Forward Plus")
+config/features=PackedStringArray("4.1", "Forward Plus")
config/icon="res://icon.svg"
[aseprite_importers]
@@ -38,6 +38,8 @@ Setting="*res://script/_global/setting.gd"
Enum="*res://script/_global/enum.gd"
Util="*res://script/_global/util.gd"
Global="*res://script/_global/global.gd"
+BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd"
+BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd"
[display]
@@ -45,7 +47,7 @@ window/size/always_on_top=true
[editor_plugins]
-enabled=PackedStringArray("res://addons/MagicaVoxel_Importer_with_Extensions/plugin.cfg", "res://addons/nklbdev.aseprite_importers/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg")
+enabled=PackedStringArray("res://addons/MagicaVoxel_Importer_with_Extensions/plugin.cfg", "res://addons/beehave/plugin.cfg", "res://addons/nklbdev.aseprite_importers/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg")
[file_customization]
diff --git a/render/mesh/slash1.obj.import b/render/mesh/slash1.obj.import
index c2e9203..c822f98 100644
--- a/render/mesh/slash1.obj.import
+++ b/render/mesh/slash1.obj.import
@@ -19,4 +19,3 @@ generate_tangents=true
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
optimize_mesh=true
-force_disable_mesh_compression=false
diff --git a/render/mesh/slash2.obj.import b/render/mesh/slash2.obj.import
index fc865a1..8944f93 100644
--- a/render/mesh/slash2.obj.import
+++ b/render/mesh/slash2.obj.import
@@ -19,4 +19,3 @@ generate_tangents=true
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
optimize_mesh=true
-force_disable_mesh_compression=false
diff --git a/render/process_material/slash_normal.tres b/render/process_material/slash_normal.tres
index e95eb8b..3314587 100644
--- a/render/process_material/slash_normal.tres
+++ b/render/process_material/slash_normal.tres
@@ -9,14 +9,14 @@ gradient = SubResource("Gradient_fjosh")
[resource]
particle_flag_rotate_y = true
-angle_min = 180.0
-angle_max = 180.0
direction = Vector3(0, 1, 0)
spread = 0.0
+gravity = Vector3(0, 0, 0)
angular_velocity_min = -720.0
angular_velocity_max = -720.0
-gravity = Vector3(0, 0, 0)
tangential_accel_min = -100.0
-scale_min = 0.25
-scale_max = 0.25
+angle_min = 180.0
+angle_max = 180.0
+scale_min = 0.5
+scale_max = 0.5
color_ramp = SubResource("GradientTexture1D_cypke")
diff --git a/render/texture/explodeDecal.png b/render/texture/shape/explodeDecal.png
similarity index 100%
rename from render/texture/explodeDecal.png
rename to render/texture/shape/explodeDecal.png
diff --git a/render/texture/explodeDecal.png.import b/render/texture/shape/explodeDecal.png.import
similarity index 70%
rename from render/texture/explodeDecal.png.import
rename to render/texture/shape/explodeDecal.png.import
index c252f30..da2918e 100644
--- a/render/texture/explodeDecal.png.import
+++ b/render/texture/shape/explodeDecal.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bjv7f83tdgq17"
-path="res://.godot/imported/explodeDecal.png-f644d5db8c0778f842c076bf4d5a2eb8.ctex"
+path="res://.godot/imported/explodeDecal.png-98bda2cf336166681eaff595a5bec12f.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://render/texture/explodeDecal.png"
-dest_files=["res://.godot/imported/explodeDecal.png-f644d5db8c0778f842c076bf4d5a2eb8.ctex"]
+source_file="res://render/texture/shape/explodeDecal.png"
+dest_files=["res://.godot/imported/explodeDecal.png-98bda2cf336166681eaff595a5bec12f.ctex"]
[params]
diff --git a/render/texture/readiness_hero.png b/render/texture/shape/readiness_hero.png
similarity index 100%
rename from render/texture/readiness_hero.png
rename to render/texture/shape/readiness_hero.png
diff --git a/render/texture/readiness_hero.png.import b/render/texture/shape/readiness_hero.png.import
similarity index 69%
rename from render/texture/readiness_hero.png.import
rename to render/texture/shape/readiness_hero.png.import
index d8e9100..7bc6bd2 100644
--- a/render/texture/readiness_hero.png.import
+++ b/render/texture/shape/readiness_hero.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dpsxejelj58f8"
-path="res://.godot/imported/readiness_hero.png-d9ad5917bf7df1f3d654dd39aaecd923.ctex"
+path="res://.godot/imported/readiness_hero.png-79941e71d54e0f21f93bb8731b58c44c.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://render/texture/readiness_hero.png"
-dest_files=["res://.godot/imported/readiness_hero.png-d9ad5917bf7df1f3d654dd39aaecd923.ctex"]
+source_file="res://render/texture/shape/readiness_hero.png"
+dest_files=["res://.godot/imported/readiness_hero.png-79941e71d54e0f21f93bb8731b58c44c.ctex"]
[params]
diff --git a/render/texture/readiness_monster.png b/render/texture/shape/readiness_monster.png
similarity index 100%
rename from render/texture/readiness_monster.png
rename to render/texture/shape/readiness_monster.png
diff --git a/render/texture/readiness_monster.png.import b/render/texture/shape/readiness_monster.png.import
similarity index 69%
rename from render/texture/readiness_monster.png.import
rename to render/texture/shape/readiness_monster.png.import
index 48b6124..a751647 100644
--- a/render/texture/readiness_monster.png.import
+++ b/render/texture/shape/readiness_monster.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://xtipei54v35i"
-path="res://.godot/imported/readiness_monster.png-c62163d25615aac211401f6ba719c65d.ctex"
+path="res://.godot/imported/readiness_monster.png-7c27fc050064accb74fc75d7edb38639.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://render/texture/readiness_monster.png"
-dest_files=["res://.godot/imported/readiness_monster.png-c62163d25615aac211401f6ba719c65d.ctex"]
+source_file="res://render/texture/shape/readiness_monster.png"
+dest_files=["res://.godot/imported/readiness_monster.png-7c27fc050064accb74fc75d7edb38639.ctex"]
[params]
diff --git a/render/texture/swordDecal.png b/render/texture/shape/swordDecal.png
similarity index 100%
rename from render/texture/swordDecal.png
rename to render/texture/shape/swordDecal.png
diff --git a/render/texture/swordDecal.png.import b/render/texture/shape/swordDecal.png.import
similarity index 70%
rename from render/texture/swordDecal.png.import
rename to render/texture/shape/swordDecal.png.import
index 5049424..ea35b7f 100644
--- a/render/texture/swordDecal.png.import
+++ b/render/texture/shape/swordDecal.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ceu7b4nuktlgy"
-path="res://.godot/imported/swordDecal.png-cb01b48f8d07246ce447a89d9248f176.ctex"
+path="res://.godot/imported/swordDecal.png-152ce59ae7b5e48a88a78d43374add4a.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://render/texture/swordDecal.png"
-dest_files=["res://.godot/imported/swordDecal.png-cb01b48f8d07246ce447a89d9248f176.ctex"]
+source_file="res://render/texture/shape/swordDecal.png"
+dest_files=["res://.godot/imported/swordDecal.png-152ce59ae7b5e48a88a78d43374add4a.ctex"]
[params]
diff --git a/resource/skill_animation/hero01_long_attack01.tres b/resource/skill_animation/hero01_long_attack01.tres
index 55f5955..d1ebb0e 100644
--- a/resource/skill_animation/hero01_long_attack01.tres
+++ b/resource/skill_animation/hero01_long_attack01.tres
@@ -75,7 +75,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
-"values": [1.0, 0.0]
+"values": [2.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/hero01_long_attack02.tres b/resource/skill_animation/hero01_long_attack02.tres
index 4939220..969ed04 100644
--- a/resource/skill_animation/hero01_long_attack02.tres
+++ b/resource/skill_animation/hero01_long_attack02.tres
@@ -75,7 +75,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
-"values": [1.0, 0.0]
+"values": [2.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/hero01_long_attack03.tres b/resource/skill_animation/hero01_long_attack03.tres
index a5d74ad..25adf07 100644
--- a/resource/skill_animation/hero01_long_attack03.tres
+++ b/resource/skill_animation/hero01_long_attack03.tres
@@ -75,7 +75,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.3, 0.4, 0.5),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
-"values": [1.0, 0.0, 1.0, 0.0]
+"values": [2.0, 0.0, 2.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/hero01_long_attack04.tres b/resource/skill_animation/hero01_long_attack04.tres
index ecd71a3..4c68447 100644
--- a/resource/skill_animation/hero01_long_attack04.tres
+++ b/resource/skill_animation/hero01_long_attack04.tres
@@ -75,7 +75,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.5, 0.6),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
-"values": [0.5, 1.0, 0.0]
+"values": [1.0, 3.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/hero01_long_flash.tres b/resource/skill_animation/hero01_long_flash.tres
index f4a8118..50dc9ec 100644
--- a/resource/skill_animation/hero01_long_flash.tres
+++ b/resource/skill_animation/hero01_long_flash.tres
@@ -75,7 +75,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
-"values": [4.0, 0.0]
+"values": [6.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/hero01_long_skill01.tres b/resource/skill_animation/hero01_long_skill01.tres
index 9c8a5b8..33d7e24 100644
--- a/resource/skill_animation/hero01_long_skill01.tres
+++ b/resource/skill_animation/hero01_long_skill01.tres
@@ -74,7 +74,7 @@ tracks/5/keys = {
"times": PackedFloat32Array(0.1, 0.4),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
-"values": [4.0, 0.0]
+"values": [6.0, 0.0]
}
tracks/6/type = "method"
tracks/6/imported = false
diff --git a/resource/skill_animation/monster01_attack01.tres b/resource/skill_animation/monster01_attack01.tres
new file mode 100644
index 0000000..d31b4f5
--- /dev/null
+++ b/resource/skill_animation/monster01_attack01.tres
@@ -0,0 +1,43 @@
+[gd_resource type="Animation" load_steps=2 format=3 uid="uid://b8ypa7uw0uam5"]
+
+[ext_resource type="SpriteFrames" uid="uid://bs74u0yvluhky" path="res://resource/animation/character/monster01_attack.aseprite" id="1_7ykbn"]
+
+[resource]
+resource_name = "monster01_attack01"
+length = 0.8
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("View:sprite_frames")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [ExtResource("1_7ykbn")]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("View:animation")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": ["attack01"]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("View:frame")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7),
+"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1),
+"update": 0,
+"values": [0, 1, 2, 3, 4, 5, 6, 7]
+}
diff --git a/resource/skill_animation_library/animation_library.tres b/resource/skill_animation_library/animation_library.tres
index bad2329..a981371 100644
--- a/resource/skill_animation_library/animation_library.tres
+++ b/resource/skill_animation_library/animation_library.tres
@@ -1,4 +1,4 @@
-[gd_resource type="AnimationLibrary" load_steps=11 format=3 uid="uid://croik07a1qko5"]
+[gd_resource type="AnimationLibrary" load_steps=12 format=3 uid="uid://croik07a1qko5"]
[ext_resource type="Animation" uid="uid://p8l0puqxrkwh" path="res://resource/skill_animation/hero01_long_air_attack01.tres" id="1_b46g3"]
[ext_resource type="Animation" uid="uid://daopmieibx3b7" path="res://resource/skill_animation/hero01_long_attack01.tres" id="1_nwjtl"]
@@ -10,6 +10,7 @@
[ext_resource type="Animation" uid="uid://cwm116apu63n1" path="res://resource/skill_animation/hero01_long_flash.tres" id="5_fumom"]
[ext_resource type="Animation" uid="uid://bjnkrte7660pt" path="res://resource/skill_animation/hero01_long_skill01.tres" id="5_kt0qw"]
[ext_resource type="Animation" uid="uid://iprcbf277rf4" path="res://resource/skill_animation/hero01_long_skill02.tres" id="7_ui68j"]
+[ext_resource type="Animation" uid="uid://b8ypa7uw0uam5" path="res://resource/skill_animation/monster01_attack01.tres" id="11_q5gn4"]
[resource]
_data = {
@@ -22,5 +23,6 @@ _data = {
"hero01_long_attack04": ExtResource("4_36e6x"),
"hero01_long_flash": ExtResource("5_fumom"),
"hero01_long_skill01": ExtResource("5_kt0qw"),
-"hero01_long_skill02": ExtResource("7_ui68j")
+"hero01_long_skill02": ExtResource("7_ui68j"),
+"monster01_attack01": ExtResource("11_q5gn4")
}
diff --git a/scene/ai/action/find_target.tscn b/scene/ai/action/find_target.tscn
new file mode 100644
index 0000000..fc10400
--- /dev/null
+++ b/scene/ai/action/find_target.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://1t2pkg7j2dkg"]
+
+[ext_resource type="Script" path="res://script/ai/action/action_find_target.gd" id="1_jb1sy"]
+
+[node name="FindTarget" type="Node"]
+script = ExtResource("1_jb1sy")
diff --git a/scene/ai/action/move_to_target.tscn b/scene/ai/action/move_to_target.tscn
new file mode 100644
index 0000000..ed61c03
--- /dev/null
+++ b/scene/ai/action/move_to_target.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://06ww04hf5wl0"]
+
+[ext_resource type="Script" path="res://script/ai/action_with_target/action_move_to_target.gd" id="1_t8ni6"]
+
+[node name="MoveToTarget" type="Node"]
+script = ExtResource("1_t8ni6")
diff --git a/scene/character/character.tscn b/scene/character/character.tscn
index 5b9d52d..00ff653 100644
--- a/scene/character/character.tscn
+++ b/scene/character/character.tscn
@@ -44,6 +44,7 @@ layers = 524288
material_override = SubResource("ShaderMaterial_3u7mw")
lod_bias = 0.001
gi_mode = 0
+pixel_size = 0.02
double_sided = false
alpha_cut = 2
texture_filter = 0
diff --git a/scene/character/monster.tscn b/scene/character/monster.tscn
index e969d45..8d827f2 100644
--- a/scene/character/monster.tscn
+++ b/scene/character/monster.tscn
@@ -1,8 +1,29 @@
-[gd_scene load_steps=2 format=3 uid="uid://cdyymv2w1qr66"]
+[gd_scene load_steps=7 format=3 uid="uid://c37rf5ecfrvwn"]
[ext_resource type="PackedScene" uid="uid://ksxwg0alt2us" path="res://scene/character/character.tscn" id="1_eshlr"]
+[ext_resource type="Script" path="res://script/character/monster/ai.gd" id="2_7ei2q"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="3_x3u4t"]
+[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="4_k2hsy"]
+[ext_resource type="PackedScene" uid="uid://1t2pkg7j2dkg" path="res://scene/ai/action/find_target.tscn" id="5_t0771"]
+[ext_resource type="PackedScene" uid="uid://06ww04hf5wl0" path="res://scene/ai/action/move_to_target.tscn" id="6_v4m1x"]
[node name="Character" instance=ExtResource("1_eshlr")]
+[node name="Status" parent="." index="1"]
+speed_up_rate = -0.5
+
[node name="View" parent="." index="2"]
-animation = &"long_attack01"
+animation = &"long_air_attack01"
+
+[node name="AI" type="Node3D" parent="." index="8"]
+script = ExtResource("2_7ei2q")
+
+[node name="BeehaveTree" type="Node" parent="AI" index="0"]
+script = ExtResource("3_x3u4t")
+
+[node name="SequenceComposite" type="Node" parent="AI/BeehaveTree" index="0"]
+script = ExtResource("4_k2hsy")
+
+[node name="FindTarget" parent="AI/BeehaveTree/SequenceComposite" index="0" instance=ExtResource("5_t0771")]
+
+[node name="MoveToTarget" parent="AI/BeehaveTree/SequenceComposite" index="1" instance=ExtResource("6_v4m1x")]
diff --git a/scene/effect/afterimage/normal.tscn b/scene/effect/afterimage/normal.tscn
index 01f120e..a6abd8c 100644
--- a/scene/effect/afterimage/normal.tscn
+++ b/scene/effect/afterimage/normal.tscn
@@ -4,6 +4,7 @@
[ext_resource type="Script" path="res://script/effect/afterimage.gd" id="2_grtq1"]
[node name="AfterImage" type="AnimatedSprite3D"]
+pixel_size = 0.02
sprite_frames = ExtResource("1_1twno")
animation = &"idle_loop"
script = ExtResource("2_grtq1")
diff --git a/scene/effect/decal/readiness_hero.tscn b/scene/effect/decal/readiness_hero.tscn
index c01d8cc..38b381f 100644
--- a/scene/effect/decal/readiness_hero.tscn
+++ b/scene/effect/decal/readiness_hero.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://7ej8d4b1lc0v"]
-[ext_resource type="Texture2D" uid="uid://dpsxejelj58f8" path="res://render/texture/readiness_hero.png" id="1_gkbai"]
+[ext_resource type="Texture2D" uid="uid://dpsxejelj58f8" path="res://render/texture/shape/readiness_hero.png" id="1_gkbai"]
[node name="ReadinessHero" type="Decal"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.358925, 0)
diff --git a/scene/effect/decal/readiness_monster.tscn b/scene/effect/decal/readiness_monster.tscn
index eee86c7..c8213e1 100644
--- a/scene/effect/decal/readiness_monster.tscn
+++ b/scene/effect/decal/readiness_monster.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://64e2u2uedpi1"]
-[ext_resource type="Texture2D" uid="uid://xtipei54v35i" path="res://render/texture/readiness_monster.png" id="1_2mpei"]
+[ext_resource type="Texture2D" uid="uid://xtipei54v35i" path="res://render/texture/shape/readiness_monster.png" id="1_2mpei"]
[node name="ReadinessHero" type="Decal"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.358925, 0)
diff --git a/scene/effect/particle/particle_slash_normal.tscn b/scene/effect/particle/particle_slash_normal.tscn
index 1d7d8e1..f269094 100644
--- a/scene/effect/particle/particle_slash_normal.tscn
+++ b/scene/effect/particle/particle_slash_normal.tscn
@@ -10,7 +10,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.699187, 4.61636)
script = ExtResource("1_fx8ev")
[node name="Slash" type="GPUParticles3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.174235, 0, 0)
material_override = ExtResource("1_6c80n")
cast_shadow = 0
emitting = false
diff --git a/scene/launcher.tscn b/scene/launcher.tscn
index bab396d..5409a7a 100644
--- a/scene/launcher.tscn
+++ b/scene/launcher.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=12 format=3 uid="uid://bkhnhc6bk3w7l"]
+[gd_scene load_steps=11 format=3 uid="uid://eoydwrunmm5n"]
[ext_resource type="Script" path="res://script/manager/game_manager.gd" id="1_q2t80"]
[ext_resource type="ArrayMesh" uid="uid://cap7t5iwpjpi2" path="res://resource/level/levelground0000.vox" id="1_u51ir"]
@@ -7,9 +7,8 @@
[ext_resource type="Script" path="res://script/editor_tool/editor_tool.gd" id="5_n3qhi"]
[ext_resource type="PackedScene" uid="uid://cc525u8auypjf" path="res://scene/ui/profile_screen.tscn" id="6_u1fxn"]
[ext_resource type="PackedScene" uid="uid://126wph4owvoy" path="res://scene/ui/hud_screen.tscn" id="7_gx646"]
-[ext_resource type="Texture2D" uid="uid://bjv7f83tdgq17" path="res://render/texture/explodeDecal.png" id="7_hqv3v"]
[ext_resource type="Script" path="res://script/manager/effect_manager.gd" id="8_0jv87"]
-[ext_resource type="PackedScene" uid="uid://b2h4pcmlii7dg" path="res://scene/effect/particle/particle_slash_normal.tscn" id="10_gyy7x"]
+[ext_resource type="Texture2D" uid="uid://bjv7f83tdgq17" path="res://render/texture/shape/explodeDecal.png" id="9_4x7bs"]
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_2mxa4"]
data = PackedVector3Array(-6.4, 0, -6.4, -3.2, 0, -6.4, -3.2, 0, 6.4, -3.2, 0, 6.4, -6.4, 0, 6.4, -6.4, 0, -6.4, -3.2, 0, 3.2, 6.4, 0, 3.2, 6.4, 0, 6.4, 6.4, 0, 6.4, -3.2, 0, 6.4, -3.2, 0, 3.2, -3.2, 0, -3.2, 3.2, 0, -3.2, 3.2, 0, 3.2, 3.2, 0, 3.2, -3.2, 0, 3.2, -3.2, 0, -3.2, -3.2, 0, -6.4, 6.4, 0, -6.4, 6.4, 0, -3.2, 6.4, 0, -3.2, -3.2, 0, -3.2, -3.2, 0, -6.4, 3.2, 0, -3.2, 6.4, 0, -3.2, 6.4, 0, 3.2, 6.4, 0, 3.2, 3.2, 0, 3.2, 3.2, 0, -3.2, -3.2, -0.1, 6.4, -3.2, -0.1, -6.4, -6.4, -0.1, -6.4, -6.4, -0.1, -6.4, -6.4, -0.1, 6.4, -3.2, -0.1, 6.4, 6.4, -0.1, 6.4, 6.4, -0.1, 3.2, -3.2, -0.1, 3.2, -3.2, -0.1, 3.2, -3.2, -0.1, 6.4, 6.4, -0.1, 6.4, 3.2, -0.1, 3.2, 3.2, -0.1, -3.2, -3.2, -0.1, -3.2, -3.2, -0.1, -3.2, -3.2, -0.1, 3.2, 3.2, -0.1, 3.2, 6.4, -0.1, -3.2, 6.4, -0.1, -6.4, -3.2, -0.1, -6.4, -3.2, -0.1, -6.4, -3.2, -0.1, -3.2, 6.4, -0.1, -3.2, 6.4, -0.1, 3.2, 6.4, -0.1, -3.2, 3.2, -0.1, -3.2, 3.2, -0.1, -3.2, 3.2, -0.1, 3.2, 6.4, -0.1, 3.2, -6.4, 0, -6.4, -6.4, 0, 6.4, -6.4, -0.1, 6.4, -6.4, -0.1, 6.4, -6.4, -0.1, -6.4, -6.4, 0, -6.4, 6.4, 0, -6.4, 6.4, -0.1, -6.4, 6.4, -0.1, 6.4, 6.4, -0.1, 6.4, 6.4, 0, 6.4, 6.4, 0, -6.4, -6.4, -0.1, 6.4, -6.4, 0, 6.4, 6.4, 0, 6.4, 6.4, 0, 6.4, 6.4, -0.1, 6.4, -6.4, -0.1, 6.4, 6.4, 0, -6.4, -6.4, 0, -6.4, -6.4, -0.1, -6.4, -6.4, -0.1, -6.4, 6.4, -0.1, -6.4, 6.4, 0, -6.4)
@@ -23,7 +22,7 @@ script = ExtResource("1_q2t80")
transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 4.01178, 2.85449)
projection = 1
current = true
-size = 5.0
+size = 8.0
frustum_offset = Vector2(2, 0)
far = 20.0
script = ExtResource("4_yqiun")
@@ -44,10 +43,6 @@ skeleton = NodePath("../../..")
[node name="CollisionShape3D" type="CollisionShape3D" parent="GameManager/LevelManager/Levelground0000/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_2mxa4")
-[node name="CSGBox3D" type="CSGBox3D" parent="GameManager/LevelManager"]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.13326, 0, 0)
-use_collision = true
-
[node name="CharacterManager" type="Node3D" parent="GameManager"]
unique_name_in_owner = true
script = ExtResource("4_oonkb")
@@ -67,8 +62,6 @@ script = ExtResource("5_n3qhi")
[node name="Decal" type="Decal" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.924324, -0.0136981, 0)
size = Vector3(0.5, 0.5, 0.5)
-texture_albedo = ExtResource("7_hqv3v")
+texture_albedo = ExtResource("9_4x7bs")
modulate = Color(0, 0, 0, 1)
cull_mask = 1
-
-[node name="Effect" parent="." instance=ExtResource("10_gyy7x")]
diff --git a/script/_global/setting.gd b/script/_global/setting.gd
index d831a37..169bbfd 100644
--- a/script/_global/setting.gd
+++ b/script/_global/setting.gd
@@ -1,7 +1,7 @@
extends Node3D
#基本信息
-const pixel_size : float = 0.01
+const pixel_size : float = 0.02
const sprite_scale : float = 1.414
#技能系统
diff --git a/script/ai/action.gd b/script/ai/action.gd
new file mode 100644
index 0000000..8517526
--- /dev/null
+++ b/script/ai/action.gd
@@ -0,0 +1,21 @@
+extends ActionLeaf
+class_name Action
+
+func get_character(actor:Node) -> Character:
+ if not actor is AI:
+ print("行为树结构错误")
+ return null
+ var ai = actor as AI
+ return ai.character
+
+func before_run(actor:Node, blackboard: Blackboard) -> void:
+ init(get_character(actor))
+
+func tick(actor:Node, blackboard: Blackboard) -> int:
+ var character = get_character(actor)
+ if character.get_status("is_stagger") or character.get_status("is_stun"):
+ return FAILURE
+ return run(character)
+
+func init(character: Character): pass
+func run(character: Character) -> int: return FAILURE
diff --git a/script/ai/action/action_find_target.gd b/script/ai/action/action_find_target.gd
new file mode 100644
index 0000000..e184df3
--- /dev/null
+++ b/script/ai/action/action_find_target.gd
@@ -0,0 +1,6 @@
+extends Action
+
+func run(character: Character) -> int:
+ var player_id = Global.character_mgr.get_player_id()
+ character.set_status("target",player_id)
+ return SUCCESS
diff --git a/script/ai/action_with_target.gd b/script/ai/action_with_target.gd
new file mode 100644
index 0000000..a9e9d42
--- /dev/null
+++ b/script/ai/action_with_target.gd
@@ -0,0 +1,11 @@
+extends Action
+class_name ActionWithTarget
+
+func init(character: Character): pass
+func run(character: Character) -> int:
+ var target = Global.character_mgr.get_character(character.get_status("target"))
+ if not target:
+ return FAILURE
+ return execute(character,target)
+
+func execute(character: Character,target: Character) -> int : return FAILURE
diff --git a/script/ai/action_with_target/action_move_to_target.gd b/script/ai/action_with_target/action_move_to_target.gd
new file mode 100644
index 0000000..d46b5d3
--- /dev/null
+++ b/script/ai/action_with_target/action_move_to_target.gd
@@ -0,0 +1,12 @@
+extends ActionWithTarget
+
+func execute(character: Character, target: Character) -> int:
+ var dir = target.pos2D() - character.pos2D()
+ var dist = dir.length()
+ if dist < 1:
+ character.move_stop()
+ else:
+ print(dir)
+ character.move_to(dir)
+ return RUNNING
+ return SUCCESS
diff --git a/script/character/battle.gd b/script/character/battle.gd
index fd20157..374258f 100644
--- a/script/character/battle.gd
+++ b/script/character/battle.gd
@@ -28,7 +28,7 @@ func attack():
var pos_dir = enemy.pos2D()-character.pos2D()
var distance = pos_dir.length()
#test
- if (distance < 1):
+ if (distance < 2):
var hit_info = HitInfo.new()
hit_info.from = character.id()
hit_info.to = enemy.id()
@@ -120,6 +120,9 @@ func settle(hit_info:HitInfo):
#取消技能
if character_to.get_status("is_skill_running"):
character_to.cancel_skill()
+
+ #停止移动
+ character_to.move_stop()
#受击动画
var trigger_hit = ""
diff --git a/script/character/character.gd b/script/character/character.gd
index 08e4052..41ec3c3 100644
--- a/script/character/character.gd
+++ b/script/character/character.gd
@@ -50,6 +50,8 @@ func ui_pos()->Vector3:return position + status.ui_offset
func get_status(status_name:String):return status.get_status(status_name)
func set_status(status_name:String,value):status.set_status(status_name,value)
func set_pos(pos:Vector3):position = pos
+func move_to(dir:Vector2):set_status("move_dir",dir)
+func move_stop():set_status("move_dir",Vector2.ZERO)
func add_buff(buff_name:String,duration:float,ignore_pause:bool=false):buff.add_buff(buff_name,duration,ignore_pause)
func remove_buff(buff_name:String):buff.remove_buff(buff_name)
func has_buff(buff_name:String)->bool:return buff.has_buff(buff_name)
diff --git a/script/character/effect.gd b/script/character/effect.gd
index 6f055cf..01c35ad 100644
--- a/script/character/effect.gd
+++ b/script/character/effect.gd
@@ -12,6 +12,7 @@ class_name Effect
var rediness : Decal
var is_pause : bool
+var is_right : bool
func init(type:Enum.ECharacterType,body_scale:Vector3):
match type:
@@ -29,7 +30,11 @@ func _process(delta):
var angle = status.move_dir.angle_to(Vector2.RIGHT)
rediness.rotation.y = angle
#flip
- scale.x = 1 if status.is_right else -1
+ if is_right != status.is_right:
+ is_right = status.is_right
+ for child in get_children():
+ if child is Particle:
+ child.scale.x = 1 if is_right else -1
#pause
if is_pause != status.is_pause:
is_pause = status.is_pause
@@ -62,6 +67,7 @@ func cast_attack_particle():
dir.x = abs(dir.x)
var angle = dir.angle_to(Vector2.RIGHT)
new_particle.rotation.y = angle
+ new_particle.scale.x = 1 if is_right else -1
add_child(new_particle)
func release_effect():
diff --git a/script/character/monster/ai.gd b/script/character/monster/ai.gd
new file mode 100644
index 0000000..74aa964
--- /dev/null
+++ b/script/character/monster/ai.gd
@@ -0,0 +1,8 @@
+extends Node3D
+class_name AI
+
+@onready var character = (get_owner() as Character)
+@onready var status = (%Status as Status)
+
+func _ready():
+ pass
diff --git a/script/character/move.gd b/script/character/move.gd
index 9ddbbe1..df36403 100644
--- a/script/character/move.gd
+++ b/script/character/move.gd
@@ -32,7 +32,7 @@ func update_speed_y(delta):
status.speed_y = character.velocity.y
func update_move(delta):
- var move_velocity = status.move_dir * (status.cfg.move.speed * (1 + status.speed_up_rate))
+ var move_velocity = status.move_dir.normalized() * (status.cfg.move.speed * (1 + status.speed_up_rate))
var skill_velocity = status.skill_dir * status.skill_move_speed
var hit_back_velocity = status.hit_back_dir * status.hit_back_speed
move_velocity += skill_velocity + hit_back_velocity
diff --git a/script/character/status.gd b/script/character/status.gd
index 5591ea8..77728c9 100644
--- a/script/character/status.gd
+++ b/script/character/status.gd
@@ -17,6 +17,7 @@ class_name Status
@export var stun : float #当前眩晕值
@export var stun_max : float #眩晕值最大值
@export var is_dead : bool #是否死亡
+@export var target : int #目标角色
@export_category("表现状态")
@export var basic_offset : Vector3 #基本偏移值
diff --git a/script/character/view.gd b/script/character/view.gd
index 33323dc..e965e06 100644
--- a/script/character/view.gd
+++ b/script/character/view.gd
@@ -148,12 +148,16 @@ func _on_animation_finished():
update_trans(true)
func update_material():
- var tex = sprite_frames.get_frame_texture(animation,frame)
var material = material_override as ShaderMaterial
- material.set_shader_parameter("tex",tex)
material.set_shader_parameter("flash_white",status.flash_white_rate)
material.set_shader_parameter("deformation_dir",status.deformation_dir)
material.set_shader_parameter("deformation_rate",status.deformation_rate*0.4)
+ call_deferred("refresh_texture")
+
+func refresh_texture():
+ var material = material_override as ShaderMaterial
+ var tex = sprite_frames.get_frame_texture(animation,frame)
+ material.set_shader_parameter("tex",tex)
func clone(target:AnimatedSprite3D):
target.sprite_frames = sprite_frames
diff --git a/script/manager/character_manager.gd b/script/manager/character_manager.gd
index dabf5cd..22e6d28 100644
--- a/script/manager/character_manager.gd
+++ b/script/manager/character_manager.gd
@@ -3,6 +3,7 @@ class_name CharacterManager
var character_map = {}
var character_idx : int = 0
+var player_id : int
func _ready():
Global.character_mgr = self
@@ -24,6 +25,9 @@ func create_character(cfg:CharacterCfg,team:Enum.ETeam,pos:Vector3):
character.set_pos(pos)
SignalManager.character_create.emit(character_idx,team,pos)
character.init_after()
+
+ if cfg.type == Enum.ECharacterType.Player:
+ player_id = character_idx
func destroy_character(id:int):
if not id in character_map:
@@ -38,6 +42,9 @@ func get_character(id:int) -> Character:
else:
return null
+func get_player() -> Character:return get_character(player_id)
+func get_player_id() -> int:return player_id
+
func get_enemy_list(id:int) -> Array[Character]:
var ret:Array[Character] = []
var target = get_character(id)