From 7e577e6c4d8d5f62c8c8d96032332a24819366fc Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Sun, 28 Jul 2024 23:10:46 -0600 Subject: [PATCH] Added Beehave as addon --- addons/beehave/LICENSE | 21 ++ addons/beehave/blackboard.gd | 51 +++ addons/beehave/debug/debugger.gd | 96 +++++ addons/beehave/debug/debugger_messages.gd | 30 ++ addons/beehave/debug/debugger_tab.gd | 125 +++++++ addons/beehave/debug/global_debugger.gd | 38 ++ .../beehave/debug/icons/horizontal_layout.svg | 3 + .../debug/icons/horizontal_layout.svg.import | 38 ++ addons/beehave/debug/icons/port_bottom.svg | 3 + .../debug/icons/port_bottom.svg.import | 38 ++ addons/beehave/debug/icons/port_left.svg | 3 + .../beehave/debug/icons/port_left.svg.import | 38 ++ addons/beehave/debug/icons/port_right.svg | 3 + .../beehave/debug/icons/port_right.svg.import | 38 ++ addons/beehave/debug/icons/port_top.svg | 3 + .../beehave/debug/icons/port_top.svg.import | 38 ++ .../beehave/debug/icons/vertical_layout.svg | 3 + .../debug/icons/vertical_layout.svg.import | 38 ++ addons/beehave/debug/new_frames.gd | 69 ++++ addons/beehave/debug/new_graph_edit.gd | 296 ++++++++++++++++ addons/beehave/debug/new_graph_node.gd | 155 +++++++++ addons/beehave/debug/old_frames.gd | 47 +++ addons/beehave/debug/old_graph_edit.gd | 286 +++++++++++++++ addons/beehave/debug/old_graph_node.gd | 166 +++++++++ addons/beehave/debug/tree_node.gd | 275 +++++++++++++++ addons/beehave/icons/action.svg | 3 + addons/beehave/icons/action.svg.import | 38 ++ addons/beehave/icons/blackboard.svg | 3 + addons/beehave/icons/blackboard.svg.import | 38 ++ addons/beehave/icons/category_bt.svg | 3 + addons/beehave/icons/category_bt.svg.import | 38 ++ addons/beehave/icons/category_composite.svg | 3 + .../icons/category_composite.svg.import | 38 ++ addons/beehave/icons/category_decorator.svg | 3 + .../icons/category_decorator.svg.import | 38 ++ addons/beehave/icons/category_leaf.svg | 3 + addons/beehave/icons/category_leaf.svg.import | 38 ++ addons/beehave/icons/condition.svg | 3 + addons/beehave/icons/condition.svg.import | 38 ++ addons/beehave/icons/cooldown.svg | 3 + addons/beehave/icons/cooldown.svg.import | 37 ++ addons/beehave/icons/delayer.svg | 3 + addons/beehave/icons/delayer.svg.import | 37 ++ addons/beehave/icons/failer.svg | 3 + addons/beehave/icons/failer.svg.import | 38 ++ addons/beehave/icons/inverter.svg | 3 + addons/beehave/icons/inverter.svg.import | 38 ++ addons/beehave/icons/limiter.svg | 3 + addons/beehave/icons/limiter.svg.import | 38 ++ addons/beehave/icons/repeater.svg | 3 + addons/beehave/icons/repeater.svg.import | 37 ++ addons/beehave/icons/selector.svg | 3 + addons/beehave/icons/selector.svg.import | 38 ++ addons/beehave/icons/selector_random.svg | 3 + .../beehave/icons/selector_random.svg.import | 38 ++ addons/beehave/icons/selector_reactive.svg | 3 + .../icons/selector_reactive.svg.import | 38 ++ addons/beehave/icons/sequence.svg | 3 + addons/beehave/icons/sequence.svg.import | 38 ++ addons/beehave/icons/sequence_random.svg | 3 + .../beehave/icons/sequence_random.svg.import | 38 ++ addons/beehave/icons/sequence_reactive.svg | 3 + .../icons/sequence_reactive.svg.import | 38 ++ addons/beehave/icons/simple_parallel.svg | 3 + .../beehave/icons/simple_parallel.svg.import | 37 ++ addons/beehave/icons/succeeder.svg | 3 + addons/beehave/icons/succeeder.svg.import | 38 ++ addons/beehave/icons/tree.svg | 3 + addons/beehave/icons/tree.svg.import | 38 ++ addons/beehave/icons/until_fail.svg | 3 + addons/beehave/icons/until_fail.svg.import | 37 ++ .../beehave/metrics/beehave_global_metrics.gd | 54 +++ addons/beehave/nodes/beehave_node.gd | 46 +++ addons/beehave/nodes/beehave_tree.gd | 329 ++++++++++++++++++ addons/beehave/nodes/composites/composite.gd | 34 ++ .../nodes/composites/randomized_composite.gd | 176 ++++++++++ addons/beehave/nodes/composites/selector.gd | 69 ++++ .../nodes/composites/selector_random.gd | 82 +++++ .../nodes/composites/selector_reactive.gd | 47 +++ addons/beehave/nodes/composites/sequence.gd | 75 ++++ .../nodes/composites/sequence_random.gd | 96 +++++ .../nodes/composites/sequence_reactive.gd | 63 ++++ .../beehave/nodes/composites/sequence_star.gd | 61 ++++ .../nodes/composites/simple_parallel.gd | 122 +++++++ addons/beehave/nodes/decorators/cooldown.gd | 49 +++ addons/beehave/nodes/decorators/decorator.gd | 33 ++ addons/beehave/nodes/decorators/delayer.gd | 49 +++ addons/beehave/nodes/decorators/failer.gd | 35 ++ addons/beehave/nodes/decorators/inverter.gd | 43 +++ addons/beehave/nodes/decorators/limiter.gd | 60 ++++ addons/beehave/nodes/decorators/repeater.gd | 58 +++ addons/beehave/nodes/decorators/succeeder.gd | 35 ++ .../beehave/nodes/decorators/time_limiter.gd | 60 ++++ addons/beehave/nodes/decorators/until_fail.gd | 33 ++ addons/beehave/nodes/leaves/action.gd | 14 + .../nodes/leaves/blackboard_compare.gd | 65 ++++ .../beehave/nodes/leaves/blackboard_erase.gd | 25 ++ addons/beehave/nodes/leaves/blackboard_has.gd | 23 ++ addons/beehave/nodes/leaves/blackboard_set.gd | 33 ++ addons/beehave/nodes/leaves/condition.gd | 12 + addons/beehave/nodes/leaves/leaf.gd | 48 +++ addons/beehave/plugin.cfg | 7 + addons/beehave/plugin.gd | 26 ++ addons/beehave/utils/utils.gd | 21 ++ project.godot | 4 +- script_templates/.gdignore | 0 script_templates/BeehaveNode/default.gd | 7 + src/npc/mech_enemy/mech_enemy.tscn | 144 ++++---- 108 files changed, 4909 insertions(+), 68 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/global_debugger.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/new_frames.gd create mode 100644 addons/beehave/debug/new_graph_edit.gd create mode 100644 addons/beehave/debug/new_graph_node.gd create mode 100644 addons/beehave/debug/old_frames.gd create mode 100644 addons/beehave/debug/old_graph_edit.gd create mode 100644 addons/beehave/debug/old_graph_node.gd 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/cooldown.svg create mode 100644 addons/beehave/icons/cooldown.svg.import create mode 100644 addons/beehave/icons/delayer.svg create mode 100644 addons/beehave/icons/delayer.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/repeater.svg create mode 100644 addons/beehave/icons/repeater.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/simple_parallel.svg create mode 100644 addons/beehave/icons/simple_parallel.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/icons/until_fail.svg create mode 100644 addons/beehave/icons/until_fail.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/composites/simple_parallel.gd create mode 100644 addons/beehave/nodes/decorators/cooldown.gd create mode 100644 addons/beehave/nodes/decorators/decorator.gd create mode 100644 addons/beehave/nodes/decorators/delayer.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/repeater.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/decorators/until_fail.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 create mode 100644 script_templates/.gdignore create mode 100644 script_templates/BeehaveNode/default.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..3948212 --- /dev/null +++ b/addons/beehave/blackboard.gd @@ -0,0 +1,51 @@ +@icon("icons/blackboard.svg") +class_name Blackboard extends Node + +const DEFAULT = "default" + +## The blackboard is an object that can be used to store and access data between +## multiple nodes of the behavior tree. +@export var blackboard: Dictionary = {}: + set(b): + blackboard = b + _data[DEFAULT] = blackboard + +var _data: Dictionary = {} + + +func _ready(): + _data[DEFAULT] = blackboard + + +func keys() -> Array[String]: + var keys: Array[String] + keys.assign(_data.keys().duplicate()) + return keys + + +func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void: + if not _data.has(blackboard_name): + _data[blackboard_name] = {} + + _data[blackboard_name][key] = value + + +func get_value( + key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT +) -> Variant: + if has_value(key, blackboard_name): + return _data[blackboard_name].get(key, default_value) + return default_value + + +func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool: + return ( + _data.has(blackboard_name) + and _data[blackboard_name].has(key) + and _data[blackboard_name][key] != null + ) + + +func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void: + if _data.has(blackboard_name): + _data[blackboard_name][key] = null diff --git a/addons/beehave/debug/debugger.gd b/addons/beehave/debug/debugger.gd new file mode 100644 index 0000000..73bdc50 --- /dev/null +++ b/addons/beehave/debug/debugger.gd @@ -0,0 +1,96 @@ +@tool +extends EditorDebuggerPlugin + +const DebuggerTab := preload("debugger_tab.gd") +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +var debugger_tab := DebuggerTab.new() +var floating_window: Window +var session: EditorDebuggerSession + + +func _has_capture(prefix: String) -> bool: + return prefix == "beehave" + + +func _capture(message: String, data: Array, session_id: int) -> bool: + # in case the behavior tree has invalid setup this might be null + if debugger_tab == null: + return false + + if message == "beehave:register_tree": + debugger_tab.register_tree(data[0]) + return true + if message == "beehave:unregister_tree": + debugger_tab.unregister_tree(data[0]) + return true + if message == "beehave:process_tick": + debugger_tab.graph.process_tick(data[0], data[1]) + return true + if message == "beehave:process_begin": + debugger_tab.graph.process_begin(data[0]) + return true + if message == "beehave:process_end": + debugger_tab.graph.process_end(data[0]) + return true + return false + + +func _setup_session(session_id: int) -> void: + session = get_session(session_id) + session.started.connect(debugger_tab.start) + session.stopped.connect(debugger_tab.stop) + + debugger_tab.name = "🐝 Beehave" + debugger_tab.make_floating.connect(_on_make_floating) + debugger_tab.session = session + session.add_session_tab(debugger_tab) + + +func _on_make_floating() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + if floating_window: + _on_window_close_requested() + return + + var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale() + var editor_interface: EditorInterface = plugin.get_editor_interface() + var editor_main_screen = editor_interface.get_editor_main_screen() + debugger_tab.get_parent().remove_child(debugger_tab) + + floating_window = Window.new() + + var panel := Panel.new() + panel.add_theme_stylebox_override( + "panel", + editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles") + ) + panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + floating_window.add_child(panel) + + var margin := MarginContainer.new() + margin.add_child(debugger_tab) + margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + margin.add_theme_constant_override("margin_right", border_size.x) + margin.add_theme_constant_override("margin_left", border_size.x) + margin.add_theme_constant_override("margin_top", border_size.y) + margin.add_theme_constant_override("margin_bottom", border_size.y) + panel.add_child(margin) + + floating_window.title = "🐝 Beehave" + floating_window.wrap_controls = true + floating_window.min_size = Vector2i(600, 350) + floating_window.size = debugger_tab.size + floating_window.position = editor_main_screen.global_position + floating_window.transient = true + floating_window.close_requested.connect(_on_window_close_requested) + editor_interface.get_base_control().add_child(floating_window) + + +func _on_window_close_requested() -> void: + debugger_tab.get_parent().remove_child(debugger_tab) + session.add_session_tab(debugger_tab) + floating_window.queue_free() + floating_window = null diff --git a/addons/beehave/debug/debugger_messages.gd b/addons/beehave/debug/debugger_messages.gd new file mode 100644 index 0000000..6e0a055 --- /dev/null +++ b/addons/beehave/debug/debugger_messages.gd @@ -0,0 +1,30 @@ +class_name BeehaveDebuggerMessages + + +static func can_send_message() -> bool: + return not Engine.is_editor_hint() and OS.has_feature("editor") + + +static func register_tree(beehave_tree: Dictionary) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:register_tree", [beehave_tree]) + + +static func unregister_tree(instance_id: int) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:unregister_tree", [instance_id]) + + +static func process_tick(instance_id: int, status: int) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_tick", [instance_id, status]) + + +static func process_begin(instance_id: int) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_begin", [instance_id]) + + +static func process_end(instance_id: int) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_end", [instance_id]) diff --git a/addons/beehave/debug/debugger_tab.gd b/addons/beehave/debug/debugger_tab.gd new file mode 100644 index 0000000..758d8b6 --- /dev/null +++ b/addons/beehave/debug/debugger_tab.gd @@ -0,0 +1,125 @@ +@tool +class_name BeehaveDebuggerTab extends PanelContainer + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +signal make_floating + +const OldBeehaveGraphEdit := preload("old_graph_edit.gd") +const NewBeehaveGraphEdit := preload("new_graph_edit.gd") + +const TREE_ICON := preload("../icons/tree.svg") + +var graph +var container: HSplitContainer +var item_list: ItemList +var message: Label + +var active_trees: Dictionary +var active_tree_id: int = -1 +var session: EditorDebuggerSession + + +func _ready() -> void: + container = HSplitContainer.new() + add_child(container) + + item_list = ItemList.new() + item_list.custom_minimum_size = Vector2(200, 0) + item_list.item_selected.connect(_on_item_selected) + container.add_child(item_list) + if Engine.get_version_info().minor >= 2: + graph = NewBeehaveGraphEdit.new(BeehaveUtils.get_frames()) + else: + graph = OldBeehaveGraphEdit.new(BeehaveUtils.get_frames()) + + container.add_child(graph) + + message = Label.new() + message.text = "Run Project for debugging" + message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + message.set_anchors_preset(Control.PRESET_CENTER) + add_child(message) + + var button := Button.new() + button.flat = true + button.name = "MakeFloatingButton" + button.icon = get_theme_icon(&"ExternalLink", &"EditorIcons") + button.pressed.connect(func(): make_floating.emit()) + button.tooltip_text = "Make floating" + button.focus_mode = Control.FOCUS_NONE + graph.get_menu_container().add_child(button) + + var toggle_button := Button.new() + toggle_button.flat = true + toggle_button.name = "TogglePanelButton" + toggle_button.icon = get_theme_icon(&"Back", &"EditorIcons") + toggle_button.pressed.connect(_on_toggle_button_pressed.bind(toggle_button)) + toggle_button.tooltip_text = "Toggle Panel" + toggle_button.focus_mode = Control.FOCUS_NONE + graph.get_menu_container().add_child(toggle_button) + graph.get_menu_container().move_child(toggle_button, 0) + + stop() + visibility_changed.connect(_on_visibility_changed) + + +func start() -> void: + container.visible = true + message.visible = false + + +func stop() -> void: + container.visible = false + message.visible = true + + active_trees.clear() + item_list.clear() + graph.beehave_tree = {} + + +func register_tree(data: Dictionary) -> void: + if not active_trees.has(data.id): + var idx := item_list.add_item(data.name, TREE_ICON) + item_list.set_item_tooltip(idx, data.path) + item_list.set_item_metadata(idx, data.id) + + active_trees[data.id] = data + + if active_tree_id == data.id.to_int(): + graph.beehave_tree = data + + +func unregister_tree(instance_id: int) -> void: + var id := str(instance_id) + for i in item_list.item_count: + if item_list.get_item_metadata(i) == id: + item_list.remove_item(i) + break + + active_trees.erase(id) + + if graph.beehave_tree.get("id", "") == id: + graph.beehave_tree = {} + + +func _on_toggle_button_pressed(toggle_button: Button) -> void: + item_list.visible = !item_list.visible + toggle_button.icon = get_theme_icon( + &"Back" if item_list.visible else &"Forward", &"EditorIcons" + ) + + +func _on_item_selected(idx: int) -> void: + var id: StringName = item_list.get_item_metadata(idx) + graph.beehave_tree = active_trees.get(id, {}) + + active_tree_id = id.to_int() + if session != null: + session.send_message("beehave:activate_tree", [active_tree_id]) + + +func _on_visibility_changed() -> void: + if session != null: + session.send_message("beehave:visibility_changed", [visible and is_visible_in_tree()]) diff --git a/addons/beehave/debug/global_debugger.gd b/addons/beehave/debug/global_debugger.gd new file mode 100644 index 0000000..7350b3e --- /dev/null +++ b/addons/beehave/debug/global_debugger.gd @@ -0,0 +1,38 @@ +extends Node + +var _registered_trees: Dictionary +var _active_tree + + +func _enter_tree() -> void: + EngineDebugger.register_message_capture("beehave", _on_debug_message) + + +func _on_debug_message(message: String, data: Array) -> bool: + if message == "activate_tree": + _set_active_tree(data[0]) + return true + if message == "visibility_changed": + if _active_tree && is_instance_valid(_active_tree): + _active_tree._can_send_message = data[0] + return true + return false + + +func _set_active_tree(tree_id: int) -> void: + var tree = _registered_trees.get(tree_id, null) + if not tree: + return + + if _active_tree && is_instance_valid(_active_tree): + _active_tree._can_send_message = false + _active_tree = tree + _active_tree._can_send_message = true + + +func register_tree(tree) -> void: + _registered_trees[tree.get_instance_id()] = tree + + +func unregister_tree(tree) -> void: + _registered_trees.erase(tree.get_instance_id()) diff --git a/addons/beehave/debug/icons/horizontal_layout.svg b/addons/beehave/debug/icons/horizontal_layout.svg new file mode 100644 index 0000000..2c04b15 --- /dev/null +++ b/addons/beehave/debug/icons/horizontal_layout.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f88cbeac51f2289d28498b77fa8c12662bc847c2cf927b2ea74c7f0005a548d +size 562 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..29f1271 --- /dev/null +++ b/addons/beehave/debug/icons/port_bottom.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34734324864cb37ddf5b8017a96f64b58cc8c71555996b166e46ff7cf12e6495 +size 222 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..4993b85 --- /dev/null +++ b/addons/beehave/debug/icons/port_left.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b82e80bc45ce27aa04e66290a3e38c623bbdefe2f96342ae75810f8ac925494 +size 221 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..cbe92ff --- /dev/null +++ b/addons/beehave/debug/icons/port_right.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:948baad319b14421e8ec1022db331b3a98004f6ead3ee4eb88b0fdf4a132606e +size 231 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..424ba8d --- /dev/null +++ b/addons/beehave/debug/icons/port_top.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aa4a503bbaed4cacd4945413ab1c51a5b38bba706ff6b6f89d8289165b213f4 +size 246 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..befc1aa --- /dev/null +++ b/addons/beehave/debug/icons/vertical_layout.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd1b27dcb2f1800c6357a8510df70664caa8c85e11dcdd158333336cca435c6 +size 528 diff --git a/addons/beehave/debug/icons/vertical_layout.svg.import b/addons/beehave/debug/icons/vertical_layout.svg.import new file mode 100644 index 0000000..8ddcfca --- /dev/null +++ b/addons/beehave/debug/icons/vertical_layout.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpyxu6i1dx5qh" +path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/vertical_layout.svg" +dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=true diff --git a/addons/beehave/debug/new_frames.gd b/addons/beehave/debug/new_frames.gd new file mode 100644 index 0000000..4b739fd --- /dev/null +++ b/addons/beehave/debug/new_frames.gd @@ -0,0 +1,69 @@ +@tool +extends RefCounted + + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + + +const SUCCESS_COLOR := Color("#07783a") +const NORMAL_COLOR := Color("#15181e") +const FAILURE_COLOR := Color("#82010b") +const RUNNING_COLOR := Color("#c29c06") + +var panel_normal: StyleBoxFlat +var panel_success: StyleBoxFlat +var panel_failure: StyleBoxFlat +var panel_running: StyleBoxFlat + +var titlebar_normal: StyleBoxFlat +var titlebar_success: StyleBoxFlat +var titlebar_failure: StyleBoxFlat +var titlebar_running: StyleBoxFlat + + +func _init() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + + + titlebar_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"titlebar", &"GraphNode")\ + .duplicate() + ) + titlebar_success = titlebar_normal.duplicate() + titlebar_failure = titlebar_normal.duplicate() + titlebar_running = titlebar_normal.duplicate() + + titlebar_success.bg_color = SUCCESS_COLOR + titlebar_failure.bg_color = FAILURE_COLOR + titlebar_running.bg_color = RUNNING_COLOR + + titlebar_success.border_color = SUCCESS_COLOR + titlebar_failure.border_color = FAILURE_COLOR + titlebar_running.border_color = RUNNING_COLOR + + + panel_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel", &"GraphNode") + .duplicate() + ) + panel_success = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel_selected", &"GraphNode") + .duplicate() + ) + panel_failure = panel_success.duplicate() + panel_running = panel_success.duplicate() + + panel_success.border_color = SUCCESS_COLOR + panel_failure.border_color = FAILURE_COLOR + panel_running.border_color = RUNNING_COLOR diff --git a/addons/beehave/debug/new_graph_edit.gd b/addons/beehave/debug/new_graph_edit.gd new file mode 100644 index 0000000..71161ab --- /dev/null +++ b/addons/beehave/debug/new_graph_edit.gd @@ -0,0 +1,296 @@ +@tool +extends GraphEdit + +const BeehaveGraphNode := preload("new_graph_node.gd") + +const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") +const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") + +const PROGRESS_SHIFT: int = 50 +const INACTIVE_COLOR: Color = Color("#898989") +const ACTIVE_COLOR: Color = Color("#c29c06") +const SUCCESS_COLOR: Color = Color("#07783a") + + +var updating_graph: bool = false +var arraging_nodes: bool = false +var beehave_tree: Dictionary: + set(value): + if beehave_tree == value: + return + beehave_tree = value + active_nodes.clear() + _update_graph() + +var horizontal_layout: bool = false: + set(value): + if updating_graph or arraging_nodes: + return + if horizontal_layout == value: + return + horizontal_layout = value + _update_layout_button() + _update_graph() + + +var frames:RefCounted +var active_nodes: Array[String] +var progress: int = 0 +var layout_button: Button + + +func _init(frames:RefCounted) -> void: + self.frames = frames + + +func _ready() -> void: + custom_minimum_size = Vector2(100, 300) + set("show_arrange_button", true) + minimap_enabled = false + layout_button = Button.new() + layout_button.flat = true + layout_button.focus_mode = Control.FOCUS_NONE + layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout) + get_menu_container().add_child(layout_button) + _update_layout_button() + + +func _update_graph() -> void: + if updating_graph: + return + + updating_graph = true + + clear_connections() + + for child in _get_child_nodes(): + remove_child(child) + child.queue_free() + + if not beehave_tree.is_empty(): + _add_nodes(beehave_tree) + _connect_nodes(beehave_tree) + _arrange_nodes.call_deferred(beehave_tree) + + updating_graph = false + + +func _add_nodes(node: Dictionary) -> void: + if node.is_empty(): + return + var gnode := BeehaveGraphNode.new(frames, horizontal_layout) + add_child(gnode) + gnode.title_text = node.name + gnode.name = node.id + gnode.icon = _get_icon(node.type.back()) + + if node.type.has(&"BeehaveTree"): + gnode.set_slots(false, true) + elif node.type.has(&"Leaf"): + gnode.set_slots(true, false) + elif node.type.has(&"Composite") or node.type.has(&"Decorator"): + gnode.set_slots(true, true) + + for child in node.get("children", []): + _add_nodes(child) + + +func _connect_nodes(node: Dictionary) -> void: + for child in node.get("children", []): + connect_node(node.id, 0, child.id, 0) + _connect_nodes(child) + + +func _arrange_nodes(node: Dictionary) -> void: + if arraging_nodes: + return + + arraging_nodes = true + + var tree_node := _create_tree_nodes(node) + tree_node.update_positions(horizontal_layout) + _place_nodes(tree_node) + + arraging_nodes = false + + +func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode: + var tree_node := TreeNode.new(get_node(node.id), root) + for child in node.get("children", []): + var child_node := _create_tree_nodes(child, tree_node) + tree_node.children.push_back(child_node) + return tree_node + + +func _place_nodes(node: TreeNode) -> void: + node.item.position_offset = Vector2(node.x, node.y) + for child in node.children: + _place_nodes(child) + + +func _get_icon(type: StringName) -> Texture2D: + var classes := ProjectSettings.get_global_class_list() + for c in classes: + if c["class"] == type: + var icon_path := c.get("icon", String()) + if not icon_path.is_empty(): + return load(icon_path) + return null + + +func get_menu_container() -> Control: + return call("get_menu_hbox") + + +func get_status(status: int) -> String: + if status == 0: + return "SUCCESS" + elif status == 1: + return "FAILURE" + return "RUNNING" + + +func process_begin(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + child.set_meta("status", -1) + + +func process_tick(instance_id: int, status: int) -> void: + var node := get_node_or_null(str(instance_id)) + if node: + node.text = "Status: %s" % get_status(status) + node.set_status(status) + node.set_meta("status", status) + if status == 0 or status == 2: + if not active_nodes.has(node.name): + active_nodes.push_back(node.name) + + +func process_end(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + var status := child.get_meta("status", -1) + match status: + 0: + active_nodes.erase(child.name) + child.set_color(SUCCESS_COLOR) + 1: + active_nodes.erase(child.name) + child.set_color(INACTIVE_COLOR) + 2: + child.set_color(ACTIVE_COLOR) + _: + child.text = " " + child.set_status(status) + child.set_color(INACTIVE_COLOR) + + +func _is_same_tree(instance_id: int) -> bool: + return str(instance_id) == beehave_tree.get("id", "") + + +func _get_child_nodes() -> Array[Node]: + return get_children().filter(func(child): return child is BeehaveGraphNode) + + +func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + for child in _get_child_nodes(): + for port in child.get_input_port_count(): + if not (child.position_offset + child.get_input_port_position(port)).is_equal_approx(to_position): + continue + to_position = child.position_offset + child.get_custom_input_port_position(horizontal_layout) + for port in child.get_output_port_count(): + if not (child.position_offset + child.get_output_port_position(port)).is_equal_approx(from_position): + continue + from_position = child.position_offset + child.get_custom_output_port_position(horizontal_layout) + return _get_elbow_connection_line(from_position, to_position) + + +func _get_elbow_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + var points: PackedVector2Array + + points.push_back(from_position) + + var mid_position := ((to_position + from_position) / 2).round() + if horizontal_layout: + points.push_back(Vector2(mid_position.x, from_position.y)) + points.push_back(Vector2(mid_position.x, to_position.y)) + else: + points.push_back(Vector2(from_position.x, mid_position.y)) + points.push_back(Vector2(to_position.x, mid_position.y)) + + points.push_back(to_position) + + return points + + +func _process(delta: float) -> void: + if not active_nodes.is_empty(): + progress += 10 if delta >= 0.05 else 1 + if progress >= 1000: + progress = 0 + queue_redraw() + + +func _draw() -> void: + if active_nodes.is_empty(): + return + + var circle_size: float = max(3, 6 * zoom) + var progress_shift: float = PROGRESS_SHIFT * zoom + + var connections := get_connection_list() + for c in connections: + var from_node: StringName + var to_node: StringName + + from_node = c.from_node + to_node = c.to_node + + if not from_node in active_nodes or not c.to_node in active_nodes: + continue + + var from := get_node(String(from_node)) + var to := get_node(String(to_node)) + + if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: + return + + var output_port_position: Vector2 + var input_port_position: Vector2 + + var scale_factor: float = from.get_rect().size.x / from.size.x + + var line := _get_elbow_connection_line( + from.position + from.get_custom_output_port_position(horizontal_layout) * scale_factor, + to.position + to.get_custom_input_port_position(horizontal_layout) * scale_factor + ) + + var curve = Curve2D.new() + for l in line: + curve.add_point(l) + + var max_steps := int(curve.get_baked_length()) + var current_shift := progress % max_steps + var p := curve.sample_baked(current_shift) + draw_circle(p, circle_size, ACTIVE_COLOR) + + var shift := current_shift - progress_shift + while shift >= 0: + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift -= progress_shift + + shift = current_shift + progress_shift + while shift <= curve.get_baked_length(): + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift += progress_shift + + +func _update_layout_button() -> void: + layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON + layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" diff --git a/addons/beehave/debug/new_graph_node.gd b/addons/beehave/debug/new_graph_node.gd new file mode 100644 index 0000000..70f06dc --- /dev/null +++ b/addons/beehave/debug/new_graph_node.gd @@ -0,0 +1,155 @@ +@tool +extends GraphNode + + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const PORT_TOP_ICON := preload("icons/port_top.svg") +const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") +const PORT_LEFT_ICON := preload("icons/port_left.svg") +const PORT_RIGHT_ICON := preload("icons/port_right.svg") + + +@export var title_text: String: + set(value): + title_text = value + if title_label: + title_label.text = value + +@export var text: String: + set(value): + text = value + if label: + label.text = " " if text.is_empty() else text + +@export var icon: Texture2D: + set(value): + icon = value + if icon_rect: + icon_rect.texture = value + +var layout_size: float: + get: + return size.y if horizontal else size.x + + +var icon_rect: TextureRect +var title_label: Label +var label: Label +var titlebar_hbox: HBoxContainer + +var frames: RefCounted +var horizontal: bool = false + + +func _init(frames:RefCounted, horizontal: bool = false) -> void: + self.frames = frames + self.horizontal = horizontal + + +func _ready() -> void: + custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() + draggable = false + + add_theme_color_override("close_color", Color.TRANSPARENT) + add_theme_icon_override("close", ImageTexture.new()) + + # For top port + var top_port: Control = Control.new() + add_child(top_port) + + icon_rect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + + titlebar_hbox = get_titlebar_hbox() + titlebar_hbox.get_child(0).queue_free() + titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN + titlebar_hbox.add_child(icon_rect) + + title_label = Label.new() + title_label.add_theme_color_override("font_color", Color.WHITE) + var title_font: Font = get_theme_font("title_font").duplicate() + if title_font is FontVariation: + title_font.variation_embolden = 1 + elif title_font is FontFile: + title_font.font_weight = 700 + title_label.add_theme_font_override("font", title_font) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text = title_text + titlebar_hbox.add_child(title_label) + + label = Label.new() + label.text = " " if text.is_empty() else text + add_child(label) + + # For bottom port + add_child(Control.new()) + + minimum_size_changed.connect(_on_size_changed) + _on_size_changed.call_deferred() + + +func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void: + if horizontal: + if is_slot_enabled_left(1): + draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-4, -5), color) + if is_slot_enabled_right(1): + draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-5, -4.5), color) + else: + if slot_index == 0 and is_slot_enabled_left(0): + draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-4.5, -7), color) + elif slot_index == 1: + draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-4.5, -5), color) + + +func get_custom_input_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(0, size.y / 2) + else: + return Vector2(size.x/2, 0) + + +func get_custom_output_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(size.x, size.y / 2) + else: + return Vector2(size.x / 2, size.y) + + +func set_status(status: int) -> void: + match status: + 0: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success) + 1: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure) + 2: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running) + _: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal) + + +func set_slots(left_enabled: bool, right_enabled: bool) -> void: + if horizontal: + set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON) + else: + set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null) + set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, Color.WHITE, null, PORT_BOTTOM_ICON) + + +func set_color(color: Color) -> void: + set_input_color(color) + set_output_color(color) + + +func set_input_color(color: Color) -> void: + set_slot_color_left(1 if horizontal else 0, color) + + +func set_output_color(color: Color) -> void: + set_slot_color_right(1 if horizontal else 2, color) + + +func _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void: + add_theme_stylebox_override("panel", panel_stylebox) + add_theme_stylebox_override("titlebar", titlebar_stylebox) + + +func _on_size_changed(): + add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x)) diff --git a/addons/beehave/debug/old_frames.gd b/addons/beehave/debug/old_frames.gd new file mode 100644 index 0000000..a9f6aa8 --- /dev/null +++ b/addons/beehave/debug/old_frames.gd @@ -0,0 +1,47 @@ +@tool +extends RefCounted + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const SUCCESS_COLOR := Color("#009944c8") +const NORMAL_COLOR := Color("#15181e") +const FAILURE_COLOR := Color("#cf000f80") +const RUNNING_COLOR := Color("#ffcc00c8") + +var empty: StyleBoxEmpty +var normal: StyleBoxFlat +var success: StyleBoxFlat +var failure: StyleBoxFlat +var running: StyleBoxFlat + + +func _init() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + + var editor_scale := BeehaveUtils.get_editor_scale() + + empty = StyleBoxEmpty.new() + + normal = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"frame", &"GraphNode") + . duplicate() + ) + + success = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"selected_frame", &"GraphNode") + . duplicate() + ) + failure = success.duplicate() + running = success.duplicate() + + success.border_color = SUCCESS_COLOR + failure.border_color = FAILURE_COLOR + running.border_color = RUNNING_COLOR diff --git a/addons/beehave/debug/old_graph_edit.gd b/addons/beehave/debug/old_graph_edit.gd new file mode 100644 index 0000000..bae64c8 --- /dev/null +++ b/addons/beehave/debug/old_graph_edit.gd @@ -0,0 +1,286 @@ +@tool +extends GraphEdit + +const BeehaveGraphNode := preload("old_graph_node.gd") + +const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") +const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") + +const PROGRESS_SHIFT: int = 50 +const INACTIVE_COLOR: Color = Color("#898989aa") +const ACTIVE_COLOR: Color = Color("#ffcc00c8") +const SUCCESS_COLOR: Color = Color("#009944c8") + +var updating_graph: bool = false +var arraging_nodes: bool = false +var beehave_tree: Dictionary: + set(value): + if beehave_tree == value: + return + beehave_tree = value + active_nodes.clear() + _update_graph() + +var horizontal_layout: bool = false: + set(value): + if updating_graph or arraging_nodes: + return + if horizontal_layout == value: + return + horizontal_layout = value + _update_layout_button() + _update_graph() + +var frames: RefCounted +var active_nodes: Array[String] +var progress: int = 0 +var layout_button: Button + + +func _init(frames: RefCounted) -> void: + self.frames = frames + + +func _ready() -> void: + custom_minimum_size = Vector2(100, 300) + set("arrange_nodes_button_hidden", true) + minimap_enabled = false + layout_button = Button.new() + layout_button.flat = true + layout_button.focus_mode = Control.FOCUS_NONE + layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout) + get_menu_container().add_child(layout_button) + _update_layout_button() + + +func _update_graph() -> void: + if updating_graph: + return + + updating_graph = true + + clear_connections() + + for child in _get_child_nodes(): + remove_child(child) + child.queue_free() + + if not beehave_tree.is_empty(): + _add_nodes(beehave_tree) + _connect_nodes(beehave_tree) + _arrange_nodes.call_deferred(beehave_tree) + + updating_graph = false + + +func _add_nodes(node: Dictionary) -> void: + if node.is_empty(): + return + var gnode := BeehaveGraphNode.new(frames, horizontal_layout) + add_child(gnode) + gnode.title_text = node.name + gnode.name = node.id + gnode.icon = _get_icon(node.type.back()) + + if node.type.has(&"BeehaveTree"): + gnode.set_slots(false, true) + elif node.type.has(&"Leaf"): + gnode.set_slots(true, false) + elif node.type.has(&"Composite") or node.type.has(&"Decorator"): + gnode.set_slots(true, true) + + for child in node.get("children", []): + _add_nodes(child) + + +func _connect_nodes(node: Dictionary) -> void: + for child in node.get("children", []): + connect_node(node.id, 0, child.id, 0) + _connect_nodes(child) + + +func _arrange_nodes(node: Dictionary) -> void: + if arraging_nodes: + return + + arraging_nodes = true + + var tree_node := _create_tree_nodes(node) + tree_node.update_positions(horizontal_layout) + _place_nodes(tree_node) + + arraging_nodes = false + + +func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode: + var tree_node := TreeNode.new(get_node(node.id), root) + for child in node.get("children", []): + var child_node := _create_tree_nodes(child, tree_node) + tree_node.children.push_back(child_node) + return tree_node + + +func _place_nodes(node: TreeNode) -> void: + node.item.position_offset = Vector2(node.x, node.y) + for child in node.children: + _place_nodes(child) + + +func _get_icon(type: StringName) -> Texture2D: + var classes := ProjectSettings.get_global_class_list() + for c in classes: + if c["class"] == type: + var icon_path := c.get("icon", String()) + if not icon_path.is_empty(): + return load(icon_path) + return null + + +func get_menu_container() -> Control: + return call("get_zoom_hbox") + + +func get_status(status: int) -> String: + if status == 0: + return "SUCCESS" + elif status == 1: + return "FAILURE" + return "RUNNING" + + +func process_begin(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + child.set_meta("status", -1) + + +func process_tick(instance_id: int, status: int) -> void: + var node := get_node_or_null(str(instance_id)) + if node: + node.text = "Status: %s" % get_status(status) + node.set_status(status) + node.set_meta("status", status) + if status == 0 or status == 2: + if not active_nodes.has(node.name): + active_nodes.push_back(node.name) + + +func process_end(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + var status := child.get_meta("status", -1) + match status: + 0: + active_nodes.erase(child.name) + child.set_color(SUCCESS_COLOR) + 1: + active_nodes.erase(child.name) + child.set_color(INACTIVE_COLOR) + 2: + child.set_color(ACTIVE_COLOR) + _: + child.text = " " + child.set_status(status) + child.set_color(INACTIVE_COLOR) + + +func _is_same_tree(instance_id: int) -> bool: + return str(instance_id) == beehave_tree.get("id", "") + + +func _get_child_nodes() -> Array[Node]: + return get_children().filter(func(child): return child is BeehaveGraphNode) + + +func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + var points: PackedVector2Array + + from_position = from_position.round() + to_position = to_position.round() + + points.push_back(from_position) + + var mid_position := ((to_position + from_position) / 2).round() + if horizontal_layout: + points.push_back(Vector2(mid_position.x, from_position.y)) + points.push_back(Vector2(mid_position.x, to_position.y)) + else: + points.push_back(Vector2(from_position.x, mid_position.y)) + points.push_back(Vector2(to_position.x, mid_position.y)) + + points.push_back(to_position) + + return points + + +func _process(delta: float) -> void: + if not active_nodes.is_empty(): + progress += 10 if delta >= 0.05 else 1 + if progress >= 1000: + progress = 0 + queue_redraw() + + +func _draw() -> void: + if active_nodes.is_empty(): + return + + var circle_size: float = max(3, 6 * zoom) + var progress_shift: float = PROGRESS_SHIFT * zoom + + var connections := get_connection_list() + for c in connections: + var from_node: StringName + var to_node: StringName + + from_node = c.from + to_node = c.to + + if not from_node in active_nodes or not c.to_node in active_nodes: + continue + + var from := get_node(String(from_node)) + var to := get_node(String(to_node)) + + if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: + return + + var output_port_position: Vector2 + var input_port_position: Vector2 + + output_port_position = ( + from.position + from.call("get_connection_output_position", c.from_port) + ) + input_port_position = to.position + to.call("get_connection_input_position", c.to_port) + + var line := _get_connection_line(output_port_position, input_port_position) + + var curve = Curve2D.new() + for l in line: + curve.add_point(l) + + var max_steps := int(curve.get_baked_length()) + var current_shift := progress % max_steps + var p := curve.sample_baked(current_shift) + draw_circle(p, circle_size, ACTIVE_COLOR) + + var shift := current_shift - progress_shift + while shift >= 0: + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift -= progress_shift + + shift = current_shift + progress_shift + while shift <= curve.get_baked_length(): + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift += progress_shift + + +func _update_layout_button() -> void: + layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON + layout_button.tooltip_text = ( + "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" + ) diff --git a/addons/beehave/debug/old_graph_node.gd b/addons/beehave/debug/old_graph_node.gd new file mode 100644 index 0000000..a0a978b --- /dev/null +++ b/addons/beehave/debug/old_graph_node.gd @@ -0,0 +1,166 @@ +@tool +extends GraphNode + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const DEFAULT_COLOR := Color("#dad4cb") + +const PORT_TOP_ICON := preload("icons/port_top.svg") +const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") +const PORT_LEFT_ICON := preload("icons/port_left.svg") +const PORT_RIGHT_ICON := preload("icons/port_right.svg") + +@export var title_text: String: + set(value): + title_text = value + if title_label: + title_label.text = value + +@export var text: String: + set(value): + text = value + if label: + label.text = " " if text.is_empty() else text + +@export var icon: Texture2D: + set(value): + icon = value + if icon_rect: + icon_rect.texture = value + +var layout_size: float: + get: + return size.y if horizontal else size.x + +var panel: PanelContainer +var icon_rect: TextureRect +var title_label: Label +var container: VBoxContainer +var label: Label + +var frames: RefCounted +var horizontal: bool = false + + +func _init(frames: RefCounted, horizontal: bool = false) -> void: + self.frames = frames + self.horizontal = horizontal + + +func _ready() -> void: + custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() + draggable = false + + add_theme_stylebox_override("frame", frames.empty if frames != null else null) + add_theme_stylebox_override("selected_frame", frames.empty if frames != null else null) + add_theme_color_override("close_color", Color.TRANSPARENT) + add_theme_icon_override("close", ImageTexture.new()) + + # For top port + add_child(Control.new()) + + panel = PanelContainer.new() + panel.mouse_filter = Control.MOUSE_FILTER_PASS + panel.add_theme_stylebox_override("panel", frames.normal if frames != null else null) + add_child(panel) + + var vbox_container := VBoxContainer.new() + panel.add_child(vbox_container) + + var title_size := 24 * BeehaveUtils.get_editor_scale() + var margin_container := MarginContainer.new() + margin_container.add_theme_constant_override( + "margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale() + ) + margin_container.mouse_filter = Control.MOUSE_FILTER_PASS + vbox_container.add_child(margin_container) + + var title_container := HBoxContainer.new() + title_container.add_child(Control.new()) + title_container.mouse_filter = Control.MOUSE_FILTER_PASS + title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + margin_container.add_child(title_container) + + icon_rect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + title_container.add_child(icon_rect) + + title_label = Label.new() + title_label.add_theme_color_override("font_color", DEFAULT_COLOR) + title_label.add_theme_font_override("font", get_theme_font("title_font")) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text = title_text + title_container.add_child(title_label) + + title_container.add_child(Control.new()) + + container = VBoxContainer.new() + container.size_flags_vertical = Control.SIZE_EXPAND_FILL + container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + panel.add_child(container) + + label = Label.new() + label.text = " " if text.is_empty() else text + container.add_child(label) + + # For bottom port + add_child(Control.new()) + + minimum_size_changed.connect(_on_size_changed) + _on_size_changed.call_deferred() + + +func set_status(status: int) -> void: + panel.add_theme_stylebox_override("panel", _get_stylebox(status)) + + +func _get_stylebox(status: int) -> StyleBox: + match status: + 0: + return frames.success + 1: + return frames.failure + 2: + return frames.running + _: + return frames.normal + + +func set_slots(left_enabled: bool, right_enabled: bool) -> void: + if horizontal: + set_slot( + 1, + left_enabled, + 0, + Color.WHITE, + right_enabled, + 0, + Color.WHITE, + PORT_LEFT_ICON, + PORT_RIGHT_ICON + ) + else: + set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null) + set_slot( + 2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON + ) + + +func set_color(color: Color) -> void: + set_input_color(color) + set_output_color(color) + + +func set_input_color(color: Color) -> void: + set_slot_color_left(1 if horizontal else 0, color) + + +func set_output_color(color: Color) -> void: + set_slot_color_right(1 if horizontal else 2, color) + + +func _on_size_changed(): + add_theme_constant_override( + "port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0) + ) diff --git a/addons/beehave/debug/tree_node.gd b/addons/beehave/debug/tree_node.gd new file mode 100644 index 0000000..1377970 --- /dev/null +++ b/addons/beehave/debug/tree_node.gd @@ -0,0 +1,275 @@ +class_name TreeNode +extends RefCounted + +# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/ + +const SIBLING_DISTANCE: float = 20.0 +const LEVEL_DISTANCE: float = 40.0 + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +var x: float +var y: float +var mod: float +var parent: TreeNode +var children: Array[TreeNode] + +var item: GraphNode + + +func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void: + parent = p_parent + item = p_item + + +func is_leaf() -> bool: + return children.is_empty() + + +func is_most_left() -> bool: + if not parent: + return true + return parent.children.front() == self + + +func is_most_right() -> bool: + if not parent: + return true + return parent.children.back() == self + + +func get_previous_sibling() -> TreeNode: + if not parent or is_most_left(): + return null + return parent.children[parent.children.find(self) - 1] + + +func get_next_sibling() -> TreeNode: + if not parent or is_most_right(): + return null + return parent.children[parent.children.find(self) + 1] + + +func get_most_left_sibling() -> TreeNode: + if not parent: + return null + + if is_most_left(): + return self + + return parent.children.front() + + +func get_most_left_child() -> TreeNode: + if children.is_empty(): + return null + return children.front() + + +func get_most_right_child() -> TreeNode: + if children.is_empty(): + return null + return children.back() + + +func update_positions(horizontally: bool = false) -> void: + _initialize_nodes(self, 0) + _calculate_initial_x(self) + + _check_all_children_on_screen(self) + _calculate_final_positions(self, 0) + + if horizontally: + _swap_x_y(self) + _calculate_x(self, 0) + else: + _calculate_y(self, 0) + + +func _initialize_nodes(node: TreeNode, depth: int) -> void: + node.x = -1 + node.y = depth + node.mod = 0 + + for child in node.children: + _initialize_nodes(child, depth + 1) + + +func _calculate_initial_x(node: TreeNode) -> void: + for child in node.children: + _calculate_initial_x(child) + if node.is_leaf(): + if not node.is_most_left(): + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) + else: + node.x = 0 + else: + var mid: float + if node.children.size() == 1: + var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2 + mid = node.children.front().x + offset + else: + var left_child := node.get_most_left_child() + var right_child := node.get_most_right_child() + mid = ( + ( + left_child.x + + right_child.x + + right_child.item.layout_size + - node.item.layout_size + ) + / 2 + ) + + if node.is_most_left(): + node.x = mid + else: + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) + node.mod = node.x - mid + + if not node.is_leaf() and not node.is_most_left(): + _check_for_conflicts(node) + + +func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void: + node.x += mod_sum + mod_sum += node.mod + + for child in node.children: + _calculate_final_positions(child, mod_sum) + + +func _check_all_children_on_screen(node: TreeNode) -> void: + var node_contour: Dictionary = {} + _get_left_contour(node, 0, node_contour) + + var shift_amount: float = 0 + for y in node_contour.keys(): + if node_contour[y] + shift_amount < 0: + shift_amount = (node_contour[y] * -1) + + if shift_amount > 0: + node.x += shift_amount + node.mod += shift_amount + + +func _check_for_conflicts(node: TreeNode) -> void: + var min_distance := SIBLING_DISTANCE + var shift_value: float = 0 + var shift_sibling: TreeNode = null + + var node_contour: Dictionary = {} # { int, float } + _get_left_contour(node, 0, node_contour) + + var sibling := node.get_most_left_sibling() + while sibling != null and sibling != node: + var sibling_contour: Dictionary = {} + _get_right_contour(sibling, 0, sibling_contour) + + for level in range( + node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1 + ): + var distance: float = node_contour[level] - sibling_contour[level] + if distance + shift_value < min_distance: + shift_value = min_distance - distance + shift_sibling = sibling + + sibling = sibling.get_next_sibling() + + if shift_value > 0: + node.x += shift_value + node.mod += shift_value + _center_nodes_between(shift_sibling, node) + + +func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void: + var left_index := left_node.parent.children.find(left_node) + var right_index := left_node.parent.children.find(right_node) + + var num_nodes_between: int = (right_index - left_index) - 1 + if num_nodes_between > 0: + # The extra distance that needs to be split into num_nodes_between + 1 + # in order to find the new node spacing so that nodes are equally spaced + var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size + # Subtract sizes on nodes in between + for i in range(left_index + 1, right_index): + distance_to_allocate -= left_node.parent.children[i].item.layout_size + # Divide space equally + var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1) + + var prev_node := left_node + var middle_node := left_node.get_next_sibling() + while middle_node != right_node: + var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes + var offset := desire_x - middle_node.x + middle_node.x += offset + middle_node.mod += offset + prev_node = middle_node + middle_node = middle_node.get_next_sibling() + + +func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void: + var node_left: float = node.x + mod_sum + var depth := int(node.y) + if not values.has(depth): + values[depth] = node_left + else: + values[depth] = min(values[depth], node_left) + + mod_sum += node.mod + for child in node.children: + _get_left_contour(child, mod_sum, values) + + +func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void: + var node_right: float = node.x + mod_sum + node.item.layout_size + var depth := int(node.y) + if not values.has(depth): + values[depth] = node_right + else: + values[depth] = max(values[depth], node_right) + + mod_sum += node.mod + for child in node.children: + _get_right_contour(child, mod_sum, values) + + +func _swap_x_y(node: TreeNode) -> void: + for child in node.children: + _swap_x_y(child) + + var temp := node.x + node.x = node.y + node.y = temp + + +func _calculate_x(node: TreeNode, offset: int) -> void: + node.x = offset + var sibling := node.get_most_left_sibling() + var max_size: int = node.item.size.x + while sibling != null: + max_size = max(sibling.item.size.x, max_size) + sibling = sibling.get_next_sibling() + + for child in node.children: + _calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale()) + + +func _calculate_y(node: TreeNode, offset: int) -> void: + node.y = offset + var sibling := node.get_most_left_sibling() + var max_size: int = node.item.size.y + while sibling != null: + max_size = max(sibling.item.size.y, max_size) + sibling = sibling.get_next_sibling() + + for child in node.children: + _calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale()) diff --git a/addons/beehave/icons/action.svg b/addons/beehave/icons/action.svg new file mode 100644 index 0000000..cbaf5f0 --- /dev/null +++ b/addons/beehave/icons/action.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba828aac24fb0b0b3628584a28ab23884bd901d918f1bd752c5994f8f2c85ef7 +size 2972 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..9fb7f1e --- /dev/null +++ b/addons/beehave/icons/blackboard.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7da2bb26b844fd0019291a19d546ade341a7da1165241806d2ade658f093bf17 +size 1477 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..13c4e56 --- /dev/null +++ b/addons/beehave/icons/category_bt.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac31256014a0ba715f1da4f6f3c8043028704c624339523641b66e512d3ee7cc +size 3920 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..321d87e --- /dev/null +++ b/addons/beehave/icons/category_composite.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bd205e2c50fb60ef83b6623f7e517318518ed8c6630d8d6046b4c93362ce34b +size 3927 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..8de9d0c --- /dev/null +++ b/addons/beehave/icons/category_decorator.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69e38791a3e806e91d8d13b62257580690fff2746826dac9ec85b5af8f25e5ee +size 3927 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..d8b17d7 --- /dev/null +++ b/addons/beehave/icons/category_leaf.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47036e92cfc0dc0763317436a9b479c30d19535b733372ac7f35d1299d4b19c4 +size 3922 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..89ad889 --- /dev/null +++ b/addons/beehave/icons/condition.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b74361149c872841dea828d22df454c18a72cbaeffe349a7c4b9a557102c5825 +size 2282 diff --git a/addons/beehave/icons/condition.svg.import b/addons/beehave/icons/condition.svg.import new file mode 100644 index 0000000..ef59099 --- /dev/null +++ b/addons/beehave/icons/condition.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ck4toqx0nggiu" +path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/condition.svg" +dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=true diff --git a/addons/beehave/icons/cooldown.svg b/addons/beehave/icons/cooldown.svg new file mode 100644 index 0000000..10b4bdb --- /dev/null +++ b/addons/beehave/icons/cooldown.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7204c79cd3cf76bb8c356ba911dea61bfda58f28f7de618f5973dee5235b2c9 +size 1439 diff --git a/addons/beehave/icons/cooldown.svg.import b/addons/beehave/icons/cooldown.svg.import new file mode 100644 index 0000000..603fb16 --- /dev/null +++ b/addons/beehave/icons/cooldown.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cb1g6e3mmj40d" +path="res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/cooldown.svg" +dest_files=["res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/beehave/icons/delayer.svg b/addons/beehave/icons/delayer.svg new file mode 100644 index 0000000..4f70ae1 --- /dev/null +++ b/addons/beehave/icons/delayer.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:732253465a4bec7824e0d4d98c627c2228e1c61d0b9e3689b3e17e438ce8a3e4 +size 1565 diff --git a/addons/beehave/icons/delayer.svg.import b/addons/beehave/icons/delayer.svg.import new file mode 100644 index 0000000..6a38d4b --- /dev/null +++ b/addons/beehave/icons/delayer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btc5ynpdytj7j" +path="res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/delayer.svg" +dest_files=["res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/beehave/icons/failer.svg b/addons/beehave/icons/failer.svg new file mode 100644 index 0000000..658a171 --- /dev/null +++ b/addons/beehave/icons/failer.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b2b591009bd8606a887d39377643fd4d90ae5eedf02e22ff4986e13d9250d65 +size 1927 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..715b57b --- /dev/null +++ b/addons/beehave/icons/inverter.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5adff50982ae32decfe2373ef279341a58ba4086b5e999da942172f3415e132 +size 2024 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..8b80bc8 --- /dev/null +++ b/addons/beehave/icons/limiter.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:030e389bb787f9d9f659ae2794b00472e330f544cf5da40bd86a14e8afb35a47 +size 1866 diff --git a/addons/beehave/icons/limiter.svg.import b/addons/beehave/icons/limiter.svg.import new file mode 100644 index 0000000..7b56b08 --- /dev/null +++ b/addons/beehave/icons/limiter.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7akxvsg0f2by" +path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/limiter.svg" +dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=true diff --git a/addons/beehave/icons/repeater.svg b/addons/beehave/icons/repeater.svg new file mode 100644 index 0000000..4300772 --- /dev/null +++ b/addons/beehave/icons/repeater.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ce5054afeae4d9d85466b24353a58e0f13b5980a67cdf7c2b1f6cde3eb54d94 +size 2848 diff --git a/addons/beehave/icons/repeater.svg.import b/addons/beehave/icons/repeater.svg.import new file mode 100644 index 0000000..1f86363 --- /dev/null +++ b/addons/beehave/icons/repeater.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxxmf535lln2p" +path="res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/repeater.svg" +dest_files=["res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/beehave/icons/selector.svg b/addons/beehave/icons/selector.svg new file mode 100644 index 0000000..c92b30d --- /dev/null +++ b/addons/beehave/icons/selector.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fbcae98a9d420653e1b34a0824ffec3fa7829bedaede81722969e812a08ee88 +size 2257 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..c80118b --- /dev/null +++ b/addons/beehave/icons/selector_random.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf616b9c598f4da62177f882c4cb614681b08972d13620092a7a3865b1594448 +size 7514 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..60faefd --- /dev/null +++ b/addons/beehave/icons/selector_reactive.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:926cd7c08bdeb393821948f4620114f482d055ef93bd3a9c5c85a24fb7136d05 +size 3653 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..021df86 --- /dev/null +++ b/addons/beehave/icons/sequence.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ca1888d2d9ffced57bbef7ee8103ab348b84eeecc39f4dbabd2c3330d5c70da +size 2235 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..96fe72a --- /dev/null +++ b/addons/beehave/icons/sequence_random.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a9da1216c7abcd0aecc3d316a10831ed833b551c41532975563ac9a1811d0bf +size 4822 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..9d95c8c --- /dev/null +++ b/addons/beehave/icons/sequence_reactive.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dba15203938983dc90e4e42cb5bdbd42b702b23526e68d03900798d14fddf21 +size 6352 diff --git a/addons/beehave/icons/sequence_reactive.svg.import b/addons/beehave/icons/sequence_reactive.svg.import new file mode 100644 index 0000000..ab0fa25 --- /dev/null +++ b/addons/beehave/icons/sequence_reactive.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rmiu1slwfkh7" +path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/sequence_reactive.svg" +dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=true diff --git a/addons/beehave/icons/simple_parallel.svg b/addons/beehave/icons/simple_parallel.svg new file mode 100644 index 0000000..79121f7 --- /dev/null +++ b/addons/beehave/icons/simple_parallel.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdc6cfecdc74b1620dff1ad46f1267e325c2e80215f730080bf67ad7009a9bc7 +size 2826 diff --git a/addons/beehave/icons/simple_parallel.svg.import b/addons/beehave/icons/simple_parallel.svg.import new file mode 100644 index 0000000..aac28d2 --- /dev/null +++ b/addons/beehave/icons/simple_parallel.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvinjswsagdbc" +path="res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/simple_parallel.svg" +dest_files=["res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/beehave/icons/succeeder.svg b/addons/beehave/icons/succeeder.svg new file mode 100644 index 0000000..981af9d --- /dev/null +++ b/addons/beehave/icons/succeeder.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54aaa663ad746c39bfdb9e6ab041ec4081026466d053dabefb14ca22fb6aa2c8 +size 1800 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..556a06f --- /dev/null +++ b/addons/beehave/icons/tree.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16734d9875af0369285e0325a26f86850e2ddd1ffece3d609abc9cd10b1897ba +size 1882 diff --git a/addons/beehave/icons/tree.svg.import b/addons/beehave/icons/tree.svg.import new file mode 100644 index 0000000..9ac0308 --- /dev/null +++ b/addons/beehave/icons/tree.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://deryyg2hbmaaw" +path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/tree.svg" +dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=true +editor/convert_colors_with_editor_theme=true diff --git a/addons/beehave/icons/until_fail.svg b/addons/beehave/icons/until_fail.svg new file mode 100644 index 0000000..a76f702 --- /dev/null +++ b/addons/beehave/icons/until_fail.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2207a4fb6c9042ea075b2b03927f9b99ef761119e99fcc7189089bd35e190db4 +size 5142 diff --git a/addons/beehave/icons/until_fail.svg.import b/addons/beehave/icons/until_fail.svg.import new file mode 100644 index 0000000..9da368e --- /dev/null +++ b/addons/beehave/icons/until_fail.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5b63h80o8din" +path="res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/until_fail.svg" +dest_files=["res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/beehave/metrics/beehave_global_metrics.gd b/addons/beehave/metrics/beehave_global_metrics.gd new file mode 100644 index 0000000..ef29db2 --- /dev/null +++ b/addons/beehave/metrics/beehave_global_metrics.gd @@ -0,0 +1,54 @@ +extends Node + +var _tree_count: int = 0 +var _active_tree_count: int = 0 +var _registered_trees: Array = [] + + +func _enter_tree() -> void: + Performance.add_custom_monitor("beehave/total_trees", _get_total_trees) + Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees) + + +func register_tree(tree) -> void: + if _registered_trees.has(tree): + return + + _registered_trees.append(tree) + _tree_count += 1 + + if tree.enabled: + _active_tree_count += 1 + + tree.tree_enabled.connect(_on_tree_enabled) + tree.tree_disabled.connect(_on_tree_disabled) + + +func unregister_tree(tree) -> void: + if not _registered_trees.has(tree): + return + + _registered_trees.erase(tree) + _tree_count -= 1 + + if tree.enabled: + _active_tree_count -= 1 + + tree.tree_enabled.disconnect(_on_tree_enabled) + tree.tree_disabled.disconnect(_on_tree_disabled) + + +func _get_total_trees() -> int: + return _tree_count + + +func _get_total_enabled_trees() -> int: + return _active_tree_count + + +func _on_tree_enabled() -> void: + _active_tree_count += 1 + + +func _on_tree_disabled() -> void: + _active_tree_count -= 1 diff --git a/addons/beehave/nodes/beehave_node.gd b/addons/beehave/nodes/beehave_node.gd new file mode 100644 index 0000000..9ab8d6e --- /dev/null +++ b/addons/beehave/nodes/beehave_node.gd @@ -0,0 +1,46 @@ +@tool +class_name BeehaveNode extends Node + +## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or +## `RUNNING` when ticked. + +enum { SUCCESS, FAILURE, RUNNING } + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + if get_children().any(func(x): return not (x is BeehaveNode)): + warnings.append("All children of this node should inherit from BeehaveNode class.") + + return warnings + + +## Executes this node and returns a status code. +## This method must be overwritten. +func tick(actor: Node, blackboard: Blackboard) -> int: + return SUCCESS + + +## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS. +func interrupt(actor: Node, blackboard: Blackboard) -> void: + pass + + +## Called before the first time it ticks by the parent. +func before_run(actor: Node, blackboard: Blackboard) -> void: + pass + + +## Called after the last time it ticks and returns +## [code]SUCCESS[/code] or [code]FAILURE[/code]. +func after_run(actor: Node, blackboard: Blackboard) -> void: + pass + + +func get_class_name() -> Array[StringName]: + return [&"BeehaveNode"] + + +func can_send_message(blackboard: Blackboard) -> bool: + return blackboard.get_value("can_send_message", false) diff --git a/addons/beehave/nodes/beehave_tree.gd b/addons/beehave/nodes/beehave_tree.gd new file mode 100644 index 0000000..e27edbd --- /dev/null +++ b/addons/beehave/nodes/beehave_tree.gd @@ -0,0 +1,329 @@ +@tool +@icon("../icons/tree.svg") +class_name BeehaveTree extends Node + +## Controls the flow of execution of the entire behavior tree. + +enum { SUCCESS, FAILURE, RUNNING } + +enum ProcessThread { IDLE, PHYSICS } + +signal tree_enabled +signal tree_disabled + +## Whether this behavior tree should be enabled or not. +@export var enabled: bool = true: + set(value): + enabled = value + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + if value: + tree_enabled.emit() + else: + interrupt() + tree_disabled.emit() + + get: + return enabled + +## How often the tree should tick, in frames. The default value of 1 means +## tick() runs every frame. +@export var tick_rate: int = 1 + +## An optional node path this behavior tree should apply to. +@export_node_path var actor_node_path: NodePath: + set(anp): + actor_node_path = anp + if actor_node_path != null and str(actor_node_path) != "..": + actor = get_node(actor_node_path) + else: + actor = get_parent() + if Engine.is_editor_hint(): + update_configuration_warnings() + +## Whether to run this tree in a physics or idle thread. +@export var process_thread: ProcessThread = ProcessThread.PHYSICS: + set(value): + process_thread = value + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + +## Custom blackboard node. An internal blackboard will be used +## if no blackboard is provided explicitly. +@export var blackboard: Blackboard: + set(b): + blackboard = b + if blackboard and _internal_blackboard: + remove_child(_internal_blackboard) + _internal_blackboard.free() + _internal_blackboard = null + elif not blackboard and not _internal_blackboard: + _internal_blackboard = Blackboard.new() + add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK) + get: + # in case blackboard is accessed before this node is, + # we need to ensure that the internal blackboard is used. + if not blackboard and not _internal_blackboard: + _internal_blackboard = Blackboard.new() + add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK) + return blackboard if blackboard else _internal_blackboard + +## When enabled, this tree is tracked individually +## as a custom monitor. +@export var custom_monitor = false: + set(b): + custom_monitor = b + if custom_monitor and _process_time_metric_name != "": + Performance.add_custom_monitor( + _process_time_metric_name, _get_process_time_metric_value + ) + _get_global_metrics().register_tree(self) + else: + if _process_time_metric_name != "": + # Remove tree metric from the engine + Performance.remove_custom_monitor(_process_time_metric_name) + _get_global_metrics().unregister_tree(self) + + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + +@export var actor: Node: + set(a): + actor = a + if actor == null: + actor = get_parent() + if Engine.is_editor_hint(): + update_configuration_warnings() + +var status: int = -1 +var last_tick: int = 0 + +var _internal_blackboard: Blackboard +var _process_time_metric_name: String +var _process_time_metric_value: float = 0.0 +var _can_send_message: bool = false + + +func _ready() -> void: + var connect_scene_tree_signal = func(signal_name: String, is_added: bool): + if not get_tree().is_connected(signal_name, _on_scene_tree_node_added_removed.bind(is_added)): + get_tree().connect(signal_name, _on_scene_tree_node_added_removed.bind(is_added)) + connect_scene_tree_signal.call("node_added", true) + connect_scene_tree_signal.call("node_removed", false) + + if not process_thread: + process_thread = ProcessThread.PHYSICS + + if not actor: + if actor_node_path: + actor = get_node(actor_node_path) + else: + actor = get_parent() + + if not blackboard: + # invoke setter to auto-initialise the blackboard. + self.blackboard = null + + # Get the name of the parent node name for metric + _process_time_metric_name = ( + "beehave [microseconds]/process_time_%s-%s" % [actor.name, get_instance_id()] + ) + + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + + # Register custom metric to the engine + if custom_monitor and not Engine.is_editor_hint(): + Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value) + _get_global_metrics().register_tree(self) + + if Engine.is_editor_hint(): + update_configuration_warnings.call_deferred() + else: + _get_global_debugger().register_tree(self) + BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)) + + # Randomize at what frames tick() will happen to avoid stutters + last_tick = randi_range(0, tick_rate - 1) + + +func _on_scene_tree_node_added_removed(node: Node, is_added: bool) -> void: + if Engine.is_editor_hint(): + return + + if node is BeehaveNode and is_ancestor_of(node): + var sgnal := node.ready if is_added else node.tree_exited + if is_added: + sgnal.connect( + func() -> void: BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)), + CONNECT_ONE_SHOT + ) + else: + sgnal.connect( + func() -> void: + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + request_ready() + ) + + +func _physics_process(_delta: float) -> void: + _process_internally() + + +func _process(_delta: float) -> void: + _process_internally() + + +func _process_internally() -> void: + if Engine.is_editor_hint(): + return + + if last_tick < tick_rate - 1: + last_tick += 1 + return + + last_tick = 0 + + # Start timing for metric + var start_time = Time.get_ticks_usec() + + blackboard.set_value("can_send_message", _can_send_message) + + if _can_send_message: + BeehaveDebuggerMessages.process_begin(get_instance_id()) + + if self.get_child_count() == 1: + tick() + + if _can_send_message: + BeehaveDebuggerMessages.process_end(get_instance_id()) + + # Check the cost for this frame and save it for metric report + _process_time_metric_value = Time.get_ticks_usec() - start_time + + +func tick() -> int: + if actor == null or get_child_count() == 0: + return FAILURE + var child := self.get_child(0) + if status != RUNNING: + child.before_run(actor, blackboard) + + status = child.tick(actor, blackboard) + if _can_send_message: + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status) + BeehaveDebuggerMessages.process_tick(get_instance_id(), status) + + # Clear running action if nothing is running + if status != RUNNING: + blackboard.set_value("running_action", null, str(actor.get_instance_id())) + child.after_run(actor, blackboard) + + return status + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + if actor == null: + warnings.append("Configure target node on tree") + + if get_children().any(func(x): return not (x is BeehaveNode)): + warnings.append("All children of this node should inherit from BeehaveNode class.") + + if get_child_count() != 1: + warnings.append("BeehaveTree should have exactly one child node.") + + return warnings + + +## Returns the currently running action +func get_running_action() -> ActionLeaf: + return blackboard.get_value("running_action", null, str(actor.get_instance_id())) + + +## Returns the last condition that was executed +func get_last_condition() -> ConditionLeaf: + return blackboard.get_value("last_condition", null, str(actor.get_instance_id())) + + +## Returns the status of the last executed condition +func get_last_condition_status() -> String: + if blackboard.has_value("last_condition_status", str(actor.get_instance_id())): + var status = blackboard.get_value( + "last_condition_status", null, str(actor.get_instance_id()) + ) + if status == SUCCESS: + return "SUCCESS" + elif status == FAILURE: + return "FAILURE" + else: + return "RUNNING" + return "" + + +## interrupts this tree if anything was running +func interrupt() -> void: + if self.get_child_count() != 0: + var first_child = self.get_child(0) + if "interrupt" in first_child: + first_child.interrupt(actor, blackboard) + + +## Enables this tree. +func enable() -> void: + self.enabled = true + + +## Disables this tree. +func disable() -> void: + self.enabled = false + + +func _exit_tree() -> void: + if custom_monitor: + if _process_time_metric_name != "": + # Remove tree metric from the engine + Performance.remove_custom_monitor(_process_time_metric_name) + _get_global_metrics().unregister_tree(self) + + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + + +# Called by the engine to profile this tree +func _get_process_time_metric_value() -> int: + return int(_process_time_metric_value) + + +func _get_debugger_data(node: Node) -> Dictionary: + if not (node is BeehaveTree or node is BeehaveNode): + return {} + + var data := { + path = node.get_path(), + name = node.name, + type = node.get_class_name(), + id = str(node.get_instance_id()) + } + if node.get_child_count() > 0: + data.children = [] + for child in node.get_children(): + var child_data := _get_debugger_data(child) + if not child_data.is_empty(): + data.children.push_back(child_data) + return data + + +func get_class_name() -> Array[StringName]: + return [&"BeehaveTree"] + + +# required to avoid lifecycle issues on initial load +# due to loading order problems with autoloads +func _get_global_metrics() -> Node: + return get_tree().root.get_node("BeehaveGlobalMetrics") + + +# required to avoid lifecycle issues on initial load +# due to loading order problems with autoloads +func _get_global_debugger() -> Node: + return get_tree().root.get_node("BeehaveGlobalDebugger") diff --git a/addons/beehave/nodes/composites/composite.gd b/addons/beehave/nodes/composites/composite.gd new file mode 100644 index 0000000..53a8e90 --- /dev/null +++ b/addons/beehave/nodes/composites/composite.gd @@ -0,0 +1,34 @@ +@tool +@icon("../../icons/category_composite.svg") +class_name Composite extends BeehaveNode + +## A Composite node controls the flow of execution of its children in a specific manner. + +var running_child: BeehaveNode = null + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_children().filter(func(x): return x is BeehaveNode).size() < 2: + warnings.append( + "Any composite node should have at least two children. Otherwise it is not useful." + ) + + return warnings + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + running_child = null + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Composite") + return classes diff --git a/addons/beehave/nodes/composites/randomized_composite.gd b/addons/beehave/nodes/composites/randomized_composite.gd new file mode 100644 index 0000000..2afa5e9 --- /dev/null +++ b/addons/beehave/nodes/composites/randomized_composite.gd @@ -0,0 +1,176 @@ +@tool +class_name RandomizedComposite extends Composite + +const WEIGHTS_PREFIX = "Weights/" + +## Sets a predicable seed +@export var random_seed: int = 0: + set(rs): + random_seed = rs + if random_seed != 0: + seed(random_seed) + else: + randomize() + +## Wether to use weights for every child or not. +@export var use_weights: bool: + set(value): + use_weights = value + if use_weights: + _update_weights(get_children()) + _connect_children_changing_signals() + notify_property_list_changed() + +var _weights: Dictionary +var _exiting_tree: bool + + +func _ready(): + _connect_children_changing_signals() + + +func _connect_children_changing_signals(): + if not child_entered_tree.is_connected(_on_child_entered_tree): + child_entered_tree.connect(_on_child_entered_tree) + + if not child_exiting_tree.is_connected(_on_child_exiting_tree): + child_exiting_tree.connect(_on_child_exiting_tree) + + +func get_shuffled_children() -> Array[Node]: + var children_bag: Array[Node] = get_children().duplicate() + if use_weights: + var weights: Array[int] + weights.assign(children_bag.map(func(child): return _weights[child.name])) + children_bag.assign(_weighted_shuffle(children_bag, weights)) + else: + children_bag.shuffle() + return children_bag + + +## Returns a shuffled version of a given array using the supplied array of weights. +## Think of weights as the chance of a given item being the first in the array. +func _weighted_shuffle(items: Array, weights: Array[int]) -> Array: + if len(items) != len(weights): + push_error( + ( + "items and weights size mismatch: expected %d weights, got %d instead." + % [len(items), len(weights)] + ) + ) + return items + + # This method is based on the weighted random sampling algorithm + # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)). + + # For each index, it will calculate random_value^(1/weight). + var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])] + var random_distribuition = range(len(items)).map(chance_calc) + + # Now we just have to order by the calculated value, descending. + random_distribuition.sort_custom(func(a, b): return a[1] > b[1]) + + return random_distribuition.map(func(dist): return items[dist[0]]) + + +func _get_property_list(): + var properties = [] + + if use_weights: + for key in _weights.keys(): + properties.append( + { + "name": WEIGHTS_PREFIX + key, + "type": TYPE_INT, + "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "1,100" + } + ) + + return properties + + +func _set(property: StringName, value: Variant) -> bool: + if property.begins_with(WEIGHTS_PREFIX): + var weight_name = property.trim_prefix(WEIGHTS_PREFIX) + _weights[weight_name] = value + return true + + return false + + +func _get(property: StringName): + if property.begins_with(WEIGHTS_PREFIX): + var weight_name = property.trim_prefix(WEIGHTS_PREFIX) + return _weights[weight_name] + + return null + + +func _update_weights(children: Array[Node]) -> void: + var new_weights = {} + for c in children: + if _weights.has(c.name): + new_weights[c.name] = _weights[c.name] + else: + new_weights[c.name] = 1 + _weights = new_weights + notify_property_list_changed() + + +func _exit_tree() -> void: + _exiting_tree = true + + +func _enter_tree() -> void: + _exiting_tree = false + + +func _on_child_entered_tree(node: Node): + _update_weights(get_children()) + + var renamed_callable = _on_child_renamed.bind(node.name, node) + if not node.renamed.is_connected(renamed_callable): + node.renamed.connect(renamed_callable) + + if not node.tree_exited.is_connected(_on_child_tree_exited): + node.tree_exited.connect(_on_child_tree_exited.bind(node)) + + +func _on_child_exiting_tree(node: Node): + var renamed_callable = _on_child_renamed.bind(node.name, node) + if node.renamed.is_connected(renamed_callable): + node.renamed.disconnect(renamed_callable) + + +func _on_child_tree_exited(node: Node) -> void: + # don't erase the individual child if the whole tree is exiting together + if not _exiting_tree: + var children = get_children() + children.erase(node) + _update_weights(children) + + if node.tree_exited.is_connected(_on_child_tree_exited): + node.tree_exited.disconnect(_on_child_tree_exited) + + +func _on_child_renamed(old_name: String, renamed_child: Node): + if old_name == renamed_child.name: + return # No need to update the weights. + + # Disconnect signal with old name... + renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child)) + # ...and connect with the new name. + renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child)) + + var original_weight = _weights[old_name] + _weights.erase(old_name) + _weights[renamed_child.name] = original_weight + notify_property_list_changed() + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"RandomizedComposite") + return classes diff --git a/addons/beehave/nodes/composites/selector.gd b/addons/beehave/nodes/composites/selector.gd new file mode 100644 index 0000000..618f602 --- /dev/null +++ b/addons/beehave/nodes/composites/selector.gd @@ -0,0 +1,69 @@ +@tool +@icon("../../icons/selector.svg") +class_name SelectorComposite extends Composite + +## Selector nodes will attempt to execute each of its children until one of +## them return `SUCCESS`. If all children return `FAILURE`, this node will also +## return `FAILURE`. +## If a child returns `RUNNING` it will tick again. + +var last_execution_index: int = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + for c in get_children(): + if c.get_index() < last_execution_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _cleanup_running_task(c, actor, blackboard) + c.after_run(actor, blackboard) + return SUCCESS + FAILURE: + _cleanup_running_task(c, actor, blackboard) + last_execution_index += 1 + c.after_run(actor, blackboard) + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return FAILURE + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + last_execution_index = 0 + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + last_execution_index = 0 + super(actor, blackboard) + + +## Changes `running_action` and `running_child` after the node finishes executing. +func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): + var blackboard_name = str(actor.get_instance_id()) + if finished_action == running_child: + running_child = null + if finished_action == blackboard.get_value("running_action", null, blackboard_name): + blackboard.set_value("running_action", null, blackboard_name) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SelectorComposite") + return classes diff --git a/addons/beehave/nodes/composites/selector_random.gd b/addons/beehave/nodes/composites/selector_random.gd new file mode 100644 index 0000000..7a42300 --- /dev/null +++ b/addons/beehave/nodes/composites/selector_random.gd @@ -0,0 +1,82 @@ +@tool +@icon("../../icons/selector_random.svg") +class_name SelectorRandomComposite extends RandomizedComposite + +## This node will attempt to execute all of its children just like a +## [code]SelectorStar[/code] would, with the exception that the children +## will be executed in a random order. + +## A shuffled list of the children that will be executed in reverse order. +var _children_bag: Array[Node] = [] +var c: Node + + +func _ready() -> void: + super() + if random_seed == 0: + randomize() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if _children_bag.is_empty(): + _reset() + + # We need to traverse the array in reverse since we will be manipulating it. + for i in _get_reversed_indexes(): + c = _children_bag[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _children_bag.erase(c) + c.after_run(actor, blackboard) + return SUCCESS + FAILURE: + _children_bag.erase(c) + c.after_run(actor, blackboard) + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return FAILURE + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _get_reversed_indexes() -> Array[int]: + var reversed: Array[int] + reversed.assign(range(_children_bag.size())) + reversed.reverse() + return reversed + + +func _reset() -> void: + var new_order = get_shuffled_children() + _children_bag = new_order.duplicate() + _children_bag.reverse() # It needs to run the children in reverse order. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SelectorRandomComposite") + return classes diff --git a/addons/beehave/nodes/composites/selector_reactive.gd b/addons/beehave/nodes/composites/selector_reactive.gd new file mode 100644 index 0000000..fa164d0 --- /dev/null +++ b/addons/beehave/nodes/composites/selector_reactive.gd @@ -0,0 +1,47 @@ +@tool +@icon("../../icons/selector_reactive.svg") +class_name SelectorReactiveComposite extends Composite + +## Selector Reactive nodes will attempt to execute each of its children until one of +## them return `SUCCESS`. If all children return `FAILURE`, this node will also +## return `FAILURE`. +## If a child returns `RUNNING` it will restart. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + for c in get_children(): + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + # Interrupt any child that was RUNNING before. + if c != running_child: + interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return SUCCESS + FAILURE: + c.after_run(actor, blackboard) + RUNNING: + if c != running_child: + interrupt(actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return FAILURE + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SelectorReactiveComposite") + return classes diff --git a/addons/beehave/nodes/composites/sequence.gd b/addons/beehave/nodes/composites/sequence.gd new file mode 100644 index 0000000..d308e8d --- /dev/null +++ b/addons/beehave/nodes/composites/sequence.gd @@ -0,0 +1,75 @@ +@tool +@icon("../../icons/sequence.svg") +class_name SequenceComposite extends Composite + +## Sequence nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and restart. +## In case a child returns `RUNNING` this node will tick again. + +var successful_index: int = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + for c in get_children(): + if c.get_index() < successful_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _cleanup_running_task(c, actor, blackboard) + successful_index += 1 + c.after_run(actor, blackboard) + FAILURE: + _cleanup_running_task(c, actor, blackboard) + # Interrupt any child that was RUNNING before. + interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + if c != running_child: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + _reset() + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _reset() -> void: + successful_index = 0 + + +## Changes `running_action` and `running_child` after the node finishes executing. +func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): + var blackboard_name = str(actor.get_instance_id()) + if finished_action == running_child: + running_child = null + if finished_action == blackboard.get_value("running_action", null, blackboard_name): + blackboard.set_value("running_action", null, blackboard_name) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceComposite") + return classes diff --git a/addons/beehave/nodes/composites/sequence_random.gd b/addons/beehave/nodes/composites/sequence_random.gd new file mode 100644 index 0000000..a744f52 --- /dev/null +++ b/addons/beehave/nodes/composites/sequence_random.gd @@ -0,0 +1,96 @@ +@tool +@icon("../../icons/sequence_random.svg") +class_name SequenceRandomComposite extends RandomizedComposite + +## This node will attempt to execute all of its children just like a +## [code]SequenceStar[/code] would, with the exception that the children +## will be executed in a random order. + +# Emitted whenever the children are shuffled. +signal reset(new_order: Array[Node]) + +## Whether the sequence should start where it left off after a previous failure. +@export var resume_on_failure: bool = false +## Whether the sequence should start where it left off after a previous interruption. +@export var resume_on_interrupt: bool = false + +## A shuffled list of the children that will be executed in reverse order. +var _children_bag: Array[Node] = [] +var c: Node + + +func _ready() -> void: + super() + if random_seed == 0: + randomize() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if _children_bag.is_empty(): + _reset() + + # We need to traverse the array in reverse since we will be manipulating it. + for i in _get_reversed_indexes(): + c = _children_bag[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _children_bag.erase(c) + c.after_run(actor, blackboard) + FAILURE: + _children_bag.erase(c) + # Interrupt any child that was RUNNING before + # but do not reset! + super.interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return SUCCESS + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + if not resume_on_failure: + _reset() + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if not resume_on_interrupt: + _reset() + super(actor, blackboard) + + +func _get_reversed_indexes() -> Array[int]: + var reversed: Array[int] + reversed.assign(range(_children_bag.size())) + reversed.reverse() + return reversed + + +func _reset() -> void: + var new_order = get_shuffled_children() + _children_bag = new_order.duplicate() + _children_bag.reverse() # It needs to run the children in reverse order. + reset.emit(new_order) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceRandomComposite") + return classes diff --git a/addons/beehave/nodes/composites/sequence_reactive.gd b/addons/beehave/nodes/composites/sequence_reactive.gd new file mode 100644 index 0000000..f40d8ab --- /dev/null +++ b/addons/beehave/nodes/composites/sequence_reactive.gd @@ -0,0 +1,63 @@ +@tool +@icon("../../icons/sequence_reactive.svg") +class_name SequenceReactiveComposite extends Composite + +## Reactive Sequence nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and restart. +## In case a child returns `RUNNING` this node will restart. + +var successful_index: int = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + for c in get_children(): + if c.get_index() < successful_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + successful_index += 1 + c.after_run(actor, blackboard) + FAILURE: + # Interrupt any child that was RUNNING before. + interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + _reset() + if running_child != c: + interrupt(actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + _reset() + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _reset() -> void: + successful_index = 0 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceReactiveComposite") + return classes diff --git a/addons/beehave/nodes/composites/sequence_star.gd b/addons/beehave/nodes/composites/sequence_star.gd new file mode 100644 index 0000000..36bb19c --- /dev/null +++ b/addons/beehave/nodes/composites/sequence_star.gd @@ -0,0 +1,61 @@ +@tool +@icon("../../icons/sequence_reactive.svg") +class_name SequenceStarComposite extends Composite + +## Sequence Star nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and tick again. +## In case a child returns `RUNNING` this node will tick again. + +var successful_index: int = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + for c in get_children(): + if c.get_index() < successful_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + successful_index += 1 + c.after_run(actor, blackboard) + FAILURE: + # Interrupt any child that was RUNNING before + # but do not reset! + super.interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + _reset() + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _reset() -> void: + successful_index = 0 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceStarComposite") + return classes diff --git a/addons/beehave/nodes/composites/simple_parallel.gd b/addons/beehave/nodes/composites/simple_parallel.gd new file mode 100644 index 0000000..4133d60 --- /dev/null +++ b/addons/beehave/nodes/composites/simple_parallel.gd @@ -0,0 +1,122 @@ +@tool +@icon("../../icons/simple_parallel.svg") +class_name SimpleParallelComposite extends Composite + +## Simple Parallel nodes will attampt to execute all chidren at same time and +## can only have exactly two children. First child as primary node, second +## child as secondary node. +## This node will always report primary node's state, and continue tick while +## primary node return 'RUNNING'. The state of secondary node will be ignored +## and executed like a subtree. +## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt +## secondary node and return primary node's result. +## If this node is running under delay mode, it will wait seconday node +## finish its action after primary node terminates. + +#how many times should secondary node repeat, zero means loop forever +@export var secondary_node_repeat_count: int = 0 + +#wether to wait secondary node finish its current action after primary node finished +@export var delay_mode: bool = false + +var delayed_result := SUCCESS +var main_task_finished: bool = false +var secondary_node_running: bool = false +var secondary_node_repeat_left: int = 0 + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_child_count() != 2: + warnings.append("SimpleParallel should have exactly two child nodes.") + + if not get_child(0) is ActionLeaf: + warnings.append("SimpleParallel should have an action leaf node as first child node.") + + return warnings + + +func tick(actor, blackboard: Blackboard): + for c in get_children(): + var node_index = c.get_index() + if node_index == 0 and not main_task_finished: + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + delayed_result = response + match response: + SUCCESS, FAILURE: + _cleanup_running_task(c, actor, blackboard) + c.after_run(actor, blackboard) + main_task_finished = true + if not delay_mode: + if secondary_node_running: + get_child(1).interrupt(actor, blackboard) + _reset() + return delayed_result + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + + elif node_index == 1: + if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0: + if not secondary_node_running: + c.before_run(actor, blackboard) + var subtree_response = c.tick(actor, blackboard) + if subtree_response != RUNNING: + secondary_node_running = false + c.after_run(actor, blackboard) + if delay_mode and main_task_finished: + _reset() + return delayed_result + elif secondary_node_repeat_left > 0: + secondary_node_repeat_left -= 1 + else: + secondary_node_running = true + + return RUNNING + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + secondary_node_repeat_left = secondary_node_repeat_count + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if not main_task_finished: + get_child(0).interrupt(actor, blackboard) + if secondary_node_running: + get_child(1).interrupt(actor, blackboard) + _reset() + super(actor, blackboard) + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _reset() -> void: + main_task_finished = false + secondary_node_running = false + + +## Changes `running_action` and `running_child` after the node finishes executing. +func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): + var blackboard_name = str(actor.get_instance_id()) + if finished_action == running_child: + running_child = null + if finished_action == blackboard.get_value("running_action", null, blackboard_name): + blackboard.set_value("running_action", null, blackboard_name) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SimpleParallelComposite") + return classes diff --git a/addons/beehave/nodes/decorators/cooldown.gd b/addons/beehave/nodes/decorators/cooldown.gd new file mode 100644 index 0000000..84c27b7 --- /dev/null +++ b/addons/beehave/nodes/decorators/cooldown.gd @@ -0,0 +1,49 @@ +@tool +@icon("../../icons/cooldown.svg") +extends Decorator +class_name CooldownDecorator + +## The Cooldown Decorator will return 'FAILURE' for a set amount of time +## after executing its child. +## The timer resets the next time its child is executed and it is not `RUNNING` + +## The wait time in seconds +@export var wait_time := 0.0 + +@onready var cache_key = "cooldown_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + var remaining_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + var response + + if c != running_child: + c.before_run(actor, blackboard) + + if remaining_time > 0: + response = FAILURE + + remaining_time -= get_physics_process_delta_time() + blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id())) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response) + else: + response = c.tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING and c is ActionLeaf: + running_child = c + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + + if response != RUNNING: + blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id())) + + return response diff --git a/addons/beehave/nodes/decorators/decorator.gd b/addons/beehave/nodes/decorators/decorator.gd new file mode 100644 index 0000000..3b93498 --- /dev/null +++ b/addons/beehave/nodes/decorators/decorator.gd @@ -0,0 +1,33 @@ +@tool +@icon("../../icons/category_decorator.svg") +class_name Decorator extends BeehaveNode + +## Decorator nodes are used to transform the result received by its child. +## Must only have one child. + +var running_child: BeehaveNode = null + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_child_count() != 1: + warnings.append("Decorator should have exactly one child node.") + + return warnings + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + running_child = null + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Decorator") + return classes diff --git a/addons/beehave/nodes/decorators/delayer.gd b/addons/beehave/nodes/decorators/delayer.gd new file mode 100644 index 0000000..2b8b73c --- /dev/null +++ b/addons/beehave/nodes/decorators/delayer.gd @@ -0,0 +1,49 @@ +@tool +@icon("../../icons/delayer.svg") +extends Decorator +class_name DelayDecorator + +## The Delay Decorator will return 'RUNNING' for a set amount of time +## before executing its child. +## The timer resets when both it and its child are not `RUNNING` + +## The wait time in seconds +@export var wait_time := 0.0 + +@onready var cache_key = "time_limiter_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + var total_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + var response + + if c != running_child: + c.before_run(actor, blackboard) + + if total_time < wait_time: + response = RUNNING + + total_time += get_physics_process_delta_time() + blackboard.set_value(cache_key, total_time, str(actor.get_instance_id())) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response) + else: + response = c.tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING and c is ActionLeaf: + running_child = c + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + + if response != RUNNING: + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + + return response diff --git a/addons/beehave/nodes/decorators/failer.gd b/addons/beehave/nodes/decorators/failer.gd new file mode 100644 index 0000000..e47312f --- /dev/null +++ b/addons/beehave/nodes/decorators/failer.gd @@ -0,0 +1,35 @@ +@tool +@icon("../../icons/failer.svg") +class_name AlwaysFailDecorator extends Decorator + +## A Failer node will always return a `FAILURE` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + else: + c.after_run(actor, blackboard) + return FAILURE + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"AlwaysFailDecorator") + return classes diff --git a/addons/beehave/nodes/decorators/inverter.gd b/addons/beehave/nodes/decorators/inverter.gd new file mode 100644 index 0000000..2570677 --- /dev/null +++ b/addons/beehave/nodes/decorators/inverter.gd @@ -0,0 +1,43 @@ +@tool +@icon("../../icons/inverter.svg") +class_name InverterDecorator extends Decorator + +## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status +## code or `SUCCESS` in case its child returns a `FAILURE` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + c.after_run(actor, blackboard) + return FAILURE + FAILURE: + c.after_run(actor, blackboard) + return SUCCESS + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + _: + push_error("This should be unreachable") + return -1 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"InverterDecorator") + return classes diff --git a/addons/beehave/nodes/decorators/limiter.gd b/addons/beehave/nodes/decorators/limiter.gd new file mode 100644 index 0000000..a38d7f3 --- /dev/null +++ b/addons/beehave/nodes/decorators/limiter.gd @@ -0,0 +1,60 @@ +@tool +@icon("../../icons/limiter.svg") +class_name LimiterDecorator extends Decorator + +## The limiter will execute its `RUNNING` child `x` amount of times. When the number of +## maximum ticks is reached, it will return a `FAILURE` status code. +## The count resets the next time that a child is not `RUNNING` + +@onready var cache_key = "limiter_%s" % self.get_instance_id() + +@export var max_count: float = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if not get_child_count() == 1: + return FAILURE + + var child = get_child(0) + var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id())) + + if current_count < max_count: + blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id())) + var response = child.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if child is ActionLeaf and response == RUNNING: + running_child = child + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + + if response != RUNNING: + child.after_run(actor, blackboard) + + return response + else: + interrupt(actor, blackboard) + child.after_run(actor, blackboard) + return FAILURE + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + blackboard.set_value(cache_key, 0, str(actor.get_instance_id())) + if get_child_count() > 0: + get_child(0).before_run(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"LimiterDecorator") + return classes + + +func _get_configuration_warnings() -> PackedStringArray: + if not get_child_count() == 1: + return ["Requires exactly one child node"] + return [] diff --git a/addons/beehave/nodes/decorators/repeater.gd b/addons/beehave/nodes/decorators/repeater.gd new file mode 100644 index 0000000..647f7e0 --- /dev/null +++ b/addons/beehave/nodes/decorators/repeater.gd @@ -0,0 +1,58 @@ +## The repeater will execute its child until it returns `SUCCESS` a certain amount of times. +## When the number of maximum ticks is reached, it will return a `SUCCESS` status code. +## If the child returns `FAILURE`, the repeater will return `FAILURE` immediately. +@tool +@icon("../../icons/repeater.svg") +class_name RepeaterDecorator extends Decorator + +@export var repetitions: int = 1 +var current_count: int = 0 + + +func before_run(actor: Node, blackboard: Blackboard): + current_count = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var child = get_child(0) + + if current_count < repetitions: + if running_child == null: + child.before_run(actor, blackboard) + + var response = child.tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + return RUNNING + + current_count += 1 + child.after_run(actor, blackboard) + + if running_child != null: + running_child = null + + if response == FAILURE: + return FAILURE + + if current_count >= repetitions: + return SUCCESS + + return RUNNING + else: + return SUCCESS + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"LimiterDecorator") + return classes diff --git a/addons/beehave/nodes/decorators/succeeder.gd b/addons/beehave/nodes/decorators/succeeder.gd new file mode 100644 index 0000000..344b90f --- /dev/null +++ b/addons/beehave/nodes/decorators/succeeder.gd @@ -0,0 +1,35 @@ +@tool +@icon("../../icons/succeeder.svg") +class_name AlwaysSucceedDecorator extends Decorator + +## A succeeder node will always return a `SUCCESS` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + else: + c.after_run(actor, blackboard) + return SUCCESS + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"AlwaysSucceedDecorator") + return classes diff --git a/addons/beehave/nodes/decorators/time_limiter.gd b/addons/beehave/nodes/decorators/time_limiter.gd new file mode 100644 index 0000000..d9638d7 --- /dev/null +++ b/addons/beehave/nodes/decorators/time_limiter.gd @@ -0,0 +1,60 @@ +@tool +@icon("../../icons/limiter.svg") +class_name TimeLimiterDecorator extends Decorator + +## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish +## before interrupting it and return a `FAILURE` status code. +## The timer resets the next time that a child is not `RUNNING` + +@export var wait_time := 0.0 + +@onready var cache_key = "time_limiter_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if not get_child_count() == 1: + return FAILURE + + var child = self.get_child(0) + var time_left = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + + if time_left < wait_time: + time_left += get_physics_process_delta_time() + blackboard.set_value(cache_key, time_left, str(actor.get_instance_id())) + var response = child.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + else: + child.after_run(actor, blackboard) + return response + else: + interrupt(actor, blackboard) + child.after_run(actor, blackboard) + return FAILURE + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + if get_child_count() > 0: + get_child(0).before_run(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"TimeLimiterDecorator") + return classes + + +func _get_configuration_warnings() -> PackedStringArray: + if not get_child_count() == 1: + return ["Requires exactly one child node"] + return [] diff --git a/addons/beehave/nodes/decorators/until_fail.gd b/addons/beehave/nodes/decorators/until_fail.gd new file mode 100644 index 0000000..8f5ebbe --- /dev/null +++ b/addons/beehave/nodes/decorators/until_fail.gd @@ -0,0 +1,33 @@ +@tool +@icon("../../icons/until_fail.svg") +class_name UntilFailDecorator +extends Decorator + +## The UntilFail Decorator will return `RUNNING` if its child returns +## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns +## `FAILURE` + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response = c.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + if response == SUCCESS: + return RUNNING + + return SUCCESS diff --git a/addons/beehave/nodes/leaves/action.gd b/addons/beehave/nodes/leaves/action.gd new file mode 100644 index 0000000..9074c07 --- /dev/null +++ b/addons/beehave/nodes/leaves/action.gd @@ -0,0 +1,14 @@ +@tool +@icon("../../icons/action.svg") +class_name ActionLeaf extends Leaf + +## Actions are leaf nodes that define a task to be performed by an actor. +## Their execution can be long running, potentially being called across multiple +## frame executions. In this case, the node should return `RUNNING` until the +## action is completed. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"ActionLeaf") + return classes diff --git a/addons/beehave/nodes/leaves/blackboard_compare.gd b/addons/beehave/nodes/leaves/blackboard_compare.gd new file mode 100644 index 0000000..38a870d --- /dev/null +++ b/addons/beehave/nodes/leaves/blackboard_compare.gd @@ -0,0 +1,65 @@ +@tool +class_name BlackboardCompareCondition extends ConditionLeaf + +## Compares two values using the specified comparison operator. +## Returns [code]FAILURE[/code] if any of the expression fails or the +## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code]. + +enum Operators { + EQUAL, + NOT_EQUAL, + GREATER, + LESS, + GREATER_EQUAL, + LESS_EQUAL, +} + +## Expression represetning left operand. +## This value can be any valid GDScript expression. +## In order to use the existing blackboard keys for comparison, +## use get_value("key_name") e.g. get_value("direction").length() +@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = "" +## Comparison operator. +@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0 +## Expression represetning right operand. +## This value can be any valid GDScript expression. +## In order to use the existing blackboard keys for comparison, +## use get_value("key_name") e.g. get_value("direction").length() +@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = "" + +@onready var _left_expression: Expression = _parse_expression(left_operand) +@onready var _right_expression: Expression = _parse_expression(right_operand) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var left: Variant = _left_expression.execute([], blackboard) + + if _left_expression.has_execute_failed(): + return FAILURE + + var right: Variant = _right_expression.execute([], blackboard) + + if _right_expression.has_execute_failed(): + return FAILURE + + var result: bool = false + + match operator: + Operators.EQUAL: + result = left == right + Operators.NOT_EQUAL: + result = left != right + Operators.GREATER: + result = left > right + Operators.LESS: + result = left < right + Operators.GREATER_EQUAL: + result = left >= right + Operators.LESS_EQUAL: + result = left <= right + + return SUCCESS if result else FAILURE + + +func _get_expression_sources() -> Array[String]: + return [left_operand, right_operand] diff --git a/addons/beehave/nodes/leaves/blackboard_erase.gd b/addons/beehave/nodes/leaves/blackboard_erase.gd new file mode 100644 index 0000000..e5cc1d4 --- /dev/null +++ b/addons/beehave/nodes/leaves/blackboard_erase.gd @@ -0,0 +1,25 @@ +@tool +class_name BlackboardEraseAction extends ActionLeaf + +## Erases the specified key from the blackboard. +## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + blackboard.erase_value(key_value) + + return SUCCESS + + +func _get_expression_sources() -> Array[String]: + return [key] diff --git a/addons/beehave/nodes/leaves/blackboard_has.gd b/addons/beehave/nodes/leaves/blackboard_has.gd new file mode 100644 index 0000000..2ee5e92 --- /dev/null +++ b/addons/beehave/nodes/leaves/blackboard_has.gd @@ -0,0 +1,23 @@ +@tool +class_name BlackboardHasCondition extends ConditionLeaf + +## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist. +## Returns [code]SUCCESS[/code] if blackboard has the specified key. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + return SUCCESS if blackboard.has_value(key_value) else FAILURE + + +func _get_expression_sources() -> Array[String]: + return [key] diff --git a/addons/beehave/nodes/leaves/blackboard_set.gd b/addons/beehave/nodes/leaves/blackboard_set.gd new file mode 100644 index 0000000..4f0ed9f --- /dev/null +++ b/addons/beehave/nodes/leaves/blackboard_set.gd @@ -0,0 +1,33 @@ +@tool +class_name BlackboardSetAction extends ActionLeaf + +## Sets the specified key to the specified value. +## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" +## Expression representing a blackboard value to assign to the specified key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) +@onready var _value_expression: Expression = _parse_expression(value) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + var value_value: Variant = _value_expression.execute([], blackboard) + + if _value_expression.has_execute_failed(): + return FAILURE + + blackboard.set_value(key_value, value_value) + + return SUCCESS + + +func _get_expression_sources() -> Array[String]: + return [key, value] diff --git a/addons/beehave/nodes/leaves/condition.gd b/addons/beehave/nodes/leaves/condition.gd new file mode 100644 index 0000000..f4610b4 --- /dev/null +++ b/addons/beehave/nodes/leaves/condition.gd @@ -0,0 +1,12 @@ +@tool +@icon("../../icons/condition.svg") +class_name ConditionLeaf extends Leaf + +## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on +## a single simple condition. They should never return `RUNNING`. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"ConditionLeaf") + return classes diff --git a/addons/beehave/nodes/leaves/leaf.gd b/addons/beehave/nodes/leaves/leaf.gd new file mode 100644 index 0000000..4946c7d --- /dev/null +++ b/addons/beehave/nodes/leaves/leaf.gd @@ -0,0 +1,48 @@ +@tool +@icon("../../icons/category_leaf.svg") +class_name Leaf extends BeehaveNode + +## Base class for all leaf nodes of the tree. + +const EXPRESSION_PLACEHOLDER: String = "Insert an expression..." + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + var children: Array[Node] = get_children() + + if children.any(func(x): return x is BeehaveNode): + warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.") + + for source in _get_expression_sources(): + var error_text: String = _parse_expression(source).get_error_text() + if not error_text.is_empty(): + warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text]) + + return warnings + + +func _parse_expression(source: String) -> Expression: + var result: Expression = Expression.new() + var error: int = result.parse(source) + + if not Engine.is_editor_hint() and error != OK: + push_error( + ( + "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" + % [source, result.get_error_text()] + ) + ) + + return result + + +func _get_expression_sources() -> Array[String]: # virtual + return [] + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Leaf") + return classes diff --git a/addons/beehave/plugin.cfg b/addons/beehave/plugin.cfg new file mode 100644 index 0000000..f06defe --- /dev/null +++ b/addons/beehave/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Beehave" +description="🐝 Behavior Tree addon for Godot Engine" +author="bitbrain" +version="2.8.2-dev" +script="plugin.gd" diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd new file mode 100644 index 0000000..cda3b0d --- /dev/null +++ b/addons/beehave/plugin.gd @@ -0,0 +1,26 @@ +@tool +extends EditorPlugin + +const BeehaveEditorDebugger := preload("debug/debugger.gd") +var editor_debugger: BeehaveEditorDebugger +var frames: RefCounted + + +func _init(): + name = "BeehavePlugin" + add_autoload_singleton("BeehaveGlobalMetrics", "metrics/beehave_global_metrics.gd") + add_autoload_singleton("BeehaveGlobalDebugger", "debug/global_debugger.gd") + print("Beehave initialized!") + + +func _enter_tree() -> void: + editor_debugger = BeehaveEditorDebugger.new() + if Engine.get_version_info().minor >= 2: + frames = preload("debug/new_frames.gd").new() + else: + frames = preload("debug/old_frames.gd").new() + add_debugger_plugin(editor_debugger) + + +func _exit_tree() -> void: + remove_debugger_plugin(editor_debugger) diff --git a/addons/beehave/utils/utils.gd b/addons/beehave/utils/utils.gd new file mode 100644 index 0000000..5f51ce7 --- /dev/null +++ b/addons/beehave/utils/utils.gd @@ -0,0 +1,21 @@ +@tool + + +static func get_plugin() -> EditorPlugin: + var tree: SceneTree = Engine.get_main_loop() + return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin") + + +static func get_editor_scale() -> float: + var plugin := get_plugin() + if plugin: + return plugin.get_editor_interface().get_editor_scale() + return 1.0 + + +static func get_frames() -> RefCounted: + var plugin := get_plugin() + if plugin: + return plugin.frames + push_error("Can't find Beehave Plugin") + return null diff --git a/project.godot b/project.godot index d572b89..3acaa53 100644 --- a/project.godot +++ b/project.godot @@ -18,6 +18,8 @@ run/max_fps=60 [autoload] GameState="*res://src/game_state.tscn" +BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd" +BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd" [debug] @@ -44,7 +46,7 @@ movie_writer/movie_file="demos/demo.avi" [editor_plugins] -enabled=PackedStringArray("res://addons/format_on_save/plugin.cfg", "res://addons/gdlint_plugin/plugin.cfg") +enabled=PackedStringArray("res://addons/beehave/plugin.cfg", "res://addons/format_on_save/plugin.cfg", "res://addons/gdlint_plugin/plugin.cfg") [filesystem] diff --git a/script_templates/.gdignore b/script_templates/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/script_templates/BeehaveNode/default.gd b/script_templates/BeehaveNode/default.gd new file mode 100644 index 0000000..2eacc01 --- /dev/null +++ b/script_templates/BeehaveNode/default.gd @@ -0,0 +1,7 @@ +# meta-name: Default +# meta-default: true +extends _BASE_ + + +func tick(actor: Node, blackboard: Blackboard) -> int: + return SUCCESS diff --git a/src/npc/mech_enemy/mech_enemy.tscn b/src/npc/mech_enemy/mech_enemy.tscn index a65abba..a54ce34 100644 --- a/src/npc/mech_enemy/mech_enemy.tscn +++ b/src/npc/mech_enemy/mech_enemy.tscn @@ -1,146 +1,151 @@ -[gd_scene load_steps=3 format=3 uid="uid://bv8ifwo8yn6h0"] +[gd_scene load_steps=4 format=3 uid="uid://bv8ifwo8yn6h0"] [ext_resource type="PackedScene" uid="uid://b4lueyrglqwou" path="res://src/npc/mech_character/mech_character.tscn" id="1_yyy7d"] [ext_resource type="Script" path="res://src/npc/mech_enemy/mech_enemy.gd" id="2_35sm4"] +[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="3_ygwmt"] [node name="MechEnemy" instance=ExtResource("1_yyy7d")] script = ExtResource("2_35sm4") [node name="Skeleton3D" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5" index="0"] -bones/1/position = Vector3(-1.16428e-18, -0.00565718, 6.28666) -bones/3/rotation = Quaternion(0.652093, 0.671798, 0.244906, 0.251959) -bones/4/rotation = Quaternion(0.728072, 0.0541667, 0.0753075, 0.679195) -bones/5/rotation = Quaternion(-0.645243, -0.00107443, -0.104832, 0.75675) -bones/6/rotation = Quaternion(-0.431558, 0.011812, -0.0626038, 0.899833) -bones/7/rotation = Quaternion(-0.848221, 0.0352536, -0.1759, 0.498335) -bones/11/rotation = Quaternion(-0.844803, 0.0417649, -0.171463, 0.505138) -bones/17/rotation = Quaternion(0.75141, 0.0198419, 0.65908, 0.0245544) -bones/23/rotation = Quaternion(0.878642, 0.00344118, 0.145015, 0.454915) -bones/27/rotation = Quaternion(-0.587724, -0.00518809, -0.107773, 0.801835) -bones/33/rotation = Quaternion(-0.580195, -0.00925014, -0.112766, 0.806581) -bones/35/rotation = Quaternion(-0.667706, 0.00408352, -0.108192, 0.73651) -bones/41/rotation = Quaternion(-0.0150049, 0.940017, 0.340583, 0.0120921) -bones/49/rotation = Quaternion(-0.000192646, 6.5783e-24, -4.95542e-31, 1) -bones/51/rotation = Quaternion(0.595717, -0.000239383, 0.000322246, 0.803195) -bones/55/rotation = Quaternion(0.595717, 0.000238617, -0.000321677, 0.803195) -bones/74/rotation = Quaternion(-0.651376, 0.671133, 0.246724, -0.253805) -bones/75/rotation = Quaternion(0.729449, -0.0540814, -0.0754655, 0.677705) -bones/76/rotation = Quaternion(-0.646293, 0.00105066, 0.104978, 0.755833) -bones/77/rotation = Quaternion(-0.429893, -0.0118377, 0.0623891, 0.900644) -bones/78/rotation = Quaternion(-0.850065, -0.0350339, 0.176334, 0.495043) -bones/82/rotation = Quaternion(-0.846687, -0.0415416, 0.17191, 0.50184) -bones/88/rotation = Quaternion(0.751374, -0.0207991, -0.659036, 0.026006) -bones/94/rotation = Quaternion(0.875443, -0.00349516, -0.14445, 0.461218) -bones/98/rotation = Quaternion(-0.589995, 0.00513959, 0.108082, 0.800124) -bones/102/rotation = Quaternion(-0.582486, 0.00920923, 0.113089, 0.804883) -bones/106/rotation = Quaternion(-0.669169, -0.00411456, 0.108403, 0.735149) -bones/112/rotation = Quaternion(0.0149266, 0.941353, 0.336875, -0.0121463) -bones/122/position = Vector3(3.16409, -0.198371, 0.386182) -bones/122/rotation = Quaternion(0.993193, -0.00680501, 0.0811606, -0.0832762) -bones/126/position = Vector3(-3.16409, -0.219364, 0.399309) -bones/126/rotation = Quaternion(0.993215, 0.006783, -0.0811624, -0.0830069) +bones/1/position = Vector3(-1.16428e-18, -0.00565962, 6.4503) +bones/1/rotation = Quaternion(0.707107, 0, 0, 0.707107) +bones/3/rotation = Quaternion(0.655539, 0.674991, 0.236051, 0.242767) +bones/4/rotation = Quaternion(0.701393, 0.0561155, 0.0724948, 0.706855) +bones/5/rotation = Quaternion(-0.62244, -0.0013949, -0.101495, 0.776058) +bones/6/rotation = Quaternion(-0.436214, 0.0114048, -0.0630001, 0.897562) +bones/7/rotation = Quaternion(-0.840423, 0.0365456, -0.173836, 0.51199) +bones/11/rotation = Quaternion(-0.836828, 0.0431592, -0.169297, 0.518839) +bones/17/rotation = Quaternion(0.751714, 0.0124658, 0.659239, 0.0131975) +bones/23/rotation = Quaternion(0.898153, 0.00353699, 0.148227, 0.41393) +bones/27/rotation = Quaternion(-0.554571, -0.00576545, -0.103074, 0.825708) +bones/33/rotation = Quaternion(-0.546725, -0.00989199, -0.108132, 0.830242) +bones/35/rotation = Quaternion(-0.634341, 0.00363393, -0.103121, 0.766136) +bones/41/rotation = Quaternion(-0.0147272, 0.937017, 0.348764, 0.0120949) +bones/49/rotation = Quaternion(0.000134406, 6.57829e-24, -4.93905e-31, 1) +bones/50/rotation = Quaternion(0.80296, 2.84212e-07, 3.82883e-07, 0.596033) +bones/51/rotation = Quaternion(0.59565, -0.000239346, 0.000322194, 0.803244) +bones/55/rotation = Quaternion(0.59565, 0.00023858, -0.000321625, 0.803244) +bones/62/rotation = Quaternion(-0.000385918, 2.26111e-14, -0.000401128, 1) +bones/66/rotation = Quaternion(-0.000385918, -2.26111e-14, 0.000401128, 1) +bones/74/rotation = Quaternion(-0.65523, 0.674634, 0.236955, -0.243713) +bones/75/rotation = Quaternion(0.700713, -0.0561521, -0.072417, 0.707534) +bones/76/rotation = Quaternion(-0.621948, 0.00140735, 0.101427, 0.776461) +bones/77/rotation = Quaternion(-0.435005, -0.0113871, 0.0628078, 0.898163) +bones/78/rotation = Quaternion(-0.841203, -0.0365174, 0.173983, 0.51066) +bones/82/rotation = Quaternion(-0.837608, -0.0431989, 0.169413, 0.517538) +bones/88/rotation = Quaternion(0.751734, -0.0114382, -0.659264, 0.0116353) +bones/94/rotation = Quaternion(0.900248, -0.00364429, -0.148526, 0.409243) +bones/98/rotation = Quaternion(-0.554672, 0.00577314, 0.103103, 0.825636) +bones/102/rotation = Quaternion(-0.546817, 0.00990845, 0.108172, 0.830176) +bones/106/rotation = Quaternion(-0.633656, -0.00361851, 0.103025, 0.766716) +bones/112/rotation = Quaternion(0.0147817, 0.936089, 0.351247, -0.0120619) +bones/122/position = Vector3(3.16409, -0.197548, 0.389143) +bones/122/rotation = Quaternion(0.993195, -0.00680326, 0.0811607, -0.0832548) +bones/126/position = Vector3(-3.16409, -0.214088, 0.385488) +bones/126/rotation = Quaternion(0.993214, 0.00678475, -0.0811623, -0.0830282) [node name="Plane_009_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="0"] -transform = Transform3D(0.999997, 0.000485554, 0.002437, -0.000570327, 0.999391, 0.0349115, -0.00241838, -0.0349128, 0.999388, -0.0103064, -0.0206124, 0.043297) +transform = Transform3D(1, 0.000145733, 0.000214532, -0.000146464, 0.999995, 0.00329205, -0.000214159, -0.00329196, 0.999995, -0.000857353, 0.00548816, 0.00510401) [node name="Plane_012_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="1"] -transform = Transform3D(0.999996, 0.00083892, 0.00269292, -0.000933304, 0.999378, 0.0352446, -0.0026615, -0.0352471, 0.999375, -0.0108726, -0.0205576, 0.0433462) +transform = Transform3D(1, 0.000221059, 0.00030861, -0.000222072, 0.999994, 0.00334173, -0.000307985, -0.0033417, 0.999994, -0.00101042, 0.00556266, 0.00527143) [node name="Cube_016_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="2"] -transform = Transform3D(0.999999, -0.00045982, 0.000978887, 0.000460625, 1, -0.000782296, -0.000978351, 0.000782743, 0.999999, -0.00814509, 0.00514628, 0.00204116) +transform = Transform3D(1, 3.1963e-06, -2.05636e-06, -3.17954e-06, 1, -0.00062114, 1.93715e-06, 0.000621229, 1, -0.000533342, 0.00826833, 0.000428498) [node name="Plane_004_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="3"] -transform = Transform3D(0.999997, -0.000461414, 0.00184978, 0.000376984, 0.998972, 0.0453231, -0.00186855, -0.0453221, 0.998971, -0.00819778, -0.0464974, 0.013404) +transform = Transform3D(0.999999, 0.00021024, 0.000109389, -0.000211, 0.999968, 0.00802783, -0.000107899, -0.00802786, 0.999968, -0.000552416, -0.00286704, 0.0031445) [node name="Cube_013_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="4"] -transform = Transform3D(0.999998, -0.000411622, 0.0021115, 0.000391766, 0.999956, 0.00940478, -0.0021151, -0.00940388, 0.999954, -0.00856543, 0.00143003, 0.00356817) +transform = Transform3D(1, -7.7337e-06, 0.000189543, 7.91252e-06, 1, -0.00081408, -0.000189669, 0.00081417, 1, -0.000608921, 0.00830662, 0.0010736) [node name="Plane_011_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="5"] -transform = Transform3D(0.999999, -0.00146952, -0.000517499, 0.00149477, 0.998609, 0.0527104, 0.000439493, -0.052711, 0.99861, 0.00517917, -0.327782, -0.01366) +transform = Transform3D(1, -0.000238255, -0.000451882, 0.00024347, 0.999932, 0.0116704, 0.000448941, -0.0116704, 0.999932, 0.00260043, -0.0660599, 0.00108385) [node name="Plane_002_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="6"] -transform = Transform3D(0.999997, 0.000829801, 0.00236498, -0.000909045, 0.999431, 0.0337141, -0.00233544, -0.0337161, 0.999429, -0.0111821, -0.00823104, 0.0703139) +transform = Transform3D(1, 0.000133425, 0.000241507, -0.000133753, 0.999999, 0.00138125, -0.00024144, -0.00138122, 0.999999, -0.000980139, 0.0090127, 0.00828457) [node name="Plane_013_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="7"] -transform = Transform3D(0.999997, 0.000829801, 0.00236498, -0.000909045, 0.999431, 0.0337141, -0.00233544, -0.0337161, 0.999429, -0.0111821, -0.00823104, 0.0703139) +transform = Transform3D(1, 0.000133425, 0.000241507, -0.000133753, 0.999999, 0.00138125, -0.00024144, -0.00138122, 0.999999, -0.000980139, 0.0090127, 0.00828457) [node name="Cube_007_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="8"] -transform = Transform3D(0.999998, -0.000544339, 0.00178048, 0.000466473, 0.999056, 0.0434497, -0.00180225, -0.0434488, 0.999054, -0.00808477, -0.0435329, 0.0137968) +transform = Transform3D(1, -1.39028e-05, 0.000154726, 1.33589e-05, 0.999994, 0.00355524, -0.000154883, -0.00355527, 0.999994, -0.000561237, 0.0025239, 0.00203538) [node name="Plane_015_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="9"] -transform = Transform3D(0.999996, -0.000287138, 0.00286065, 0.000457756, 0.99821, -0.0598118, -0.00283816, 0.0598128, 0.998206, -0.0106308, 0.182979, -0.0575895) +transform = Transform3D(1, -3.82438e-05, 0.000288308, 4.11123e-05, 0.99995, -0.00993481, -0.000288047, 0.00993481, 0.999951, -0.000836372, 0.0319744, -0.00744534) [node name="Cube_010_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="10"] -transform = Transform3D(1, -6.51497e-05, -0.000741082, 0.00010597, 0.998477, 0.0551796, 0.000736529, -0.0551796, 0.998476, 0.00478578, -0.337517, -0.142482) +transform = Transform3D(1, -8.85185e-05, -0.000402403, 9.2756e-05, 0.999944, 0.0105775, 0.000401297, -0.0105773, 0.999944, 0.00243735, -0.0664654, -0.0171566) [node name="Cube_012_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="11"] -transform = Transform3D(1, -0.000290575, -0.000596972, 0.000307944, 0.999572, 0.0292708, 0.000588385, -0.0292709, 0.999572, 0.00409222, -0.194976, -0.160153) +transform = Transform3D(1, -3.38895e-05, -0.000168451, 3.42347e-05, 0.999998, 0.00207865, 0.000168241, -0.00207859, 0.999998, 0.00111485, -0.0191698, -0.0206766) [node name="Cylinder_002_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="12"] -transform = Transform3D(1, -1.77636e-15, 0, -1.77636e-15, 1, -4.37113e-08, 0, 4.37113e-08, 1, 0, -0.0056572, -0.185539) +transform = Transform3D(1, -1.77636e-15, 0, -1.77636e-15, 1, -4.37113e-08, 0, 4.37113e-08, 1, 0, -0.00565964, -0.0218992) [node name="Cube_006_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="13"] -transform = Transform3D(1, -2.81557e-09, -3.1913e-07, 2.92491e-09, 1, 0.000400752, 3.19014e-07, -0.000400722, 1, 3.21865e-06, -0.00832629, -0.185574) +transform = Transform3D(1, -7.22333e-08, -4.46377e-07, 7.21816e-08, 1, -8.74402e-05, 4.4627e-07, 8.74996e-05, 1, 4.52995e-06, -0.00594449, -0.0223007) [node name="Cube_017_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="14"] -transform = Transform3D(1, 3.87811e-09, 3.20584e-07, -4.00175e-09, 1, 0.000400752, -3.20585e-07, -0.000400722, 1, -3.21865e-06, -0.00832629, -0.185575) +transform = Transform3D(1, 7.32203e-08, 4.47715e-07, -7.31951e-08, 1, -8.74104e-05, -4.47725e-07, 8.74996e-05, 1, -4.52995e-06, -0.00594473, -0.0223017) [node name="Cube_005_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="15"] -transform = Transform3D(1, -1.7053e-13, -3.41061e-13, 2.84217e-13, 1, 0.00038439, 3.41061e-13, -0.00038439, 1, -9.09154e-13, -0.00814646, -0.185539) +transform = Transform3D(1, -1.7053e-13, -3.41061e-13, 2.84217e-13, 1, -0.000269741, 3.41061e-13, 0.000269741, 1, -9.09154e-13, -0.00391495, -0.0219002) [node name="Cube_005_Inside_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="16"] -transform = Transform3D(1, -1.7053e-13, -3.41061e-13, 2.84217e-13, 1, 0.00038439, 3.41061e-13, -0.00038439, 1, -9.09154e-13, -0.00814646, -0.185539) +transform = Transform3D(1, -1.7053e-13, -3.41061e-13, 2.84217e-13, 1, -0.000269741, 3.41061e-13, 0.000269741, 1, -9.09154e-13, -0.00391495, -0.0219002) [node name="Plane_003_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="17"] -transform = Transform3D(1, 1.08713e-10, -1.01801e-08, -1.06269e-10, 1, 0.000385009, 1.01955e-08, -0.000385009, 1, -2.38419e-07, -0.00815082, -0.185538) +transform = Transform3D(1, 1.08713e-10, -1.01801e-08, -1.12891e-10, 1, -0.000269094, 1.01675e-08, 0.000269094, 1, -2.38419e-07, -0.00391984, -0.0218983) [node name="Plane_016_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="18"] -transform = Transform3D(1, -1.08713e-10, 1.01801e-08, 1.06269e-10, 1, 0.000385009, -1.01955e-08, -0.000385009, 1, 2.38419e-07, -0.00815082, -0.185538) +transform = Transform3D(1, -1.08713e-10, 1.01801e-08, 1.12891e-10, 1, -0.000269094, -1.01675e-08, 0.000269094, 1, 2.38419e-07, -0.00391984, -0.0218983) [node name="Cube_008_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="19"] -transform = Transform3D(1, -2.63915e-23, -2.53357e-27, 2.63915e-23, 1, 0.000385292, -7.63485e-27, -0.000385292, 1, -1.16428e-18, -0.00815087, -0.185538) +transform = Transform3D(1, -2.63915e-23, 1.76931e-27, 2.63915e-23, 1, -0.000268811, 5.32501e-27, 0.000268811, 1, -1.16428e-18, -0.00391982, -0.0218987) [node name="Cube_008_Inside_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="20"] -transform = Transform3D(1, -2.63915e-23, -2.53357e-27, 2.63915e-23, 1, 0.000385292, -7.63485e-27, -0.000385292, 1, -1.16428e-18, -0.00815087, -0.185538) +transform = Transform3D(1, -2.63915e-23, 1.76931e-27, 2.63915e-23, 1, -0.000268811, 5.32501e-27, 0.000268811, 1, -1.16428e-18, -0.00391982, -0.0218987) [node name="Plane_006_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="21"] -transform = Transform3D(0.999997, -0.000607982, -0.00254669, 0.000717133, 0.999074, 0.0430564, 0.00251812, -0.0430582, 0.99907, 0.0105388, -0.0498098, 0.0639408) +transform = Transform3D(1, -0.000108123, -0.000152774, 0.000109196, 0.999976, 0.00709432, 0.000152066, -0.00709426, 0.999975, 0.000838518, -0.0188953, 0.00772113) [node name="Plane_008_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="22"] -transform = Transform3D(0.999996, -0.00101309, -0.00279707, 0.00113362, 0.999057, 0.0434392, 0.0027504, -0.0434423, 0.999053, 0.011152, -0.0497416, 0.0639521) +transform = Transform3D(1, -0.000143483, -0.000108622, 0.000144213, 0.999976, 0.00708106, 0.000107653, -0.00708091, 0.999975, 0.000835896, -0.0188912, 0.00749898) [node name="Cube_004_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="23"] -transform = Transform3D(0.999999, 0.000460286, -0.000988677, -0.000460526, 1, -0.000239536, 0.000988543, 0.000239983, 1, 0.00817108, -0.0184986, 0.0145023) +transform = Transform3D(1, 4.54765e-05, -3.00109e-05, -4.54523e-05, 1, 8.33571e-05, 3.00854e-05, -8.3223e-05, 1, 0.000581264, -0.0135735, 0.000261933) [node name="Plane_005_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="24"] -transform = Transform3D(0.999999, 0.000405326, -0.00189744, -0.000303373, 0.998592, 0.0530632, 0.00191578, -0.0530627, 0.998589, 0.008219, -0.0759304, 0.0265851) +transform = Transform3D(1, -0.000170141, -0.00010179, 0.000171602, 0.999971, 0.0077185, 0.000100121, -0.0077187, 0.99997, 0.000602007, -0.0207109, 0.00132155) [node name="Cube_003_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="25"] -transform = Transform3D(0.999998, 0.000369057, -0.00217903, -0.000339225, 0.999907, 0.013675, 0.00218385, -0.0136742, 0.999904, 0.00860381, -0.0234946, 0.01547) +transform = Transform3D(1, 3.65078e-05, -0.000163198, -3.60832e-05, 0.999997, 0.00261706, 0.000163347, -0.00261691, 0.999996, 0.000629902, -0.0145211, 0.000165224) [node name="Plane_007_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="26"] -transform = Transform3D(0.999999, 0.00156599, 0.000695144, -0.00160982, 0.997662, 0.0683289, -0.000586563, -0.0683298, 0.997663, -0.00633335, -0.409366, 0.00616884) +transform = Transform3D(1, 0.000167027, -2.21808e-05, -0.000166863, 0.999982, 0.00610763, 2.32439e-05, -0.00610745, 0.999981, -0.00026226, -0.0456142, -0.00227928) [node name="Plane_014_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="27"] -transform = Transform3D(0.999997, -0.000939056, -0.00244332, 0.00103824, 0.999163, 0.0408954, 0.00240282, -0.0408978, 0.99916, 0.0113447, -0.0370917, 0.0879161) +transform = Transform3D(1, -0.000107557, -0.000178587, 0.000108734, 0.99998, 0.00634545, 0.000177927, -0.00634536, 0.99998, 0.000925064, -0.0176228, 0.00887609) [node name="Plane_010_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="28"] -transform = Transform3D(0.999996, -0.00171345, -0.00251737, 0.0018158, 0.99915, 0.0412064, 0.00244461, -0.0412109, 0.999148, 0.0125444, -0.0369414, 0.0866697) +transform = Transform3D(1, -0.000210896, -0.000159319, 0.000211924, 0.99998, 0.00637299, 0.000157997, -0.00637293, 0.99998, 0.0010252, -0.0175548, 0.00866151) [node name="Cube_014_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="29"] -transform = Transform3D(0.999998, 0.00050576, -0.00184611, -0.000414819, 0.998802, 0.0489303, 0.00186861, -0.0489295, 0.9988, 0.00812054, -0.0698277, 0.0263016) +transform = Transform3D(1, 4.13135e-05, -0.000126265, -4.05014e-05, 0.999979, 0.00646088, 0.000126563, -0.00646082, 0.999979, 0.000580549, -0.019682, 0.00104856) [node name="Plane_001_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="30"] -transform = Transform3D(0.999996, 0.000247695, -0.00293774, -0.000418685, 0.998296, -0.0583475, 0.00291826, 0.0583485, 0.998292, 0.0106921, 0.165177, -0.0487609) +transform = Transform3D(1, 5.77271e-05, -0.000259183, -5.90608e-05, 0.999987, -0.00522417, 0.000258945, 0.00522429, 0.999986, 0.000860214, 0.00581241, -0.00720644) [node name="Cube_015_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="31"] -transform = Transform3D(0.999999, 0.000164211, 0.0011584, -0.000243263, 0.997646, 0.0685699, -0.00114446, -0.0685698, 0.997646, -0.00724411, -0.416749, -0.130722) +transform = Transform3D(1, 1.80248e-05, 0.00012907, -1.904e-05, 0.999968, 0.00806225, -0.000128856, -0.00806198, 0.999967, -0.000834942, -0.0547957, -0.0165582) [node name="Cube_001_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="32"] -transform = Transform3D(1, 0.000287027, 0.0007048, -0.000311396, 0.999394, 0.0348053, -0.000694436, -0.0348055, 0.999394, -0.00478506, -0.230519, -0.154716) +transform = Transform3D(1, 3.45399e-05, 0.000109066, -3.50451e-05, 0.999988, 0.00486019, -0.000108839, -0.00486007, 0.999988, -0.00073266, -0.0370965, -0.0181041) [node name="Cylinder_001_Material_0" parent="Mesh/Mech/Sketchfab_model/bed584e06fe94e71a7ac484cee9f0a16_fbx/Object_2/RootNode/Armature/Object_5/Skeleton3D" index="33"] -transform = Transform3D(1, 1.77636e-15, 0, 1.77636e-15, 1, -4.37113e-08, 0, 4.37113e-08, 1, 0, -0.0056572, -0.185539) +transform = Transform3D(1, 1.77636e-15, 0, 1.77636e-15, 1, -4.37113e-08, 0, 4.37113e-08, 1, 0, -0.00565964, -0.0218992) [node name="NavigationAgent3D" type="NavigationAgent3D" parent="." index="5"] path_desired_distance = 2.0 @@ -148,6 +153,11 @@ target_desired_distance = 5.0 path_max_distance = 10.01 debug_enabled = true +[node name="BeehaveTree" type="Node" parent="." index="6" node_paths=PackedStringArray("blackboard", "actor")] +script = ExtResource("3_ygwmt") +blackboard = NodePath("@Node@21085") +actor = NodePath("..") + [connection signal="link_reached" from="NavigationAgent3D" to="." method="_on_nav_link_reached"] [editable path="Mesh/Mech"]