├── script_templates ├── .gdignore └── BeehaveNode │ └── default.gd └── addons └── beehave ├── plugin.gd.uid ├── blackboard.gd.uid ├── utils ├── utils.gd.uid └── utils.gd ├── debug ├── debugger.gd.uid ├── new_frames.gd.uid ├── old_frames.gd.uid ├── tree_node.gd.uid ├── debugger_messages.gd.uid ├── debugger_tab.gd.uid ├── global_debugger.gd.uid ├── new_graph_edit.gd.uid ├── new_graph_node.gd.uid ├── old_graph_edit.gd.uid ├── old_graph_node.gd.uid ├── new_node_blackboard.gd.uid ├── icons │ ├── port_left.svg │ ├── port_bottom.svg │ ├── port_right.svg │ ├── port_top.svg │ ├── vertical_layout.svg │ ├── horizontal_layout.svg │ ├── port_top.svg.import │ ├── port_left.svg.import │ ├── port_right.svg.import │ ├── port_bottom.svg.import │ ├── vertical_layout.svg.import │ └── horizontal_layout.svg.import ├── old_frames.gd ├── debugger_messages.gd ├── new_frames.gd ├── global_debugger.gd ├── new_node_blackboard.gd ├── debugger.gd ├── old_graph_node.gd ├── new_graph_node.gd └── debugger_tab.gd ├── nodes ├── beehave_node.gd.uid ├── leaves │ ├── leaf.gd.uid │ ├── action.gd.uid │ ├── condition.gd.uid │ ├── blackboard_has.gd.uid │ ├── blackboard_set.gd.uid │ ├── blackboard_compare.gd.uid │ ├── blackboard_erase.gd.uid │ ├── condition.gd │ ├── action.gd │ ├── blackboard_erase.gd │ ├── blackboard_has.gd │ ├── blackboard_set.gd │ ├── leaf.gd │ └── blackboard_compare.gd ├── beehave_tree.gd.uid ├── composites │ ├── composite.gd.uid │ ├── selector.gd.uid │ ├── sequence.gd.uid │ ├── selector_random.gd.uid │ ├── sequence_random.gd.uid │ ├── sequence_star.gd.uid │ ├── simple_parallel.gd.uid │ ├── randomized_composite.gd.uid │ ├── selector_reactive.gd.uid │ ├── sequence_reactive.gd.uid │ ├── composite.gd │ ├── selector_random.gd │ ├── sequence_reactive.gd │ ├── sequence_random.gd │ ├── sequence_star.gd │ ├── sequence.gd │ ├── selector_reactive.gd │ ├── selector.gd │ ├── simple_parallel.gd │ └── randomized_composite.gd ├── decorators │ ├── cooldown.gd.uid │ ├── decorator.gd.uid │ ├── delayer.gd.uid │ ├── failer.gd.uid │ ├── inverter.gd.uid │ ├── limiter.gd.uid │ ├── repeater.gd.uid │ ├── succeeder.gd.uid │ ├── until_fail.gd.uid │ ├── time_limiter.gd.uid │ ├── decorator.gd │ ├── failer.gd │ ├── succeeder.gd │ ├── until_fail.gd │ ├── inverter.gd │ ├── delayer.gd │ ├── cooldown.gd │ ├── repeater.gd │ ├── time_limiter.gd │ └── limiter.gd └── beehave_node.gd ├── metrics ├── beehave_global_metrics.gd.uid └── beehave_global_metrics.gd ├── plugin.cfg ├── LICENSE ├── icons ├── tree.svg.import ├── action.svg.import ├── failer.svg.import ├── inverter.svg.import ├── limiter.svg.import ├── selector.svg.import ├── sequence.svg.import ├── condition.svg.import ├── succeeder.svg.import ├── blackboard.svg.import ├── category_bt.svg.import ├── category_leaf.svg.import ├── selector_random.svg.import ├── sequence_random.svg.import ├── sequence_reactive.svg.import ├── category_composite.svg.import ├── category_decorator.svg.import ├── selector_reactive.svg.import ├── cooldown.svg ├── blackboard.svg ├── delayer.svg ├── succeeder.svg ├── limiter.svg ├── tree.svg ├── failer.svg ├── inverter.svg ├── sequence.svg ├── selector.svg ├── condition.svg ├── simple_parallel.svg ├── repeater.svg ├── action.svg ├── selector_reactive.svg ├── category_bt.svg ├── category_leaf.svg ├── category_composite.svg ├── category_decorator.svg ├── sequence_random.svg ├── until_fail.svg └── sequence_reactive.svg ├── plugin.gd └── blackboard.gd /script_templates/.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cpmewlew40rut 2 | -------------------------------------------------------------------------------- /addons/beehave/blackboard.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dme5f24l0edsf 2 | -------------------------------------------------------------------------------- /addons/beehave/utils/utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b7s2xntekg47c 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dhh8iwql2tur2 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_frames.gd.uid: -------------------------------------------------------------------------------- 1 | uid://tn5lef5pc6ol 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/old_frames.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bhapw50f625uy 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/tree_node.gd.uid: -------------------------------------------------------------------------------- 1 | uid://20qccrlr5enw 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/beehave_node.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c37shq5uf4gk 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/leaf.gd.uid: -------------------------------------------------------------------------------- 1 | uid://t88253ohwyv1 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger_messages.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ubuuvrubeh85 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger_tab.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b7hbn0y1kfarn 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/global_debugger.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bl3ma400hpvsh 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_graph_edit.gd.uid: -------------------------------------------------------------------------------- 1 | uid://nqi2umf7xr2h 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_graph_node.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bpjiuilpk7qkl 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/old_graph_edit.gd.uid: -------------------------------------------------------------------------------- 1 | uid://qhj602h7xnwu 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/old_graph_node.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bkng0fnhet2hx 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/beehave_tree.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bb0t2ovl7wifo 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/action.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cv74jjdrwevpr 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/condition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cbnbvj8prv63i 2 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_node_blackboard.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ne6tky0wxkxy 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/composite.gd.uid: -------------------------------------------------------------------------------- 1 | uid://fengp7jc44qv 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector.gd.uid: -------------------------------------------------------------------------------- 1 | uid://8hn4kne15ac5 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cg016dbe7gs1x 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/cooldown.gd.uid: -------------------------------------------------------------------------------- 1 | uid://2qri6rrfv8ui 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/decorator.gd.uid: -------------------------------------------------------------------------------- 1 | uid://kfhcykujhf38 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/delayer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dorri1tul8gfx 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/failer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dwfdg523bk776 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/inverter.gd.uid: -------------------------------------------------------------------------------- 1 | uid://crkjak4kyv56m 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/limiter.gd.uid: -------------------------------------------------------------------------------- 1 | uid://crjqmr7expgtl 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/repeater.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c5v7wuqn1xvdy 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/succeeder.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dsf3a8vlolhx8 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/until_fail.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c84st521ytmk3 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_has.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dd8lrjdy885gg 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_set.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cvq483a337v6s 2 | -------------------------------------------------------------------------------- /addons/beehave/metrics/beehave_global_metrics.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c3ktl6ontsdt7 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector_random.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dvnmhlldp23hg 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_random.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b5iop640q3aue 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_star.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bbaojjriq676j 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/simple_parallel.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bkr7qj44253ue 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/time_limiter.gd.uid: -------------------------------------------------------------------------------- 1 | uid://we22430vcb44 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_compare.gd.uid: -------------------------------------------------------------------------------- 1 | uid://uoy6r3dbnq25 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_erase.gd.uid: -------------------------------------------------------------------------------- 1 | uid://r8b0istslwhq 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/randomized_composite.gd.uid: -------------------------------------------------------------------------------- 1 | uid://doqgfjw53d1l4 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector_reactive.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cw22yurt5l74k 2 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_reactive.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dcojdhvj8qcw0 2 | -------------------------------------------------------------------------------- /addons/beehave/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Beehave" 4 | description="🐝 Behavior Tree addon for Godot Engine" 5 | author="bitbrain" 6 | version="2.9.3-dev" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /script_templates/BeehaveNode/default.gd: -------------------------------------------------------------------------------- 1 | # meta-name: Default 2 | # meta-default: true 3 | extends _BASE_ 4 | 5 | 6 | func tick(actor: Node, blackboard: Blackboard) -> int: 7 | return SUCCESS 8 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_bottom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/condition.gd: -------------------------------------------------------------------------------- 1 | ## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on 2 | ## a single simple condition. They should never return `RUNNING`. 3 | @tool 4 | @icon("../../icons/condition.svg") 5 | class_name ConditionLeaf extends Leaf 6 | 7 | 8 | func get_class_name() -> Array[StringName]: 9 | var classes := super() 10 | classes.push_back(&"ConditionLeaf") 11 | return classes 12 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/action.gd: -------------------------------------------------------------------------------- 1 | ## Actions are leaf nodes that define a task to be performed by an actor. 2 | ## Their execution can be long running, potentially being called across multiple 3 | ## frame executions. In this case, the node should return `RUNNING` until the 4 | ## action is completed. 5 | @tool 6 | @icon("../../icons/action.svg") 7 | class_name ActionLeaf extends Leaf 8 | 9 | 10 | func get_class_name() -> Array[StringName]: 11 | var classes := super() 12 | classes.push_back(&"ActionLeaf") 13 | return classes 14 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/vertical_layout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/horizontal_layout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/beehave/utils/utils.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | 3 | 4 | static func get_plugin() -> EditorPlugin: 5 | var tree: SceneTree = Engine.get_main_loop() 6 | return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin") 7 | 8 | 9 | static func get_editor_scale() -> float: 10 | var plugin := get_plugin() 11 | if plugin: 12 | return plugin.get_editor_interface().get_editor_scale() 13 | return 1.0 14 | 15 | 16 | static func get_frames() -> RefCounted: 17 | var plugin := get_plugin() 18 | if plugin: 19 | return plugin.frames 20 | push_error("Can't find Beehave Plugin") 21 | return null 22 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_erase.gd: -------------------------------------------------------------------------------- 1 | ## Erases the specified key from the blackboard. 2 | ## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. 3 | @tool 4 | class_name BlackboardEraseAction extends ActionLeaf 5 | 6 | ## Expression representing a blackboard key. 7 | @export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" 8 | 9 | @onready var _key_expression: Expression = _parse_expression(key) 10 | 11 | 12 | func tick(actor: Node, blackboard: Blackboard) -> int: 13 | var key_value: Variant = _key_expression.execute([], blackboard) 14 | 15 | if _key_expression.has_execute_failed(): 16 | return FAILURE 17 | 18 | blackboard.erase_value(key_value) 19 | 20 | return SUCCESS 21 | 22 | 23 | func _get_expression_sources() -> Array[String]: 24 | return [key] 25 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_has.gd: -------------------------------------------------------------------------------- 1 | ## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist. 2 | ## Returns [code]SUCCESS[/code] if blackboard has the specified key. 3 | @tool 4 | class_name BlackboardHasCondition extends ConditionLeaf 5 | 6 | ## Expression representing a blackboard key. 7 | @export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" 8 | 9 | @onready var _key_expression: Expression = _parse_expression(key) 10 | 11 | 12 | func tick(actor: Node, blackboard: Blackboard) -> int: 13 | var key_value: Variant = _key_expression.execute([], blackboard) 14 | 15 | if _key_expression.has_execute_failed(): 16 | return FAILURE 17 | 18 | return SUCCESS if blackboard.has_value(key_value) else FAILURE 19 | 20 | 21 | func _get_expression_sources() -> Array[String]: 22 | return [key] 23 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/decorator.gd: -------------------------------------------------------------------------------- 1 | ## Decorator nodes are used to transform the result received by its child. 2 | ## Must only have one child. 3 | @tool 4 | @icon("../../icons/category_decorator.svg") 5 | class_name Decorator extends BeehaveNode 6 | 7 | var running_child: BeehaveNode = null 8 | 9 | 10 | func _get_configuration_warnings() -> PackedStringArray: 11 | var warnings: PackedStringArray = super._get_configuration_warnings() 12 | 13 | if get_child_count() != 1: 14 | warnings.append("Decorator should have exactly one child node.") 15 | 16 | return warnings 17 | 18 | 19 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 20 | if running_child != null: 21 | running_child.interrupt(actor, blackboard) 22 | running_child = null 23 | super.interrupt(actor, blackboard) 24 | 25 | 26 | func after_run(actor: Node, blackboard: Blackboard) -> void: 27 | running_child = null 28 | 29 | 30 | func get_class_name() -> Array[StringName]: 31 | var classes := super() 32 | classes.push_back(&"Decorator") 33 | return classes 34 | -------------------------------------------------------------------------------- /addons/beehave/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 bitbrain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/failer.gd: -------------------------------------------------------------------------------- 1 | ## A Failer node will always return a `FAILURE` status code. 2 | @tool 3 | @icon("../../icons/failer.svg") 4 | class_name AlwaysFailDecorator extends Decorator 5 | 6 | 7 | func tick(actor: Node, blackboard: Blackboard) -> int: 8 | var c: BeehaveNode = get_child(0) 9 | 10 | if c != running_child: 11 | c.before_run(actor, blackboard) 12 | 13 | var response: int = c._safe_tick(actor, blackboard) 14 | if can_send_message(blackboard): 15 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 16 | 17 | if c is ConditionLeaf: 18 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 19 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 20 | 21 | if response == RUNNING: 22 | running_child = c 23 | if c is ActionLeaf: 24 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 25 | return RUNNING 26 | else: 27 | c.after_run(actor, blackboard) 28 | return FAILURE 29 | 30 | 31 | func get_class_name() -> Array[StringName]: 32 | var classes := super() 33 | classes.push_back(&"AlwaysFailDecorator") 34 | return classes 35 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/succeeder.gd: -------------------------------------------------------------------------------- 1 | ## A succeeder node will always return a `SUCCESS` status code. 2 | @tool 3 | @icon("../../icons/succeeder.svg") 4 | class_name AlwaysSucceedDecorator extends Decorator 5 | 6 | 7 | func tick(actor: Node, blackboard: Blackboard) -> int: 8 | var c: BeehaveNode = get_child(0) 9 | 10 | if c != running_child: 11 | c.before_run(actor, blackboard) 12 | 13 | var response: int = c._safe_tick(actor, blackboard) 14 | if can_send_message(blackboard): 15 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 16 | 17 | if c is ConditionLeaf: 18 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 19 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 20 | 21 | if response == RUNNING: 22 | running_child = c 23 | if c is ActionLeaf: 24 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 25 | return RUNNING 26 | else: 27 | c.after_run(actor, blackboard) 28 | return SUCCESS 29 | 30 | 31 | func get_class_name() -> Array[StringName]: 32 | var classes := super() 33 | classes.push_back(&"AlwaysSucceedDecorator") 34 | return classes 35 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_set.gd: -------------------------------------------------------------------------------- 1 | ## Sets the specified key to the specified value. 2 | ## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. 3 | @tool 4 | class_name BlackboardSetAction extends ActionLeaf 5 | 6 | ## Expression representing a blackboard key. 7 | @export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" 8 | ## Expression representing a blackboard value to assign to the specified key. 9 | @export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = "" 10 | 11 | @onready var _key_expression: Expression = _parse_expression(key) 12 | @onready var _value_expression: Expression = _parse_expression(value) 13 | 14 | 15 | func tick(actor: Node, blackboard: Blackboard) -> int: 16 | var key_value: Variant = _key_expression.execute([], blackboard) 17 | 18 | if _key_expression.has_execute_failed(): 19 | return FAILURE 20 | 21 | var value_value: Variant = _value_expression.execute([], blackboard) 22 | 23 | if _value_expression.has_execute_failed(): 24 | return FAILURE 25 | 26 | blackboard.set_value(key_value, value_value) 27 | 28 | return SUCCESS 29 | 30 | 31 | func _get_expression_sources() -> Array[String]: 32 | return [key, value] 33 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/until_fail.gd: -------------------------------------------------------------------------------- 1 | ## The UntilFail Decorator will return `RUNNING` if its child returns 2 | ## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns 3 | ## `FAILURE` 4 | @tool 5 | @icon("../../icons/until_fail.svg") 6 | class_name UntilFailDecorator 7 | extends Decorator 8 | 9 | 10 | func tick(actor: Node, blackboard: Blackboard) -> int: 11 | var c: BeehaveNode = get_child(0) 12 | 13 | if c != running_child: 14 | c.before_run(actor, blackboard) 15 | 16 | var response: int = c._safe_tick(actor, blackboard) 17 | if can_send_message(blackboard): 18 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 19 | 20 | if c is ConditionLeaf: 21 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 22 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 23 | 24 | if response == RUNNING: 25 | running_child = c 26 | if c is ActionLeaf: 27 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 28 | return RUNNING 29 | if response == SUCCESS: 30 | c.after_run(actor, blackboard) 31 | return RUNNING 32 | 33 | c.after_run(actor, blackboard) 34 | return SUCCESS 35 | -------------------------------------------------------------------------------- /addons/beehave/debug/old_frames.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends RefCounted 3 | 4 | const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") 5 | 6 | const SUCCESS_COLOR := Color("#009944c8") 7 | const NORMAL_COLOR := Color("#15181e") 8 | const FAILURE_COLOR := Color("#cf000f80") 9 | const RUNNING_COLOR := Color("#ffcc00c8") 10 | 11 | var empty: StyleBoxEmpty 12 | var normal: StyleBoxFlat 13 | var success: StyleBoxFlat 14 | var failure: StyleBoxFlat 15 | var running: StyleBoxFlat 16 | 17 | 18 | func _init() -> void: 19 | var plugin := BeehaveUtils.get_plugin() 20 | if not plugin: 21 | return 22 | 23 | var editor_scale := BeehaveUtils.get_editor_scale() 24 | 25 | empty = StyleBoxEmpty.new() 26 | 27 | normal = ( 28 | plugin 29 | . get_editor_interface() 30 | . get_base_control() 31 | . get_theme_stylebox(&"frame", &"GraphNode") 32 | . duplicate() 33 | ) 34 | 35 | success = ( 36 | plugin 37 | . get_editor_interface() 38 | . get_base_control() 39 | . get_theme_stylebox(&"selected_frame", &"GraphNode") 40 | . duplicate() 41 | ) 42 | failure = success.duplicate() 43 | running = success.duplicate() 44 | 45 | success.border_color = SUCCESS_COLOR 46 | failure.border_color = FAILURE_COLOR 47 | running.border_color = RUNNING_COLOR 48 | -------------------------------------------------------------------------------- /addons/beehave/icons/tree.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://deryyg2hbmaaw" 6 | path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/tree.svg" 15 | dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/action.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://btrq8e0kyxthg" 6 | path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/action.svg" 15 | dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/failer.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://2fj7htaqvcud" 6 | path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/failer.svg" 15 | dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/inverter.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cffmoc3og8hux" 6 | path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/inverter.svg" 15 | dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/limiter.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c7akxvsg0f2by" 6 | path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/limiter.svg" 15 | dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/selector.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b2c5d20doh4sp" 6 | path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/selector.svg" 15 | dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c5gw354thiofm" 6 | path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/sequence.svg" 15 | dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/condition.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ck4toqx0nggiu" 6 | path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/condition.svg" 15 | dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/succeeder.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dl6wo332kglbe" 6 | path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/succeeder.svg" 15 | dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/blackboard.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dw7rom0hiff6c" 6 | path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/blackboard.svg" 15 | dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_top.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bw8wmxdfom8eh" 6 | path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/port_top.svg" 15 | dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_bt.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://qpdd6ue7x82h" 6 | path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/category_bt.svg" 15 | dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_left.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bnufc8p6spdtn" 6 | path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/port_left.svg" 15 | dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_right.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bbmd6vk23ympm" 6 | path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/port_right.svg" 15 | dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_leaf.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://eq0sp4g3s75r" 6 | path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/category_leaf.svg" 15 | dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/port_bottom.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://da3b236rjbqns" 6 | path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/port_bottom.svg" 15 | dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/selector_random.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bmnkcmk7bkdjd" 6 | path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/selector_random.svg" 15 | dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence_random.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bat8ptdw5qt1d" 6 | path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/sequence_random.svg" 15 | dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence_reactive.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://rmiu1slwfkh7" 6 | path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/sequence_reactive.svg" 15 | dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/vertical_layout.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bpyxu6i1dx5qh" 6 | path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/vertical_layout.svg" 15 | dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_composite.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://863s568sneja" 6 | path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/category_composite.svg" 15 | dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_decorator.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c2ie8m4ddawlb" 6 | path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/category_decorator.svg" 15 | dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/icons/selector_reactive.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://crkbov0h8sb8l" 6 | path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/icons/selector_reactive.svg" 15 | dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/debug/icons/horizontal_layout.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bah77esichnyx" 6 | path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex" 7 | metadata={ 8 | "has_editor_variant": true, 9 | "vram_texture": false 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://addons/beehave/debug/icons/horizontal_layout.svg" 15 | dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=0 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=false 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=1 42 | svg/scale=1.0 43 | editor/scale_with_editor_scale=true 44 | editor/convert_colors_with_editor_theme=true 45 | -------------------------------------------------------------------------------- /addons/beehave/metrics/beehave_global_metrics.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var _tree_count: int = 0 4 | var _active_tree_count: int = 0 5 | var _registered_trees: Array = [] 6 | 7 | 8 | func _enter_tree() -> void: 9 | Performance.add_custom_monitor("beehave/total_trees", _get_total_trees) 10 | Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees) 11 | 12 | 13 | func register_tree(tree) -> void: 14 | if _registered_trees.has(tree): 15 | return 16 | 17 | _registered_trees.append(tree) 18 | _tree_count += 1 19 | 20 | if tree.enabled: 21 | _active_tree_count += 1 22 | 23 | tree.tree_enabled.connect(_on_tree_enabled) 24 | tree.tree_disabled.connect(_on_tree_disabled) 25 | 26 | 27 | func unregister_tree(tree) -> void: 28 | if not _registered_trees.has(tree): 29 | return 30 | 31 | _registered_trees.erase(tree) 32 | _tree_count -= 1 33 | 34 | if tree.enabled: 35 | _active_tree_count -= 1 36 | 37 | tree.tree_enabled.disconnect(_on_tree_enabled) 38 | tree.tree_disabled.disconnect(_on_tree_disabled) 39 | 40 | 41 | func _get_total_trees() -> int: 42 | return _tree_count 43 | 44 | 45 | func _get_total_enabled_trees() -> int: 46 | return _active_tree_count 47 | 48 | 49 | func _on_tree_enabled() -> void: 50 | _active_tree_count += 1 51 | 52 | 53 | func _on_tree_disabled() -> void: 54 | _active_tree_count -= 1 55 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger_messages.gd: -------------------------------------------------------------------------------- 1 | class_name BeehaveDebuggerMessages 2 | 3 | 4 | static func can_send_message() -> bool: 5 | return not Engine.is_editor_hint() and OS.has_feature("editor") 6 | 7 | 8 | static func register_tree(beehave_tree: Dictionary) -> void: 9 | if can_send_message(): 10 | EngineDebugger.send_message("beehave:register_tree", [beehave_tree]) 11 | 12 | 13 | static func unregister_tree(instance_id: int) -> void: 14 | if can_send_message(): 15 | EngineDebugger.send_message("beehave:unregister_tree", [instance_id]) 16 | 17 | 18 | static func process_tick(instance_id: int, status: int, blackboard: Dictionary = {}) -> void: 19 | if can_send_message(): 20 | EngineDebugger.send_message("beehave:process_tick", [instance_id, status, blackboard]) 21 | 22 | static func process_interrupt(instance_id: int, blackboard: Dictionary = {}) -> void: 23 | if can_send_message(): 24 | EngineDebugger.send_message("beehave:process_interrupt", [instance_id, blackboard]) 25 | 26 | static func process_begin(instance_id: int, blackboard: Dictionary = {}) -> void: 27 | if can_send_message(): 28 | EngineDebugger.send_message("beehave:process_begin", [instance_id, blackboard]) 29 | 30 | 31 | static func process_end(instance_id: int, blackboard: Dictionary = {}) -> void: 32 | if can_send_message(): 33 | EngineDebugger.send_message("beehave:process_end", [instance_id, blackboard]) 34 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/inverter.gd: -------------------------------------------------------------------------------- 1 | ## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status 2 | ## code or `SUCCESS` in case its child returns a `FAILURE` status code. 3 | @tool 4 | @icon("../../icons/inverter.svg") 5 | class_name InverterDecorator extends Decorator 6 | 7 | 8 | func tick(actor: Node, blackboard: Blackboard) -> int: 9 | var c: BeehaveNode = get_child(0) 10 | 11 | if c != running_child: 12 | c.before_run(actor, blackboard) 13 | 14 | var response: int = c._safe_tick(actor, blackboard) 15 | if can_send_message(blackboard): 16 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 17 | 18 | if c is ConditionLeaf: 19 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 20 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 21 | 22 | match response: 23 | SUCCESS: 24 | c.after_run(actor, blackboard) 25 | return FAILURE 26 | FAILURE: 27 | c.after_run(actor, blackboard) 28 | return SUCCESS 29 | RUNNING: 30 | running_child = c 31 | if c is ActionLeaf: 32 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 33 | return RUNNING 34 | _: 35 | push_error("This should be unreachable") 36 | return -1 37 | 38 | 39 | func get_class_name() -> Array[StringName]: 40 | var classes := super() 41 | classes.push_back(&"InverterDecorator") 42 | return classes 43 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/leaf.gd: -------------------------------------------------------------------------------- 1 | ## Base class for all leaf nodes of the tree. 2 | @tool 3 | @icon("../../icons/category_leaf.svg") 4 | class_name Leaf extends BeehaveNode 5 | 6 | const EXPRESSION_PLACEHOLDER: String = "Insert an expression..." 7 | 8 | 9 | func _get_configuration_warnings() -> PackedStringArray: 10 | var warnings: PackedStringArray = [] 11 | 12 | var children: Array[Node] = get_children() 13 | 14 | if children.any(func(x): return x is BeehaveNode): 15 | warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.") 16 | 17 | for source in _get_expression_sources(): 18 | var error_text: String = _parse_expression(source).get_error_text() 19 | if not error_text.is_empty(): 20 | warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text]) 21 | 22 | return warnings 23 | 24 | 25 | func _parse_expression(source: String) -> Expression: 26 | var result: Expression = Expression.new() 27 | var error: int = result.parse(source) 28 | 29 | if not Engine.is_editor_hint() and error != OK: 30 | push_error( 31 | ( 32 | "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" 33 | % [source, result.get_error_text()] 34 | ) 35 | ) 36 | 37 | return result 38 | 39 | 40 | func _get_expression_sources() -> Array[String]: # virtual 41 | return [] 42 | 43 | 44 | func get_class_name() -> Array[StringName]: 45 | var classes := super() 46 | classes.push_back(&"Leaf") 47 | return classes 48 | -------------------------------------------------------------------------------- /addons/beehave/icons/cooldown.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/blackboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/delayer.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /addons/beehave/nodes/beehave_node.gd: -------------------------------------------------------------------------------- 1 | ## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or 2 | ## `RUNNING` when ticked. 3 | @tool 4 | class_name BeehaveNode extends Node 5 | 6 | enum { SUCCESS, FAILURE, RUNNING } 7 | 8 | 9 | func _get_configuration_warnings() -> PackedStringArray: 10 | var warnings: PackedStringArray = [] 11 | 12 | if get_children().any(func(x): return not (x is BeehaveNode)): 13 | warnings.append("All children of this node should inherit from BeehaveNode class.") 14 | 15 | return warnings 16 | 17 | 18 | ## Executes this node and returns a status code. 19 | ## This method must be overwritten. 20 | func tick(actor: Node, blackboard: Blackboard) -> int: 21 | return SUCCESS 22 | 23 | 24 | ## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS. 25 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 26 | BeehaveDebuggerMessages.process_interrupt(self.get_instance_id(), blackboard.get_debug_data()) 27 | 28 | 29 | ## Called before the first time it ticks by the parent. 30 | func before_run(actor: Node, blackboard: Blackboard) -> void: 31 | pass 32 | 33 | 34 | ## Called after the last time it ticks and returns 35 | ## [code]SUCCESS[/code] or [code]FAILURE[/code]. 36 | func after_run(actor: Node, blackboard: Blackboard) -> void: 37 | pass 38 | 39 | 40 | func get_class_name() -> Array[StringName]: 41 | return [&"BeehaveNode"] 42 | 43 | 44 | func can_send_message(blackboard: Blackboard) -> bool: 45 | return blackboard.get_value("can_send_message", false) 46 | 47 | 48 | func _safe_tick(actor: Node, blackboard: Blackboard) -> int: 49 | var response = tick(actor, blackboard) 50 | if not response is int: 51 | push_error("All tick methods must return an int, got %s" % response) 52 | return FAILURE 53 | return response 54 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/composite.gd: -------------------------------------------------------------------------------- 1 | ## A Composite node controls the flow of execution of its children in a specific manner. 2 | @tool 3 | @icon("../../icons/category_composite.svg") 4 | class_name Composite extends BeehaveNode 5 | 6 | 7 | var running_child: BeehaveNode = null 8 | 9 | 10 | func _get_configuration_warnings() -> PackedStringArray: 11 | var warnings: PackedStringArray = super._get_configuration_warnings() 12 | 13 | if get_children().filter(func(x): return x is BeehaveNode).size() < 2: 14 | warnings.append( 15 | "Any composite node should have at least two children. Otherwise it is not useful." 16 | ) 17 | 18 | return warnings 19 | 20 | 21 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 22 | if running_child != null: 23 | running_child.interrupt(actor, blackboard) 24 | running_child = null 25 | super.interrupt(actor, blackboard) 26 | 27 | 28 | func after_run(actor: Node, blackboard: Blackboard) -> void: 29 | running_child = null 30 | 31 | 32 | func get_class_name() -> Array[StringName]: 33 | var classes := super () 34 | classes.push_back(&"Composite") 35 | return classes 36 | 37 | 38 | func _cleanup_running(child: Node, actor: Node, blackboard: Blackboard) -> void: 39 | if child == running_child: 40 | running_child = null 41 | var id = str(actor.get_instance_id()) 42 | if child == blackboard.get_value("running_action", null, id): 43 | blackboard.set_value("running_action", null, id) 44 | 45 | 46 | func _interrupt_children(actor: Node, blackboard: Blackboard, from_index: int, last_index: int) -> void: 47 | var children = get_children() 48 | var start = from_index + 1 49 | var end = 0 50 | if last_index > from_index: 51 | end = last_index + 1 52 | else: 53 | return 54 | 55 | for j in range(start, end): 56 | var stale = children[j] 57 | stale.interrupt(actor, blackboard) 58 | -------------------------------------------------------------------------------- /addons/beehave/icons/succeeder.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_frames.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends RefCounted 3 | 4 | 5 | const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") 6 | 7 | 8 | const SUCCESS_COLOR := Color("#07783a") 9 | const NORMAL_COLOR := Color("#15181e") 10 | const FAILURE_COLOR := Color("#82010b") 11 | const RUNNING_COLOR := Color("#c29c06") 12 | 13 | var panel_normal: StyleBoxFlat 14 | var panel_success: StyleBoxFlat 15 | var panel_failure: StyleBoxFlat 16 | var panel_running: StyleBoxFlat 17 | 18 | var titlebar_normal: StyleBoxFlat 19 | var titlebar_success: StyleBoxFlat 20 | var titlebar_failure: StyleBoxFlat 21 | var titlebar_running: StyleBoxFlat 22 | 23 | 24 | func _init() -> void: 25 | var plugin := BeehaveUtils.get_plugin() 26 | if not plugin: 27 | return 28 | 29 | 30 | titlebar_normal = ( 31 | plugin 32 | .get_editor_interface() 33 | .get_base_control() 34 | .get_theme_stylebox(&"titlebar", &"GraphNode")\ 35 | .duplicate() 36 | ) 37 | titlebar_success = titlebar_normal.duplicate() 38 | titlebar_failure = titlebar_normal.duplicate() 39 | titlebar_running = titlebar_normal.duplicate() 40 | 41 | titlebar_success.bg_color = SUCCESS_COLOR 42 | titlebar_failure.bg_color = FAILURE_COLOR 43 | titlebar_running.bg_color = RUNNING_COLOR 44 | 45 | titlebar_success.border_color = SUCCESS_COLOR 46 | titlebar_failure.border_color = FAILURE_COLOR 47 | titlebar_running.border_color = RUNNING_COLOR 48 | 49 | 50 | panel_normal = ( 51 | plugin 52 | .get_editor_interface() 53 | .get_base_control() 54 | .get_theme_stylebox(&"panel", &"GraphNode") 55 | .duplicate() 56 | ) 57 | panel_success = ( 58 | plugin 59 | .get_editor_interface() 60 | .get_base_control() 61 | .get_theme_stylebox(&"panel_selected", &"GraphNode") 62 | .duplicate() 63 | ) 64 | panel_failure = panel_success.duplicate() 65 | panel_running = panel_success.duplicate() 66 | 67 | panel_success.border_color = SUCCESS_COLOR 68 | panel_failure.border_color = FAILURE_COLOR 69 | panel_running.border_color = RUNNING_COLOR 70 | -------------------------------------------------------------------------------- /addons/beehave/icons/limiter.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/failer.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/delayer.gd: -------------------------------------------------------------------------------- 1 | ## The Delay Decorator will return 'RUNNING' for a set amount of time 2 | ## before executing its child. 3 | ## The timer resets when both it and its child are not `RUNNING` 4 | ## or when the node is interrupted (such as when the behavior tree changes branches). 5 | @tool 6 | @icon("../../icons/delayer.svg") 7 | extends Decorator 8 | class_name DelayDecorator 9 | 10 | ## The wait time in seconds 11 | @export var wait_time := 0.0 12 | 13 | @onready var cache_key = "time_limiter_%s" % self.get_instance_id() 14 | 15 | 16 | func tick(actor: Node, blackboard: Blackboard) -> int: 17 | var c: BeehaveNode = get_child(0) 18 | var actor_id := str(actor.get_instance_id()) 19 | var total_time: float = blackboard.get_value(cache_key, 0.0, actor_id) 20 | var response: int 21 | 22 | if total_time < wait_time: 23 | response = RUNNING 24 | 25 | total_time += get_physics_process_delta_time() 26 | blackboard.set_value(cache_key, total_time, actor_id) 27 | 28 | if can_send_message(blackboard): 29 | BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response, blackboard.get_debug_data()) 30 | else: 31 | if c != running_child: 32 | c.before_run(actor, blackboard) 33 | 34 | response = c._safe_tick(actor, blackboard) 35 | 36 | if can_send_message(blackboard): 37 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 38 | 39 | if c is ConditionLeaf: 40 | blackboard.set_value("last_condition", c, actor_id) 41 | blackboard.set_value("last_condition_status", response, actor_id) 42 | 43 | if response == RUNNING: 44 | running_child = c 45 | if c is ActionLeaf: 46 | blackboard.set_value("running_action", c, actor_id) 47 | else: 48 | c.after_run(actor, blackboard) 49 | blackboard.set_value(cache_key, 0.0, actor_id) 50 | 51 | return response 52 | 53 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 54 | # Reset the delay timer when the branch changes 55 | blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) 56 | 57 | super(actor, blackboard) 58 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/cooldown.gd: -------------------------------------------------------------------------------- 1 | ## The Cooldown Decorator will return 'FAILURE' for a set amount of time 2 | ## after executing its child. 3 | ## The timer resets the next time its child is executed and it is not `RUNNING` 4 | ## or when the node is interrupted (such as when the behavior tree changes branches). 5 | @tool 6 | @icon("../../icons/cooldown.svg") 7 | extends Decorator 8 | class_name CooldownDecorator 9 | 10 | ## The wait time in seconds 11 | @export var wait_time := 0.0 12 | 13 | @onready var cache_key = "cooldown_%s" % self.get_instance_id() 14 | 15 | 16 | func tick(actor: Node, blackboard: Blackboard) -> int: 17 | var c: BeehaveNode = get_child(0) 18 | var actor_id := str(actor.get_instance_id()) 19 | var end_time: float = blackboard.get_value(cache_key, 0.0, actor_id) 20 | var current_time = Time.get_ticks_msec() 21 | var response: int 22 | 23 | if current_time < end_time: 24 | response = FAILURE 25 | 26 | if can_send_message(blackboard): 27 | BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response, blackboard.get_debug_data()) 28 | else: 29 | if c != running_child: 30 | c.before_run(actor, blackboard) 31 | 32 | response = c._safe_tick(actor, blackboard) 33 | 34 | if can_send_message(blackboard): 35 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 36 | 37 | if c is ConditionLeaf: 38 | blackboard.set_value("last_condition", c, actor_id) 39 | blackboard.set_value("last_condition_status", response, actor_id) 40 | 41 | if response == RUNNING: 42 | running_child = c 43 | if c is ActionLeaf: 44 | blackboard.set_value("running_action", c, actor_id) 45 | else: 46 | end_time = Time.get_ticks_msec() + wait_time * 1000 47 | c.after_run(actor, blackboard) 48 | blackboard.set_value(cache_key, end_time, actor_id) 49 | 50 | return response 51 | 52 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 53 | # Reset the cooldown when the branch changes 54 | blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) 55 | super.interrupt(actor, blackboard) -------------------------------------------------------------------------------- /addons/beehave/icons/inverter.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/repeater.gd: -------------------------------------------------------------------------------- 1 | ## The repeater will execute its child until it returns `SUCCESS` a certain amount of times. 2 | ## When the number of maximum ticks is reached, it will return a `SUCCESS` status code. 3 | ## If the child returns `FAILURE`, the repeater will return `FAILURE` immediately. 4 | ## The counter resets when the node is interrupted (such as when the behavior tree changes branches). 5 | @tool 6 | @icon("../../icons/repeater.svg") 7 | class_name RepeaterDecorator extends Decorator 8 | 9 | @export var repetitions: int = 1 10 | var current_count: int = 0 11 | 12 | 13 | func before_run(actor: Node, blackboard: Blackboard): 14 | current_count = 0 15 | 16 | 17 | func tick(actor: Node, blackboard: Blackboard) -> int: 18 | var child: BeehaveNode = get_child(0) 19 | 20 | if current_count < repetitions: 21 | if running_child == null: 22 | child.before_run(actor, blackboard) 23 | 24 | var response: int = child.tick(actor, blackboard) 25 | 26 | if can_send_message(blackboard): 27 | BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) 28 | 29 | if child is ConditionLeaf: 30 | blackboard.set_value("last_condition", child, str(actor.get_instance_id())) 31 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 32 | 33 | if response == RUNNING: 34 | running_child = child 35 | if child is ActionLeaf: 36 | blackboard.set_value("running_action", child, str(actor.get_instance_id())) 37 | return RUNNING 38 | 39 | current_count += 1 40 | child.after_run(actor, blackboard) 41 | 42 | if running_child != null: 43 | running_child = null 44 | 45 | if response == FAILURE: 46 | return FAILURE 47 | 48 | if current_count >= repetitions: 49 | return SUCCESS 50 | 51 | return RUNNING 52 | else: 53 | return SUCCESS 54 | 55 | 56 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 57 | # Reset the internal counter when the node is interrupted 58 | current_count = 0 59 | 60 | super(actor, blackboard) 61 | 62 | 63 | func get_class_name() -> Array[StringName]: 64 | var classes := super() 65 | classes.push_back(&"LimiterDecorator") 66 | return classes 67 | -------------------------------------------------------------------------------- /addons/beehave/nodes/leaves/blackboard_compare.gd: -------------------------------------------------------------------------------- 1 | ## Compares two values using the specified comparison operator. 2 | ## Returns [code]FAILURE[/code] if any of the expression fails or the 3 | ## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code]. 4 | @tool 5 | class_name BlackboardCompareCondition extends ConditionLeaf 6 | 7 | enum Operators { 8 | EQUAL, 9 | NOT_EQUAL, 10 | GREATER, 11 | LESS, 12 | GREATER_EQUAL, 13 | LESS_EQUAL, 14 | } 15 | 16 | ## Expression represetning left operand. 17 | ## This value can be any valid GDScript expression. 18 | ## In order to use the existing blackboard keys for comparison, 19 | ## use get_value("key_name") e.g. get_value("direction").length() 20 | @export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = "" 21 | ## Comparison operator. 22 | @export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0 23 | ## Expression represetning right operand. 24 | ## This value can be any valid GDScript expression. 25 | ## In order to use the existing blackboard keys for comparison, 26 | ## use get_value("key_name") e.g. get_value("direction").length() 27 | @export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = "" 28 | 29 | @onready var _left_expression: Expression = _parse_expression(left_operand) 30 | @onready var _right_expression: Expression = _parse_expression(right_operand) 31 | 32 | 33 | func tick(actor: Node, blackboard: Blackboard) -> int: 34 | var left: Variant = _left_expression.execute([], blackboard) 35 | 36 | if _left_expression.has_execute_failed(): 37 | return FAILURE 38 | 39 | var right: Variant = _right_expression.execute([], blackboard) 40 | 41 | if _right_expression.has_execute_failed(): 42 | return FAILURE 43 | 44 | var result: bool = false 45 | 46 | match operator: 47 | Operators.EQUAL: 48 | result = left == right 49 | Operators.NOT_EQUAL: 50 | result = left != right 51 | Operators.GREATER: 52 | result = left > right 53 | Operators.LESS: 54 | result = left < right 55 | Operators.GREATER_EQUAL: 56 | result = left >= right 57 | Operators.LESS_EQUAL: 58 | result = left <= right 59 | 60 | return SUCCESS if result else FAILURE 61 | 62 | 63 | func _get_expression_sources() -> Array[String]: 64 | return [left_operand, right_operand] 65 | -------------------------------------------------------------------------------- /addons/beehave/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const BeehaveEditorDebugger := preload("debug/debugger.gd") 5 | var editor_debugger: BeehaveEditorDebugger 6 | var frames: RefCounted 7 | var _custom_types: Array[StringName] = [] 8 | 9 | 10 | func _init(): 11 | name = "BeehavePlugin" 12 | add_autoload_singleton("BeehaveGlobalMetrics", "metrics/beehave_global_metrics.gd") 13 | add_autoload_singleton("BeehaveGlobalDebugger", "debug/global_debugger.gd") 14 | 15 | # Add project settings 16 | if not ProjectSettings.has_setting("beehave/debugger/start_detached"): 17 | ProjectSettings.set_setting("beehave/debugger/start_detached", false) 18 | ProjectSettings.set_initial_value("beehave/debugger/start_detached", false) 19 | ProjectSettings.add_property_info({ 20 | "name": "beehave/debugger/start_detached", 21 | "type": TYPE_BOOL, 22 | "hint": PROPERTY_HINT_NONE, 23 | "hint_string": "If enabled, the debugger will start in a separate window" 24 | }) 25 | ProjectSettings.save() 26 | 27 | print("Beehave initialized!") 28 | 29 | 30 | func _enter_tree() -> void: 31 | editor_debugger = BeehaveEditorDebugger.new() 32 | if Engine.get_version_info().minor >= 2: 33 | frames = preload("debug/new_frames.gd").new() 34 | else: 35 | frames = preload("debug/old_frames.gd").new() 36 | add_debugger_plugin(editor_debugger) 37 | _register_custom_types() 38 | 39 | 40 | func _exit_tree() -> void: 41 | _unregister_custom_types() 42 | remove_debugger_plugin(editor_debugger) 43 | 44 | 45 | func _register_custom_types() -> void: 46 | # Some users report the script-class cache randomly missing BeehaveTree, 47 | # which makes the editor return a null instance. Register the type manually 48 | # so the node is still available even if the cache is broken. 49 | var tree_script: Script = load("res://addons/beehave/nodes/beehave_tree.gd") 50 | if tree_script == null: 51 | push_warning("Beehave: failed to load BeehaveTree script; custom type not registered.") 52 | return 53 | 54 | var tree_icon: Texture2D = load("res://addons/beehave/icons/tree.svg") 55 | add_custom_type("BeehaveTree", "Node", tree_script, tree_icon) 56 | _custom_types.append(&"BeehaveTree") 57 | 58 | 59 | func _unregister_custom_types() -> void: 60 | for type_name in _custom_types: 61 | remove_custom_type(type_name) 62 | _custom_types.clear() 63 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/selector.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/condition.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/time_limiter.gd: -------------------------------------------------------------------------------- 1 | ## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish 2 | ## before interrupting it and return a `FAILURE` status code. 3 | ## The timer resets the next time that a child is not `RUNNING` 4 | ## or when the node is interrupted (such as when the behavior tree changes branches). 5 | @tool 6 | @icon("../../icons/limiter.svg") 7 | class_name TimeLimiterDecorator extends Decorator 8 | 9 | @export var wait_time := 0.0 10 | 11 | @onready var cache_key: String = "time_limiter_%s" % self.get_instance_id() 12 | 13 | 14 | func tick(actor: Node, blackboard: Blackboard) -> int: 15 | if not get_child_count() == 1: 16 | return FAILURE 17 | 18 | var child: BeehaveNode = self.get_child(0) 19 | var time_left: float = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) 20 | 21 | if time_left < wait_time: 22 | time_left += get_physics_process_delta_time() 23 | blackboard.set_value(cache_key, time_left, str(actor.get_instance_id())) 24 | var response: int = child.tick(actor, blackboard) 25 | if can_send_message(blackboard): 26 | BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) 27 | 28 | if child is ConditionLeaf: 29 | blackboard.set_value("last_condition", child, str(actor.get_instance_id())) 30 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 31 | 32 | if response == RUNNING: 33 | running_child = child 34 | if child is ActionLeaf: 35 | blackboard.set_value("running_action", child, str(actor.get_instance_id())) 36 | else: 37 | child.after_run(actor, blackboard) 38 | return response 39 | else: 40 | interrupt(actor, blackboard) 41 | child.after_run(actor, blackboard) 42 | return FAILURE 43 | 44 | 45 | func before_run(actor: Node, blackboard: Blackboard) -> void: 46 | blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) 47 | if get_child_count() > 0: 48 | get_child(0).before_run(actor, blackboard) 49 | 50 | 51 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 52 | # Reset the timer when the node is interrupted 53 | blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) 54 | 55 | super(actor, blackboard) 56 | 57 | 58 | func get_class_name() -> Array[StringName]: 59 | var classes := super() 60 | classes.push_back(&"TimeLimiterDecorator") 61 | return classes 62 | 63 | 64 | func _get_configuration_warnings() -> PackedStringArray: 65 | if not get_child_count() == 1: 66 | return ["Requires exactly one child node"] 67 | return [] 68 | -------------------------------------------------------------------------------- /addons/beehave/debug/global_debugger.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var _registered_trees: Dictionary 4 | var _active_tree 5 | var _pending_activation_id: int = -1 # Store the ID if activation arrives before registration 6 | var _editor_visible: bool = false # Track editor visibility 7 | 8 | 9 | func _enter_tree() -> void: 10 | EngineDebugger.register_message_capture("beehave", _on_debug_message) 11 | 12 | 13 | func _on_debug_message(message: String, data: Array) -> bool: 14 | match message: 15 | "activate_tree": 16 | var requested_id: int = data[0] 17 | _set_active_tree(requested_id) 18 | return true 19 | "visibility_changed": 20 | _editor_visible = data[0] 21 | if _active_tree and is_instance_valid(_active_tree): 22 | # Only update _can_send_message based on editor visibility 23 | _active_tree._can_send_message = _editor_visible 24 | elif _pending_activation_id != -1: 25 | # If we have a pending activation and visibility changes, try to activate it again 26 | _set_active_tree(_pending_activation_id) 27 | return true 28 | return false 29 | 30 | 31 | func _set_active_tree(tree_id: int) -> void: 32 | var tree = _registered_trees.get(tree_id, null) 33 | 34 | if tree and is_instance_valid(tree): 35 | # Tree found, proceed with activation 36 | if _active_tree and is_instance_valid(_active_tree): 37 | _active_tree._can_send_message = false # Deactivate old one 38 | _active_tree = tree 39 | # Activate the new tree ONLY if the editor debugger tab is actually visible 40 | _active_tree._can_send_message = _editor_visible 41 | 42 | # If this was the pending ID, clear it 43 | if _pending_activation_id == tree_id: 44 | _pending_activation_id = -1 45 | else: 46 | # Tree not found (yet), mark it as pending activation 47 | _pending_activation_id = tree_id 48 | 49 | if _active_tree and is_instance_valid(_active_tree): 50 | _active_tree._can_send_message = false 51 | _active_tree = null # No tree is active now 52 | 53 | 54 | func register_tree(tree) -> void: 55 | var tree_id = tree.get_instance_id() 56 | _registered_trees[tree_id] = tree 57 | 58 | # Check if this tree was waiting for activation 59 | if tree_id == _pending_activation_id: 60 | # Found the pending tree, activate it now 61 | _set_active_tree(tree_id) # This will set _active_tree and handle _can_send_message 62 | 63 | 64 | func unregister_tree(tree) -> void: 65 | var tree_id = tree.get_instance_id() 66 | _registered_trees.erase(tree_id) 67 | if _active_tree == tree: 68 | _active_tree = null 69 | if _pending_activation_id == tree_id: 70 | _pending_activation_id = -1 71 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector_random.gd: -------------------------------------------------------------------------------- 1 | ## This node will attempt to execute all of its children just like a 2 | ## [code]SelectorStar[/code] would, with the exception that the children 3 | ## will be executed in a random order. 4 | @tool 5 | @icon("../../icons/selector_random.svg") 6 | class_name SelectorRandomComposite extends RandomizedComposite 7 | 8 | ## A shuffled list of the children that will be executed in reverse order. 9 | var _children_bag: Array[Node] = [] 10 | var c: Node 11 | 12 | 13 | func _ready() -> void: 14 | super() 15 | if random_seed == 0: 16 | randomize() 17 | 18 | 19 | func tick(actor: Node, blackboard: Blackboard) -> int: 20 | if _children_bag.is_empty(): 21 | _reset() 22 | 23 | # We need to traverse the array in reverse since we will be manipulating it. 24 | for i in _get_reversed_indexes(): 25 | c = _children_bag[i] 26 | 27 | if c != running_child: 28 | c.before_run(actor, blackboard) 29 | 30 | var response: int = c._safe_tick(actor, blackboard) 31 | if can_send_message(blackboard): 32 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 33 | 34 | if c is ConditionLeaf: 35 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 36 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 37 | 38 | match response: 39 | SUCCESS: 40 | _children_bag.erase(c) 41 | c.after_run(actor, blackboard) 42 | return SUCCESS 43 | FAILURE: 44 | _children_bag.erase(c) 45 | c.after_run(actor, blackboard) 46 | RUNNING: 47 | if c != running_child: 48 | if running_child != null: 49 | running_child.interrupt(actor, blackboard) 50 | running_child = c 51 | if c is ActionLeaf: 52 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 53 | return RUNNING 54 | 55 | return FAILURE 56 | 57 | 58 | func after_run(actor: Node, blackboard: Blackboard) -> void: 59 | _reset() 60 | super(actor, blackboard) 61 | 62 | 63 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 64 | _reset() 65 | super(actor, blackboard) 66 | 67 | 68 | func _get_reversed_indexes() -> Array[int]: 69 | var reversed: Array[int] 70 | reversed.assign(range(_children_bag.size())) 71 | reversed.reverse() 72 | return reversed 73 | 74 | 75 | func _reset() -> void: 76 | var new_order = get_shuffled_children() 77 | _children_bag = new_order.duplicate() 78 | _children_bag.reverse() # It needs to run the children in reverse order. 79 | 80 | 81 | func get_class_name() -> Array[StringName]: 82 | var classes := super() 83 | classes.push_back(&"SelectorRandomComposite") 84 | return classes 85 | -------------------------------------------------------------------------------- /addons/beehave/icons/simple_parallel.svg: -------------------------------------------------------------------------------- 1 | 2 | simple_parallel 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /addons/beehave/nodes/decorators/limiter.gd: -------------------------------------------------------------------------------- 1 | ## The limiter will execute its `RUNNING` child `x` amount of times. When the number of 2 | ## maximum ticks is reached, it will return a `FAILURE` status code. 3 | ## The count resets the next time that a child is not `RUNNING` 4 | ## or when the node is interrupted (such as when the behavior tree changes branches). 5 | @tool 6 | @icon("../../icons/limiter.svg") 7 | class_name LimiterDecorator extends Decorator 8 | 9 | @onready var cache_key = "limiter_%s" % self.get_instance_id() 10 | 11 | @export var max_count: int = 0 12 | 13 | 14 | func tick(actor: Node, blackboard: Blackboard) -> int: 15 | if not get_child_count() == 1: 16 | return FAILURE 17 | 18 | var child: BeehaveNode = get_child(0) 19 | var current_count: int = blackboard.get_value(cache_key, 0, str(actor.get_instance_id())) 20 | 21 | if current_count < max_count: 22 | blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id())) 23 | var response: int = child.tick(actor, blackboard) 24 | if can_send_message(blackboard): 25 | BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) 26 | 27 | if child is ConditionLeaf: 28 | blackboard.set_value("last_condition", child, str(actor.get_instance_id())) 29 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 30 | 31 | if response == RUNNING: 32 | running_child = child 33 | if child is ActionLeaf: 34 | blackboard.set_value("running_action", child, str(actor.get_instance_id())) 35 | else: 36 | # If the child is no longer running, reset the counter for next time 37 | _reset_counter(actor, blackboard) 38 | child.after_run(actor, blackboard) 39 | 40 | return response 41 | else: 42 | interrupt(actor, blackboard) 43 | child.after_run(actor, blackboard) 44 | return FAILURE 45 | 46 | 47 | func before_run(actor: Node, blackboard: Blackboard) -> void: 48 | # Initialize the counter to 0 when we first start running 49 | _reset_counter(actor, blackboard) 50 | if get_child_count() > 0: 51 | get_child(0).before_run(actor, blackboard) 52 | 53 | 54 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 55 | # The tree is changing branches, so the count should reset 56 | _reset_counter(actor, blackboard) 57 | 58 | # Call super, which may affect our blackboard values 59 | super(actor, blackboard) 60 | 61 | 62 | # Resets the counter in the blackboard 63 | func _reset_counter(actor: Node, blackboard: Blackboard) -> void: 64 | blackboard.set_value(cache_key, 0, str(actor.get_instance_id())) 65 | 66 | 67 | func get_class_name() -> Array[StringName]: 68 | var classes := super() 69 | classes.push_back(&"LimiterDecorator") 70 | return classes 71 | 72 | 73 | func _get_configuration_warnings() -> PackedStringArray: 74 | if not get_child_count() == 1: 75 | return ["Requires exactly one child node"] 76 | return [] 77 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_node_blackboard.gd: -------------------------------------------------------------------------------- 1 | extends VBoxContainer 2 | 3 | var frames: RefCounted 4 | var graph_node: GraphNode 5 | 6 | var item_tree: Tree 7 | 8 | func _init(frames: RefCounted, node: GraphNode) -> void: 9 | self.frames = frames 10 | graph_node = node 11 | 12 | graph_node.blackboard_updated.connect(_update_list) 13 | 14 | func _ready() -> void: 15 | name = graph_node.name 16 | 17 | set_anchors_preset(Control.PRESET_FULL_RECT) 18 | size_flags_horizontal = Control.SIZE_EXPAND_FILL 19 | size_flags_vertical = Control.SIZE_EXPAND_FILL 20 | 21 | var title_panel: Panel = Panel.new() 22 | title_panel.set_anchors_preset(Control.PRESET_FULL_RECT) 23 | title_panel.custom_minimum_size = Vector2(200, 50) 24 | add_child(title_panel) 25 | var title_hbox: HBoxContainer = HBoxContainer.new() 26 | title_hbox.alignment = BoxContainer.ALIGNMENT_CENTER 27 | title_hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL 28 | title_hbox.set_anchors_preset(Control.PRESET_FULL_RECT) 29 | title_panel.add_child(title_hbox) 30 | 31 | var icon_rect: TextureRect = TextureRect.new() 32 | icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED 33 | icon_rect.texture = graph_node.icon 34 | icon_rect.set_size(Vector2(20, 20)) 35 | title_hbox.add_child(icon_rect) 36 | 37 | var title: Label = Label.new() 38 | title.text = graph_node.title_text 39 | title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER 40 | title.set_anchors_preset(Control.PRESET_FULL_RECT) 41 | title_hbox.add_child(title) 42 | 43 | item_tree = Tree.new() 44 | item_tree.custom_minimum_size = Vector2(200, 400) 45 | item_tree.size_flags_horizontal = Control.SIZE_EXPAND_FILL 46 | item_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL 47 | item_tree.hide_root = true 48 | item_tree.allow_search = false 49 | item_tree.columns = 2 50 | add_child(item_tree) 51 | 52 | _update_list() 53 | 54 | func _update_list() -> void: 55 | item_tree.clear() 56 | 57 | var root: TreeItem = item_tree.create_item() 58 | 59 | if graph_node.blackboard.size() == 0: 60 | var no_bb_message: TreeItem = item_tree.create_item(root) 61 | no_bb_message.set_text(0, "No blackboard data") 62 | return 63 | 64 | for bb_name in graph_node.blackboard: 65 | var bb_name_branch: TreeItem = item_tree.create_item(root) 66 | bb_name_branch.set_text(0, bb_name) 67 | 68 | #print(graph_node.blackboard.get(bb_name)) 69 | for key in graph_node.blackboard.get(bb_name): 70 | var bb_kv_leaf: TreeItem = item_tree.create_item(bb_name_branch) 71 | bb_kv_leaf.set_text(0, str(key)) 72 | bb_kv_leaf.set_text(1, str(graph_node.blackboard.get(bb_name).get(key))) 73 | 74 | func _get_icon(type: StringName) -> Texture2D: 75 | var classes := ProjectSettings.get_global_class_list() 76 | for c in classes: 77 | if c["class"] == type: 78 | var icon_path := c.get("icon", String()) 79 | if not icon_path.is_empty(): 80 | return load(icon_path) 81 | return null 82 | -------------------------------------------------------------------------------- /addons/beehave/icons/repeater.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 35 | 36 | 40 | 41 | -------------------------------------------------------------------------------- /addons/beehave/icons/action.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_reactive.gd: -------------------------------------------------------------------------------- 1 | ## Reactive Sequence nodes will attempt to execute all of its children and report 2 | ## `SUCCESS` in case all of the children report a `SUCCESS` status code. 3 | ## If at least one child reports a `FAILURE` status code, this node will also 4 | ## return `FAILURE` and restart. 5 | ## In case a child returns `RUNNING` this node will restart. 6 | @tool 7 | @icon("../../icons/sequence_reactive.svg") 8 | class_name SequenceReactiveComposite extends Composite 9 | 10 | 11 | # Track where we last failed – so we detect a backward jump 12 | var previous_failure_index: int = -1 13 | # Separate index for running as failure and running can diverge in reactive sequence 14 | var previous_running_index: int = -1 15 | 16 | 17 | func tick(actor: Node, blackboard: Blackboard) -> int: 18 | var children = get_children() 19 | for i in range(children.size()): 20 | var c = children[i] 21 | 22 | if c != running_child: 23 | c.before_run(actor, blackboard) 24 | 25 | var response: int = c._safe_tick(actor, blackboard) 26 | if can_send_message(blackboard): 27 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 28 | 29 | if c is ConditionLeaf: 30 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 31 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 32 | 33 | match response: 34 | SUCCESS: 35 | if running_child != null and running_child == c: 36 | # do not interrupt as this child finishes running! 37 | _cleanup_running(running_child, actor, blackboard) 38 | c.after_run(actor, blackboard) 39 | FAILURE: 40 | _interrupt_children(actor, blackboard, i, previous_failure_index) 41 | 42 | # remember where we failed for next tick 43 | previous_failure_index = c.get_index() 44 | 45 | if running_child != null: 46 | running_child.interrupt(actor, blackboard) 47 | _cleanup_running(running_child, actor, blackboard) 48 | c.after_run(actor, blackboard) 49 | return FAILURE 50 | RUNNING: 51 | _reset() 52 | if running_child != null and running_child != c: 53 | running_child.interrupt(actor, blackboard) 54 | _cleanup_running(running_child, actor, blackboard) 55 | running_child = c 56 | if c is ActionLeaf: 57 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 58 | _interrupt_children(actor, blackboard, i, previous_running_index) 59 | previous_running_index = i 60 | return RUNNING 61 | return SUCCESS 62 | 63 | 64 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 65 | _interrupt_children(actor, blackboard, -1, previous_running_index if previous_running_index > previous_failure_index else previous_failure_index) 66 | if running_child != null: 67 | running_child.interrupt(actor, blackboard) 68 | _cleanup_running(running_child, actor, blackboard) 69 | _reset() 70 | super (actor, blackboard) 71 | 72 | 73 | func _reset() -> void: 74 | previous_failure_index = -1 75 | previous_running_index = -1 76 | 77 | 78 | func get_class_name() -> Array[StringName]: 79 | var classes := super () 80 | classes.push_back(&"SequenceReactiveComposite") 81 | return classes 82 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_random.gd: -------------------------------------------------------------------------------- 1 | ## This node will attempt to execute all of its children just like a 2 | ## [code]SequenceStar[/code] would, with the exception that the children 3 | ## will be executed in a random order. 4 | @tool 5 | @icon("../../icons/sequence_random.svg") 6 | class_name SequenceRandomComposite extends RandomizedComposite 7 | 8 | # Emitted whenever the children are shuffled. 9 | signal reset(new_order: Array[Node]) 10 | 11 | ## Whether the sequence should start where it left off after a previous failure. 12 | @export var resume_on_failure: bool = false 13 | ## Whether the sequence should start where it left off after a previous interruption. 14 | @export var resume_on_interrupt: bool = false 15 | 16 | ## A shuffled list of the children that will be executed in reverse order. 17 | var _children_bag: Array[Node] = [] 18 | var c: Node 19 | 20 | 21 | func _ready() -> void: 22 | super() 23 | if random_seed == 0: 24 | randomize() 25 | 26 | 27 | func tick(actor: Node, blackboard: Blackboard) -> int: 28 | if _children_bag.is_empty(): 29 | _reset() 30 | 31 | # We need to traverse the array in reverse since we will be manipulating it. 32 | for i in _get_reversed_indexes(): 33 | c = _children_bag[i] 34 | 35 | if c != running_child: 36 | c.before_run(actor, blackboard) 37 | 38 | var response: int = c._safe_tick(actor, blackboard) 39 | if can_send_message(blackboard): 40 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 41 | 42 | if c is ConditionLeaf: 43 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 44 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 45 | 46 | match response: 47 | SUCCESS: 48 | _children_bag.erase(c) 49 | c.after_run(actor, blackboard) 50 | FAILURE: 51 | _children_bag.erase(c) 52 | # Interrupt any child that was RUNNING before 53 | # but do not reset! 54 | super.interrupt(actor, blackboard) 55 | c.after_run(actor, blackboard) 56 | return FAILURE 57 | RUNNING: 58 | running_child = c 59 | if c is ActionLeaf: 60 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 61 | return RUNNING 62 | 63 | return SUCCESS 64 | 65 | 66 | func after_run(actor: Node, blackboard: Blackboard) -> void: 67 | if not resume_on_failure: 68 | _reset() 69 | super(actor, blackboard) 70 | 71 | 72 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 73 | if not resume_on_interrupt: 74 | if running_child != null: 75 | running_child.interrupt(actor, blackboard) 76 | running_child = null 77 | _reset() 78 | super(actor, blackboard) 79 | 80 | 81 | func _get_reversed_indexes() -> Array[int]: 82 | var reversed: Array[int] 83 | reversed.assign(range(_children_bag.size())) 84 | reversed.reverse() 85 | return reversed 86 | 87 | 88 | func _reset() -> void: 89 | var new_order: Array[Node] = get_shuffled_children() 90 | _children_bag = new_order.duplicate() 91 | _children_bag.reverse() # It needs to run the children in reverse order. 92 | reset.emit(new_order) 93 | 94 | 95 | func get_class_name() -> Array[StringName]: 96 | var classes := super() 97 | classes.push_back(&"SequenceRandomComposite") 98 | return classes 99 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence_star.gd: -------------------------------------------------------------------------------- 1 | ## Sequence Star nodes will attempt to execute all of its children and report 2 | ## `SUCCESS` in case all of the children report a `SUCCESS` status code. 3 | ## If at least one child reports a `FAILURE` status code, this node will also 4 | ## return `FAILURE` and tick again. 5 | ## In case a child returns `RUNNING` this node will tick again. 6 | @tool 7 | @icon("../../icons/sequence_reactive.svg") 8 | class_name SequenceStarComposite extends Composite 9 | 10 | var successful_index: int = 0 11 | # Track where we last failed – so we detect a backward jump 12 | var previous_failure_or_running_index: int = -1 13 | 14 | 15 | func tick(actor: Node, blackboard: Blackboard) -> int: 16 | var children = get_children() 17 | for i in range(children.size()): 18 | var c = children[i] 19 | if c.get_index() < successful_index: 20 | continue 21 | 22 | if c != running_child: 23 | c.before_run(actor, blackboard) 24 | 25 | var response: int = c._safe_tick(actor, blackboard) 26 | if can_send_message(blackboard): 27 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 28 | 29 | if c is ConditionLeaf: 30 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 31 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 32 | 33 | match response: 34 | SUCCESS: 35 | if running_child != null and running_child == c: 36 | # do not interrupt as this child finishes running! 37 | _cleanup_running(running_child, actor, blackboard) 38 | successful_index += 1 39 | c.after_run(actor, blackboard) 40 | FAILURE: 41 | _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) 42 | 43 | # remember where we failed for next tick 44 | previous_failure_or_running_index = c.get_index() 45 | 46 | # Interrupt any child that was RUNNING before 47 | # but do not reset! 48 | if running_child != null: 49 | running_child.interrupt(actor, blackboard) 50 | _cleanup_running(running_child, actor, blackboard) 51 | 52 | c.after_run(actor, blackboard) 53 | return FAILURE 54 | RUNNING: 55 | if running_child != null and running_child != c: 56 | running_child.interrupt(actor, blackboard) 57 | _cleanup_running(running_child, actor, blackboard) 58 | running_child = c 59 | if c is ActionLeaf: 60 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 61 | _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) 62 | previous_failure_or_running_index = i 63 | return RUNNING 64 | successful_index = 0 65 | return SUCCESS 66 | 67 | 68 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 69 | _interrupt_children(actor, blackboard, successful_index - 1, previous_failure_or_running_index) 70 | if running_child != null: 71 | running_child.interrupt(actor, blackboard) 72 | running_child = null 73 | _reset() 74 | super(actor, blackboard) 75 | 76 | 77 | func _reset() -> void: 78 | successful_index = 0 79 | previous_failure_or_running_index = -1 80 | 81 | 82 | func get_class_name() -> Array[StringName]: 83 | var classes := super() 84 | classes.push_back(&"SequenceStarComposite") 85 | return classes 86 | -------------------------------------------------------------------------------- /addons/beehave/blackboard.gd: -------------------------------------------------------------------------------- 1 | @icon("icons/blackboard.svg") 2 | class_name Blackboard extends Node 3 | 4 | const DEFAULT = "default" 5 | 6 | ## The blackboard is an object that can be used to store and access data between 7 | ## multiple nodes of the behavior tree. 8 | @export var blackboard: Dictionary = {}: 9 | set(b): 10 | blackboard = b.duplicate() 11 | _data[DEFAULT] = blackboard 12 | 13 | var _data: Dictionary = {} 14 | 15 | 16 | func _ready(): 17 | blackboard = blackboard.duplicate() 18 | _data[DEFAULT] = blackboard 19 | 20 | 21 | func keys() -> Array[String]: 22 | var keys: Array[String] 23 | keys.assign(_data.keys().duplicate()) 24 | return keys 25 | 26 | 27 | func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void: 28 | if not _data.has(blackboard_name): 29 | _data[blackboard_name] = {} 30 | 31 | _data[blackboard_name][key] = value 32 | 33 | 34 | func get_value( 35 | key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT 36 | ) -> Variant: 37 | if has_value(key, blackboard_name): 38 | return _data[blackboard_name].get(key, default_value) 39 | return default_value 40 | 41 | 42 | func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool: 43 | return ( 44 | _data.has(blackboard_name) 45 | and _data[blackboard_name].has(key) 46 | and _data[blackboard_name][key] != null 47 | ) 48 | 49 | 50 | func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void: 51 | if _data.has(blackboard_name): 52 | _data[blackboard_name][key] = null 53 | 54 | func get_debug_data() -> Dictionary: 55 | # Avoid sending raw Objects (Nodes, Resources, etc.) over the 56 | # EngineDebugger connection, which can cause Variant marshalling errors 57 | # and break the editor debugger stream. 58 | return _sanitize_for_debug(_data) 59 | 60 | 61 | static func _sanitize_for_debug(value: Variant, depth: int = 0) -> Variant: 62 | # Limit recursion depth defensively to avoid issues with deeply nested 63 | # or self‑referential data structures. At this point we only need a 64 | # human‑readable representation for the debugger UI. 65 | if depth > 4: 66 | return str(value) 67 | 68 | var t := typeof(value) 69 | 70 | match t: 71 | TYPE_DICTIONARY: 72 | var result := {} 73 | for key in value.keys(): 74 | var safe_key := _sanitize_for_debug(key, depth + 1) 75 | var safe_val := _sanitize_for_debug(value[key], depth + 1) 76 | result[safe_key] = safe_val 77 | return result 78 | 79 | TYPE_ARRAY: 80 | var arr: Array = [] 81 | for v in value: 82 | arr.append(_sanitize_for_debug(v, depth + 1)) 83 | return arr 84 | 85 | TYPE_OBJECT: 86 | if value == null: 87 | return null 88 | 89 | if value is Node: 90 | var path := "" 91 | if value.get_tree(): 92 | path = str(value.get_path()) 93 | return { 94 | "type": value.get_class(), 95 | "name": value.name, 96 | "path": path, 97 | "id": value.get_instance_id(), 98 | } 99 | 100 | if value is Resource: 101 | return { 102 | "type": value.get_class(), 103 | "resource_path": value.resource_path, 104 | } 105 | 106 | # Fallback: string representation is enough for the debugger. 107 | return str(value) 108 | 109 | _: 110 | return value 111 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/sequence.gd: -------------------------------------------------------------------------------- 1 | ## Sequence nodes will attempt to execute all of its children and report 2 | ## `SUCCESS` in case all of the children report a `SUCCESS` status code. 3 | ## If at least one child reports a `FAILURE` status code, this node will also 4 | ## return `FAILURE` and restart. 5 | ## In case a child returns `RUNNING` this node will tick again. 6 | @tool 7 | @icon("../../icons/sequence.svg") 8 | class_name SequenceComposite extends Composite 9 | 10 | var successful_index: int = 0 11 | # Track where we last failed – so we detect a backward jump 12 | var previous_failure_or_running_index: int = -1 13 | 14 | 15 | func tick(actor: Node, blackboard: Blackboard) -> int: 16 | var children = get_children() 17 | for i in range(children.size()): 18 | var c = children[i] 19 | if c.get_index() < successful_index: 20 | continue 21 | 22 | if c != running_child: 23 | c.before_run(actor, blackboard) 24 | 25 | var response: int = c._safe_tick(actor, blackboard) 26 | if can_send_message(blackboard): 27 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 28 | 29 | if c is ConditionLeaf: 30 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 31 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 32 | 33 | match response: 34 | SUCCESS: 35 | if running_child != null and running_child == c: 36 | # do not interrupt as this child finishes running! 37 | _cleanup_running(running_child, actor, blackboard) 38 | successful_index += 1 39 | c.after_run(actor, blackboard) 40 | FAILURE: 41 | if running_child != null: 42 | running_child.interrupt(actor, blackboard) 43 | _cleanup_running(running_child, actor, blackboard) 44 | 45 | _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) 46 | 47 | # remember where we failed for next tick 48 | previous_failure_or_running_index = c.get_index() 49 | successful_index = 0 50 | 51 | # Interrupt any child that was RUNNING before 52 | # but do not reset! 53 | if running_child != null: 54 | running_child.interrupt(actor, blackboard) 55 | running_child = null 56 | 57 | c.after_run(actor, blackboard) 58 | return FAILURE 59 | RUNNING: 60 | if running_child != null and c != running_child: 61 | running_child.interrupt(actor, blackboard) 62 | _cleanup_running(running_child, actor, blackboard) 63 | if c != running_child: 64 | running_child = c 65 | if c is ActionLeaf: 66 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 67 | _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) 68 | previous_failure_or_running_index = i 69 | return RUNNING 70 | 71 | successful_index = 0 72 | return SUCCESS 73 | 74 | 75 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 76 | _interrupt_children(actor, blackboard, successful_index - 1, previous_failure_or_running_index) 77 | if running_child != null: 78 | running_child.interrupt(actor, blackboard) 79 | _cleanup_running(running_child, actor, blackboard) 80 | _reset() 81 | super(actor, blackboard) 82 | 83 | 84 | func _reset() -> void: 85 | successful_index = 0 86 | previous_failure_or_running_index = -1 87 | 88 | func get_class_name() -> Array[StringName]: 89 | var classes := super() 90 | classes.push_back(&"SequenceComposite") 91 | return classes 92 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector_reactive.gd: -------------------------------------------------------------------------------- 1 | ## Selector Reactive nodes will attempt to execute each of its children until one of 2 | ## them return `SUCCESS`. If all children return `FAILURE`, this node will also 3 | ## return `FAILURE`. 4 | ## If a child returns `RUNNING` it will restart. 5 | @tool 6 | @icon("../../icons/selector_reactive.svg") 7 | class_name SelectorReactiveComposite extends Composite 8 | 9 | 10 | 11 | # Track where we last succeeded – so we detect true branch changes 12 | var previous_success_or_running_index: int = -1 13 | var ready_to_interrupt_all: bool = false 14 | 15 | func tick(actor: Node, blackboard: Blackboard) -> int: 16 | var children := get_children() 17 | var children_count = children.size() 18 | var processed_count = 0 19 | 20 | for i in range(children.size()): 21 | var c = children[i] 22 | 23 | if c != running_child: 24 | c.before_run(actor, blackboard) 25 | 26 | var response: int = c._safe_tick(actor, blackboard) 27 | processed_count += 1 28 | 29 | if can_send_message(blackboard): 30 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 31 | 32 | if c is ConditionLeaf: 33 | blackboard.set_value("last_condition", c, str(actor.get_instance_id())) 34 | blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) 35 | 36 | match response: 37 | SUCCESS: 38 | # clean up the one that just succeeded 39 | if running_child != null: 40 | if running_child != c: 41 | running_child.interrupt(actor, blackboard) 42 | _cleanup_running(running_child, actor, blackboard) 43 | c.after_run(actor, blackboard) 44 | 45 | _interrupt_children(actor, blackboard, i, previous_success_or_running_index) 46 | previous_success_or_running_index = i 47 | ready_to_interrupt_all = false 48 | return SUCCESS 49 | 50 | FAILURE: 51 | c.after_run(actor, blackboard) 52 | 53 | RUNNING: 54 | if c != running_child: 55 | if running_child != null: 56 | running_child.interrupt(actor, blackboard) 57 | running_child = c 58 | if c is ActionLeaf: 59 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 60 | _interrupt_children(actor, blackboard, i, previous_success_or_running_index) 61 | previous_success_or_running_index = i 62 | ready_to_interrupt_all = false 63 | return RUNNING 64 | 65 | # all failed → reset our success‐tracker 66 | ready_to_interrupt_all = (processed_count == children_count) 67 | return FAILURE 68 | 69 | 70 | func after_run(actor: Node, blackboard: Blackboard) -> void: 71 | super(actor, blackboard) 72 | 73 | 74 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 75 | if ready_to_interrupt_all: 76 | # If all children failed, interrupt all children 77 | var children = get_children() 78 | if children.size() > 0: 79 | _interrupt_children(actor, blackboard, -1, children.size() - 1) 80 | ready_to_interrupt_all = false 81 | else: 82 | # Use the normal interrupt logic for partial processing 83 | _interrupt_children(actor, blackboard, -1, previous_success_or_running_index) 84 | if running_child != null: 85 | running_child.interrupt(actor, blackboard) 86 | _cleanup_running(running_child, actor, blackboard) 87 | previous_success_or_running_index = -1 88 | super(actor, blackboard) 89 | 90 | 91 | func get_class_name() -> Array[StringName]: 92 | var classes := super() 93 | classes.push_back(&"SelectorReactiveComposite") 94 | return classes 95 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorDebuggerPlugin 3 | 4 | const DebuggerTab := preload("debugger_tab.gd") 5 | const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") 6 | 7 | var debugger_tab := DebuggerTab.new() 8 | var floating_window: Window 9 | var session: EditorDebuggerSession 10 | 11 | 12 | func _has_capture(prefix: String) -> bool: 13 | return prefix == "beehave" 14 | 15 | 16 | func _capture(message: String, data: Array, session_id: int) -> bool: 17 | # in case the behavior tree has invalid setup this might be null 18 | if debugger_tab == null: 19 | return false 20 | 21 | if message == "beehave:register_tree": 22 | debugger_tab.register_tree(data[0]) 23 | return true 24 | if message == "beehave:unregister_tree": 25 | debugger_tab.unregister_tree(data[0]) 26 | return true 27 | if message == "beehave:process_interrupt": 28 | debugger_tab.graph.process_interrupt(data[0], data[1]) 29 | return true 30 | if message == "beehave:process_tick": 31 | debugger_tab.graph.process_tick(data[0], data[1], data[2]) 32 | return true 33 | if message == "beehave:process_begin": 34 | debugger_tab.graph.process_begin(data[0], data[1]) 35 | return true 36 | if message == "beehave:process_end": 37 | debugger_tab.graph.process_end(data[0], data[1]) 38 | return true 39 | return false 40 | 41 | 42 | func _setup_session(session_id: int) -> void: 43 | session = get_session(session_id) 44 | session.started.connect(debugger_tab.start) 45 | session.stopped.connect(debugger_tab.stop) 46 | 47 | debugger_tab.name = "🐝 Beehave" 48 | debugger_tab.make_floating.connect(_on_make_floating) 49 | debugger_tab.session = session 50 | session.add_session_tab(debugger_tab) 51 | 52 | 53 | func _on_make_floating() -> void: 54 | var plugin := BeehaveUtils.get_plugin() 55 | if not plugin: 56 | return 57 | if floating_window: 58 | _on_window_close_requested() 59 | return 60 | 61 | var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale() 62 | var editor_interface: EditorInterface = plugin.get_editor_interface() 63 | var editor_main_screen = editor_interface.get_editor_main_screen() 64 | debugger_tab.get_parent().remove_child(debugger_tab) 65 | 66 | floating_window = Window.new() 67 | 68 | var panel := Panel.new() 69 | panel.add_theme_stylebox_override( 70 | "panel", 71 | editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles") 72 | ) 73 | panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) 74 | floating_window.add_child(panel) 75 | 76 | var margin := MarginContainer.new() 77 | margin.add_child(debugger_tab) 78 | margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) 79 | margin.add_theme_constant_override("margin_right", border_size.x) 80 | margin.add_theme_constant_override("margin_left", border_size.x) 81 | margin.add_theme_constant_override("margin_top", border_size.y) 82 | margin.add_theme_constant_override("margin_bottom", border_size.y) 83 | panel.add_child(margin) 84 | 85 | floating_window.title = "🐝 Beehave" 86 | floating_window.wrap_controls = true 87 | floating_window.min_size = Vector2i(600, 350) 88 | floating_window.size = debugger_tab.size 89 | floating_window.position = editor_main_screen.global_position 90 | floating_window.transient = true 91 | floating_window.close_requested.connect(_on_window_close_requested) 92 | editor_interface.get_base_control().add_child(floating_window) 93 | 94 | 95 | func _on_window_close_requested() -> void: 96 | debugger_tab.get_parent().remove_child(debugger_tab) 97 | session.add_session_tab(debugger_tab) 98 | floating_window.queue_free() 99 | floating_window = null 100 | -------------------------------------------------------------------------------- /addons/beehave/icons/selector_reactive.svg: -------------------------------------------------------------------------------- 1 | 2 | 46 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/selector.gd: -------------------------------------------------------------------------------- 1 | ## A Selector runs its children in order until one succeeds or is running. 2 | ## On failure, skips already-processed children across ticks. 3 | @tool 4 | @icon("../../icons/selector.svg") 5 | class_name SelectorComposite extends Composite 6 | 7 | 8 | var last_execution_index: int = 0 9 | var previous_success_or_running_index: int = -1 10 | var ready_to_interrupt_all: bool = false 11 | 12 | 13 | func tick(actor: Node, blackboard: Blackboard) -> int: 14 | var children = get_children() 15 | var children_count = children.size() 16 | var processed_count = 0 17 | 18 | for i in range(children.size()): 19 | var child = children[i] 20 | if child.get_index() < last_execution_index: 21 | processed_count += 1 22 | continue 23 | 24 | if child != running_child: 25 | child.before_run(actor, blackboard) 26 | 27 | var response = child._safe_tick(actor, blackboard) 28 | processed_count += 1 29 | 30 | if can_send_message(blackboard): 31 | BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) 32 | 33 | if child is ConditionLeaf: 34 | var id = str(actor.get_instance_id()) 35 | blackboard.set_value("last_condition", child, id) 36 | blackboard.set_value("last_condition_status", response, id) 37 | 38 | match response: 39 | SUCCESS: 40 | if running_child != null: 41 | if running_child != child: 42 | running_child.interrupt(actor, blackboard) 43 | _cleanup_running(running_child, actor, blackboard) 44 | child.after_run(actor, blackboard) 45 | _interrupt_children(actor, blackboard, i, previous_success_or_running_index) 46 | previous_success_or_running_index = i 47 | ready_to_interrupt_all = false 48 | return SUCCESS 49 | 50 | FAILURE: 51 | if running_child != null and running_child == child: 52 | _cleanup_running(running_child, actor, blackboard) 53 | child.after_run(actor, blackboard) 54 | last_execution_index = max(last_execution_index, child.get_index() + 1) 55 | 56 | RUNNING: 57 | if child != running_child: 58 | if running_child != null: 59 | running_child.interrupt(actor, blackboard) 60 | running_child = child 61 | if child is ActionLeaf: 62 | blackboard.set_value("running_action", child, str(actor.get_instance_id())) 63 | _interrupt_children(actor, blackboard, i, previous_success_or_running_index) 64 | previous_success_or_running_index = i 65 | ready_to_interrupt_all = false 66 | return RUNNING 67 | 68 | # all children failed 69 | ready_to_interrupt_all = (processed_count == children_count) 70 | last_execution_index = 0 71 | return FAILURE 72 | 73 | 74 | func after_run(actor: Node, blackboard: Blackboard) -> void: 75 | last_execution_index = 0 76 | super(actor, blackboard) 77 | 78 | 79 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 80 | if ready_to_interrupt_all: 81 | # If all children failed, interrupt all children by using indices 0 and children.size()-1 82 | var children = get_children() 83 | if children.size() > 0: 84 | _interrupt_children(actor, blackboard, -1, children.size() - 1) 85 | ready_to_interrupt_all = false 86 | else: 87 | # Use the normal interrupt logic for partial processing 88 | _interrupt_children(actor, blackboard, last_execution_index, previous_success_or_running_index) 89 | if running_child != null: 90 | running_child.interrupt(actor, blackboard) 91 | _cleanup_running(running_child, actor, blackboard) 92 | last_execution_index = 0 93 | previous_success_or_running_index = -1 94 | super(actor, blackboard) 95 | 96 | 97 | func get_class_name() -> Array[StringName]: 98 | var classes = super() 99 | classes.push_back(&"SelectorComposite") 100 | return classes 101 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_bt.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_leaf.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_composite.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/category_decorator.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/simple_parallel.gd: -------------------------------------------------------------------------------- 1 | ## Simple Parallel nodes will attampt to execute all chidren at same time and 2 | ## can only have exactly two children. First child as primary node, second 3 | ## child as secondary node. 4 | ## This node will always report primary node's state, and continue tick while 5 | ## primary node return 'RUNNING'. The state of secondary node will be ignored 6 | ## and executed like a subtree. 7 | ## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt 8 | ## secondary node and return primary node's result. 9 | ## If this node is running under delay mode, it will wait seconday node 10 | ## finish its action after primary node terminates. 11 | @tool 12 | @icon("../../icons/simple_parallel.svg") 13 | class_name SimpleParallelComposite extends Composite 14 | 15 | #how many times should secondary node repeat, zero means loop forever 16 | @export var secondary_node_repeat_count: int = 0 17 | 18 | #wether to wait secondary node finish its current action after primary node finished 19 | @export var delay_mode: bool = false 20 | 21 | var delayed_result := SUCCESS 22 | var main_task_finished: bool = false 23 | var secondary_node_running: bool = false 24 | var secondary_node_repeat_left: int = 0 25 | 26 | 27 | func _get_configuration_warnings() -> PackedStringArray: 28 | var warnings: PackedStringArray = super._get_configuration_warnings() 29 | 30 | if get_child_count() != 2: 31 | warnings.append("SimpleParallel should have exactly two child nodes.") 32 | 33 | return warnings 34 | 35 | 36 | func tick(actor, blackboard: Blackboard): 37 | for c in get_children(): 38 | var node_index: int = c.get_index() 39 | if node_index == 0 and not main_task_finished: 40 | if c != running_child: 41 | c.before_run(actor, blackboard) 42 | 43 | var response: int = c._safe_tick(actor, blackboard) 44 | if can_send_message(blackboard): 45 | BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) 46 | 47 | delayed_result = response 48 | match response: 49 | SUCCESS, FAILURE: 50 | _cleanup_running_task(c, actor, blackboard) 51 | c.after_run(actor, blackboard) 52 | main_task_finished = true 53 | if not delay_mode: 54 | if secondary_node_running: 55 | get_child(1).interrupt(actor, blackboard) 56 | _reset() 57 | return delayed_result 58 | RUNNING: 59 | running_child = c 60 | if c is ActionLeaf: 61 | blackboard.set_value("running_action", c, str(actor.get_instance_id())) 62 | 63 | elif node_index == 1: 64 | if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0: 65 | if not secondary_node_running: 66 | c.before_run(actor, blackboard) 67 | var subtree_response = c._safe_tick(actor, blackboard) 68 | if subtree_response != RUNNING: 69 | secondary_node_running = false 70 | c.after_run(actor, blackboard) 71 | if delay_mode and main_task_finished: 72 | _reset() 73 | return delayed_result 74 | elif secondary_node_repeat_left > 0: 75 | secondary_node_repeat_left -= 1 76 | else: 77 | secondary_node_running = true 78 | 79 | return RUNNING 80 | 81 | 82 | func before_run(actor: Node, blackboard: Blackboard) -> void: 83 | secondary_node_repeat_left = secondary_node_repeat_count 84 | super(actor, blackboard) 85 | 86 | 87 | func interrupt(actor: Node, blackboard: Blackboard) -> void: 88 | if not main_task_finished: 89 | get_child(0).interrupt(actor, blackboard) 90 | if secondary_node_running: 91 | get_child(1).interrupt(actor, blackboard) 92 | _reset() 93 | super(actor, blackboard) 94 | 95 | 96 | func after_run(actor: Node, blackboard: Blackboard) -> void: 97 | _reset() 98 | super(actor, blackboard) 99 | 100 | 101 | func _reset() -> void: 102 | main_task_finished = false 103 | secondary_node_running = false 104 | 105 | 106 | ## Changes `running_action` and `running_child` after the node finishes executing. 107 | func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): 108 | var blackboard_name = str(actor.get_instance_id()) 109 | if finished_action == running_child: 110 | running_child = null 111 | if finished_action == blackboard.get_value("running_action", null, blackboard_name): 112 | blackboard.set_value("running_action", null, blackboard_name) 113 | 114 | 115 | func get_class_name() -> Array[StringName]: 116 | var classes := super() 117 | classes.push_back(&"SimpleParallelComposite") 118 | return classes 119 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence_random.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /addons/beehave/icons/until_fail.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 36 | 37 | 41 | 45 | 46 | -------------------------------------------------------------------------------- /addons/beehave/debug/old_graph_node.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends GraphNode 3 | 4 | const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") 5 | 6 | const DEFAULT_COLOR := Color("#dad4cb") 7 | 8 | const PORT_TOP_ICON := preload("icons/port_top.svg") 9 | const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") 10 | const PORT_LEFT_ICON := preload("icons/port_left.svg") 11 | const PORT_RIGHT_ICON := preload("icons/port_right.svg") 12 | 13 | @export var title_text: String: 14 | set(value): 15 | title_text = value 16 | if title_label: 17 | title_label.text = value 18 | 19 | @export var text: String: 20 | set(value): 21 | text = value 22 | if label: 23 | label.text = " " if text.is_empty() else text 24 | 25 | @export var icon: Texture2D: 26 | set(value): 27 | icon = value 28 | if icon_rect: 29 | icon_rect.texture = value 30 | 31 | var layout_size: float: 32 | get: 33 | return size.y if horizontal else size.x 34 | 35 | var panel: PanelContainer 36 | var icon_rect: TextureRect 37 | var title_label: Label 38 | var container: VBoxContainer 39 | var label: Label 40 | 41 | var frames: RefCounted 42 | var horizontal: bool = false 43 | 44 | 45 | func _init(frames: RefCounted, horizontal: bool = false) -> void: 46 | self.frames = frames 47 | self.horizontal = horizontal 48 | 49 | 50 | func _ready() -> void: 51 | custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() 52 | draggable = false 53 | 54 | add_theme_stylebox_override("frame", frames.empty if frames != null else null) 55 | add_theme_stylebox_override("selected_frame", frames.empty if frames != null else null) 56 | add_theme_color_override("close_color", Color.TRANSPARENT) 57 | add_theme_icon_override("close", ImageTexture.new()) 58 | 59 | # For top port 60 | add_child(Control.new()) 61 | 62 | panel = PanelContainer.new() 63 | panel.mouse_filter = Control.MOUSE_FILTER_PASS 64 | panel.add_theme_stylebox_override("panel", frames.normal if frames != null else null) 65 | add_child(panel) 66 | 67 | var vbox_container := VBoxContainer.new() 68 | panel.add_child(vbox_container) 69 | 70 | var title_size := 24 * BeehaveUtils.get_editor_scale() 71 | var margin_container := MarginContainer.new() 72 | margin_container.add_theme_constant_override( 73 | "margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale() 74 | ) 75 | margin_container.mouse_filter = Control.MOUSE_FILTER_PASS 76 | vbox_container.add_child(margin_container) 77 | 78 | var title_container := HBoxContainer.new() 79 | title_container.add_child(Control.new()) 80 | title_container.mouse_filter = Control.MOUSE_FILTER_PASS 81 | title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL 82 | margin_container.add_child(title_container) 83 | 84 | icon_rect = TextureRect.new() 85 | icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED 86 | title_container.add_child(icon_rect) 87 | 88 | title_label = Label.new() 89 | title_label.add_theme_color_override("font_color", DEFAULT_COLOR) 90 | title_label.add_theme_font_override("font", get_theme_font("title_font")) 91 | title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER 92 | title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL 93 | title_label.text = title_text 94 | title_container.add_child(title_label) 95 | 96 | title_container.add_child(Control.new()) 97 | 98 | container = VBoxContainer.new() 99 | container.size_flags_vertical = Control.SIZE_EXPAND_FILL 100 | container.size_flags_horizontal = Control.SIZE_EXPAND_FILL 101 | panel.add_child(container) 102 | 103 | label = Label.new() 104 | label.text = " " if text.is_empty() else text 105 | container.add_child(label) 106 | 107 | # For bottom port 108 | add_child(Control.new()) 109 | 110 | minimum_size_changed.connect(_on_size_changed) 111 | _on_size_changed.call_deferred() 112 | 113 | 114 | func set_status(status: int) -> void: 115 | panel.add_theme_stylebox_override("panel", _get_stylebox(status)) 116 | 117 | 118 | func _get_stylebox(status: int) -> StyleBox: 119 | match status: 120 | BeehaveNode.SUCCESS: 121 | return frames.success 122 | BeehaveNode.FAILURE: 123 | return frames.failure 124 | BeehaveNode.RUNNING: 125 | return frames.running 126 | _: 127 | return frames.normal 128 | 129 | 130 | func set_slots(left_enabled: bool, right_enabled: bool) -> void: 131 | if horizontal: 132 | set_slot( 133 | 1, 134 | left_enabled, 135 | 0, 136 | Color.WHITE, 137 | right_enabled, 138 | 0, 139 | Color.WHITE, 140 | PORT_LEFT_ICON, 141 | PORT_RIGHT_ICON 142 | ) 143 | else: 144 | set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null) 145 | set_slot( 146 | 2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON 147 | ) 148 | 149 | 150 | func set_color(color: Color) -> void: 151 | set_input_color(color) 152 | set_output_color(color) 153 | 154 | 155 | func set_input_color(color: Color) -> void: 156 | set_slot_color_left(1 if horizontal else 0, color) 157 | 158 | 159 | func set_output_color(color: Color) -> void: 160 | set_slot_color_right(1 if horizontal else 2, color) 161 | 162 | 163 | func _on_size_changed(): 164 | add_theme_constant_override( 165 | "port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0) 166 | ) 167 | -------------------------------------------------------------------------------- /addons/beehave/nodes/composites/randomized_composite.gd: -------------------------------------------------------------------------------- 1 | ## Base class for composite nodes that execute their children in randomized order. 2 | ## Provides functionality for shuffling children with optional weight-based distribution. 3 | ## When weights are enabled, children with higher weights have a higher chance of being 4 | ## selected first in the random ordering. 5 | @tool 6 | class_name RandomizedComposite extends Composite 7 | 8 | const WEIGHTS_PREFIX = "Weights/" 9 | 10 | ## Sets a predicable seed 11 | @export var random_seed: int = 0: 12 | set(rs): 13 | random_seed = rs 14 | if random_seed != 0: 15 | seed(random_seed) 16 | else: 17 | randomize() 18 | 19 | ## Wether to use weights for every child or not. 20 | @export var use_weights: bool: 21 | set(value): 22 | use_weights = value 23 | if use_weights: 24 | _update_weights(get_children()) 25 | _connect_children_changing_signals() 26 | notify_property_list_changed() 27 | 28 | var _weights: Dictionary 29 | var _exiting_tree: bool 30 | 31 | 32 | func _ready(): 33 | _connect_children_changing_signals() 34 | 35 | 36 | func _connect_children_changing_signals(): 37 | if not child_entered_tree.is_connected(_on_child_entered_tree): 38 | child_entered_tree.connect(_on_child_entered_tree) 39 | 40 | if not child_exiting_tree.is_connected(_on_child_exiting_tree): 41 | child_exiting_tree.connect(_on_child_exiting_tree) 42 | 43 | 44 | func get_shuffled_children() -> Array[Node]: 45 | var children_bag: Array[Node] = get_children().duplicate() 46 | if use_weights: 47 | var weights: Array[int] 48 | weights.assign(children_bag.map(func(child): return _weights[child.name])) 49 | children_bag.assign(_weighted_shuffle(children_bag, weights)) 50 | else: 51 | children_bag.shuffle() 52 | return children_bag 53 | 54 | 55 | ## Returns a shuffled version of a given array using the supplied array of weights. 56 | ## Think of weights as the chance of a given item being the first in the array. 57 | func _weighted_shuffle(items: Array, weights: Array[int]) -> Array: 58 | if len(items) != len(weights): 59 | push_error( 60 | ( 61 | "items and weights size mismatch: expected %d weights, got %d instead." 62 | % [len(items), len(weights)] 63 | ) 64 | ) 65 | return items 66 | 67 | # This method is based on the weighted random sampling algorithm 68 | # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)). 69 | 70 | # For each index, it will calculate random_value^(1/weight). 71 | var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])] 72 | var random_distribuition = range(len(items)).map(chance_calc) 73 | 74 | # Now we just have to order by the calculated value, descending. 75 | random_distribuition.sort_custom(func(a, b): return a[1] > b[1]) 76 | 77 | return random_distribuition.map(func(dist): return items[dist[0]]) 78 | 79 | 80 | func _get_property_list(): 81 | var properties = [] 82 | 83 | if use_weights: 84 | for key in _weights.keys(): 85 | properties.append( 86 | { 87 | "name": WEIGHTS_PREFIX + key, 88 | "type": TYPE_INT, 89 | "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, 90 | "hint": PROPERTY_HINT_RANGE, 91 | "hint_string": "1,100" 92 | } 93 | ) 94 | 95 | return properties 96 | 97 | 98 | func _set(property: StringName, value: Variant) -> bool: 99 | if property.begins_with(WEIGHTS_PREFIX): 100 | var weight_name = property.trim_prefix(WEIGHTS_PREFIX) 101 | _weights[weight_name] = value 102 | return true 103 | 104 | return false 105 | 106 | 107 | func _get(property: StringName): 108 | if property.begins_with(WEIGHTS_PREFIX): 109 | var weight_name = property.trim_prefix(WEIGHTS_PREFIX) 110 | return _weights[weight_name] 111 | 112 | return null 113 | 114 | 115 | func _update_weights(children: Array[Node]) -> void: 116 | var new_weights = {} 117 | for c in children: 118 | if _weights.has(c.name): 119 | new_weights[c.name] = _weights[c.name] 120 | else: 121 | new_weights[c.name] = 1 122 | _weights = new_weights 123 | notify_property_list_changed() 124 | 125 | 126 | func _exit_tree() -> void: 127 | _exiting_tree = true 128 | 129 | 130 | func _enter_tree() -> void: 131 | _exiting_tree = false 132 | 133 | 134 | func _on_child_entered_tree(node: Node): 135 | _update_weights(get_children()) 136 | 137 | var renamed_callable = _on_child_renamed.bind(node.name, node) 138 | if not node.renamed.is_connected(renamed_callable): 139 | node.renamed.connect(renamed_callable) 140 | 141 | if not node.tree_exited.is_connected(_on_child_tree_exited): 142 | node.tree_exited.connect(_on_child_tree_exited.bind(node)) 143 | 144 | 145 | func _on_child_exiting_tree(node: Node): 146 | var renamed_callable = _on_child_renamed.bind(node.name, node) 147 | if node.renamed.is_connected(renamed_callable): 148 | node.renamed.disconnect(renamed_callable) 149 | 150 | 151 | func _on_child_tree_exited(node: Node) -> void: 152 | # don't erase the individual child if the whole tree is exiting together 153 | if not _exiting_tree: 154 | var children = get_children() 155 | children.erase(node) 156 | _update_weights(children) 157 | 158 | if node.tree_exited.is_connected(_on_child_tree_exited): 159 | node.tree_exited.disconnect(_on_child_tree_exited) 160 | 161 | 162 | func _on_child_renamed(old_name: String, renamed_child: Node): 163 | if old_name == renamed_child.name: 164 | return # No need to update the weights. 165 | 166 | # Disconnect signal with old name... 167 | renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child)) 168 | # ...and connect with the new name. 169 | renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child)) 170 | 171 | var original_weight = _weights[old_name] 172 | _weights.erase(old_name) 173 | _weights[renamed_child.name] = original_weight 174 | notify_property_list_changed() 175 | 176 | 177 | func get_class_name() -> Array[StringName]: 178 | var classes := super() 179 | classes.push_back(&"RandomizedComposite") 180 | return classes 181 | -------------------------------------------------------------------------------- /addons/beehave/icons/sequence_reactive.svg: -------------------------------------------------------------------------------- 1 | 2 | 61 | -------------------------------------------------------------------------------- /addons/beehave/debug/new_graph_node.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends GraphNode 3 | 4 | signal blackboard_updated 5 | 6 | const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") 7 | 8 | const PORT_TOP_ICON := preload("icons/port_top.svg") 9 | const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") 10 | const PORT_LEFT_ICON := preload("icons/port_left.svg") 11 | const PORT_RIGHT_ICON := preload("icons/port_right.svg") 12 | 13 | 14 | @export var title_text: String: 15 | set(value): 16 | title_text = value 17 | if title_label: 18 | title_label.text = value 19 | 20 | @export var text: String: 21 | set(value): 22 | text = value 23 | if label: 24 | label.text = " " if text.is_empty() else text 25 | 26 | @export var icon: Texture2D: 27 | set(value): 28 | icon = value 29 | if icon_rect: 30 | icon_rect.texture = value 31 | 32 | @export var blackboard: Dictionary: 33 | set(value): 34 | blackboard = value 35 | blackboard_updated.emit() 36 | 37 | var layout_size: float: 38 | get: 39 | return size.y if horizontal else size.x 40 | 41 | 42 | var icon_rect: TextureRect 43 | var title_label: Label 44 | var label: Label 45 | var titlebar_hbox: HBoxContainer 46 | 47 | var frames: RefCounted 48 | var horizontal: bool = false 49 | var panels_tween: Tween 50 | 51 | 52 | func _init(frames:RefCounted, horizontal: bool = false) -> void: 53 | self.frames = frames 54 | self.horizontal = horizontal 55 | 56 | 57 | func _ready() -> void: 58 | custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() 59 | draggable = false 60 | 61 | add_theme_color_override("close_color", Color.TRANSPARENT) 62 | add_theme_icon_override("close", ImageTexture.new()) 63 | 64 | # For top port 65 | var top_port: Control = Control.new() 66 | add_child(top_port) 67 | 68 | icon_rect = TextureRect.new() 69 | icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED 70 | 71 | titlebar_hbox = get_titlebar_hbox() 72 | titlebar_hbox.get_child(0).queue_free() 73 | titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN 74 | titlebar_hbox.add_child(icon_rect) 75 | 76 | title_label = Label.new() 77 | title_label.add_theme_color_override("font_color", Color.WHITE) 78 | var title_font: Font = get_theme_font("title_font").duplicate() 79 | if title_font is FontVariation: 80 | title_font.variation_embolden = 1 81 | elif title_font is FontFile: 82 | title_font.font_weight = 700 83 | title_label.add_theme_font_override("font", title_font) 84 | title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER 85 | title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL 86 | title_label.text = title_text 87 | titlebar_hbox.add_child(title_label) 88 | 89 | label = Label.new() 90 | label.text = " " if text.is_empty() else text 91 | add_child(label) 92 | 93 | # For bottom port 94 | add_child(Control.new()) 95 | 96 | minimum_size_changed.connect(_on_size_changed) 97 | _on_size_changed.call_deferred() 98 | 99 | 100 | func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void: 101 | if horizontal: 102 | if is_slot_enabled_left(1): 103 | draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-10, -11), color) 104 | if is_slot_enabled_right(1): 105 | draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-9, -11), color) 106 | else: 107 | if slot_index == 0 and is_slot_enabled_left(0): 108 | draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-10, -15), color) 109 | elif slot_index == 1: 110 | draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-10, -9), color) 111 | 112 | 113 | func get_custom_input_port_position(horizontal: bool) -> Vector2: 114 | if horizontal: 115 | return Vector2(0, size.y / 2) 116 | else: 117 | return Vector2(size.x/2, 0) 118 | 119 | 120 | func get_custom_output_port_position(horizontal: bool) -> Vector2: 121 | if horizontal: 122 | return Vector2(size.x, size.y / 2) 123 | else: 124 | return Vector2(size.x / 2, size.y) 125 | 126 | 127 | func set_status(status: int) -> void: 128 | match status: 129 | BeehaveNode.SUCCESS: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success) 130 | BeehaveNode.FAILURE: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure) 131 | BeehaveNode.RUNNING: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running) 132 | _: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal) 133 | 134 | 135 | func set_slots(left_enabled: bool, right_enabled: bool) -> void: 136 | if horizontal: 137 | set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON) 138 | else: 139 | set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null) 140 | set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, Color.WHITE, null, PORT_BOTTOM_ICON) 141 | 142 | 143 | func set_color(color: Color) -> void: 144 | set_input_color(color) 145 | set_output_color(color) 146 | 147 | 148 | func set_input_color(color: Color) -> void: 149 | set_slot_color_left(1 if horizontal else 0, color) 150 | 151 | 152 | func set_output_color(color: Color) -> void: 153 | set_slot_color_right(1 if horizontal else 2, color) 154 | 155 | 156 | func _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void: 157 | # First update and any status change gets immediate panel update 158 | if not has_theme_stylebox_override("panel") or panel_stylebox != frames.panel_normal: 159 | if panels_tween: 160 | panels_tween.kill() 161 | panels_tween = null 162 | 163 | add_theme_stylebox_override("panel", panel_stylebox) 164 | add_theme_stylebox_override("titlebar", titlebar_stylebox) 165 | return 166 | 167 | # Don't need to do anything if we're already tweening back to normal 168 | if panels_tween: 169 | return 170 | 171 | # Don't need to do anything if our colors are already the same as a normal 172 | var cur_panel_stylebox: StyleBox = get_theme_stylebox("panel") 173 | var cur_titlebar_stylebox: StyleBox = get_theme_stylebox("titlebar") 174 | if cur_panel_stylebox.bg_color == frames.panel_normal.bg_color: 175 | return 176 | 177 | # Apply a duplicate of our current panels that we can tween 178 | add_theme_stylebox_override("panel", cur_panel_stylebox.duplicate()) 179 | add_theme_stylebox_override("titlebar", cur_titlebar_stylebox.duplicate()) 180 | cur_panel_stylebox = get_theme_stylebox("panel") 181 | cur_titlebar_stylebox = get_theme_stylebox("titlebar") 182 | 183 | # Going back to normal is a fade 184 | panels_tween = create_tween() 185 | panels_tween.parallel().tween_property(cur_panel_stylebox, "bg_color", panel_stylebox.bg_color, 1.0) 186 | panels_tween.parallel().tween_property(cur_panel_stylebox, "border_color", panel_stylebox.border_color, 1.0) 187 | panels_tween.parallel().tween_property(cur_titlebar_stylebox, "bg_color", panel_stylebox.bg_color, 1.0) 188 | panels_tween.parallel().tween_property(cur_titlebar_stylebox, "border_color", panel_stylebox.border_color, 1.0) 189 | 190 | 191 | func _on_size_changed(): 192 | add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x)) 193 | -------------------------------------------------------------------------------- /addons/beehave/debug/debugger_tab.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BeehaveDebuggerTab 3 | extends PanelContainer 4 | 5 | const Utils = preload("res://addons/beehave/utils/utils.gd") 6 | const TREE_ICON = preload("../icons/tree.svg") 7 | const OldGraph = preload("old_graph_edit.gd") 8 | const NewGraph = preload("new_graph_edit.gd") 9 | const Blackboard = preload("new_node_blackboard.gd") 10 | 11 | signal make_floating 12 | 13 | var session: EditorDebuggerSession 14 | var first_run = true 15 | var active_trees = {} 16 | var active_tree_id = -1 17 | 18 | # UI nodes 19 | var container: HSplitContainer 20 | var item_list: ItemList 21 | var graph_container: HSplitContainer 22 | var graph 23 | var blackboard_vbox: VBoxContainer 24 | var message: Label 25 | 26 | func _ready() -> void: 27 | _build_ui() 28 | _init_graph() 29 | stop() 30 | 31 | visibility_changed.connect(_on_visibility_changed) 32 | if visible and is_visible_in_tree(): 33 | get_tree().create_timer(0.5).timeout.connect(_on_visibility_changed) 34 | 35 | 36 | func _build_ui() -> void: 37 | # Main split fills entire panel 38 | container = HSplitContainer.new() 39 | container.set_anchors_preset(Control.PRESET_FULL_RECT) 40 | add_child(container) 41 | 42 | # Left: behavior‐tree list 43 | item_list = ItemList.new() 44 | item_list.custom_minimum_size = Vector2(300, 0) 45 | item_list.item_selected.connect(_on_item_selected) 46 | container.add_child(item_list) 47 | 48 | # Right: graph + (optional) blackboard 49 | graph_container = HSplitContainer.new() 50 | graph_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL 51 | container.add_child(graph_container) 52 | 53 | # Blackboard pane: narrow, hidden until needed 54 | blackboard_vbox = VBoxContainer.new() 55 | blackboard_vbox.custom_minimum_size = Vector2(500, 0) 56 | blackboard_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL 57 | blackboard_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL 58 | blackboard_vbox.hide() 59 | graph_container.add_child(blackboard_vbox) 60 | 61 | # "Run Project for debugging" overlay 62 | message = Label.new() 63 | message.text = "Run Project for debugging" 64 | message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER 65 | message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER 66 | message.set_anchors_preset(Control.PRESET_CENTER) 67 | add_child(message) 68 | 69 | 70 | func _init_graph() -> void: 71 | var frames = Utils.get_frames() 72 | if Engine.get_version_info().minor >= 2: 73 | graph = NewGraph.new(frames) 74 | else: 75 | graph = OldGraph.new(frames) 76 | 77 | # Graph on the left, blackboard (index 1) on the right 78 | graph.node_selected.connect(_on_graph_node_selected) 79 | graph.node_deselected.connect(_on_graph_node_deselected) 80 | graph.size_flags_horizontal = Control.SIZE_EXPAND_FILL 81 | graph_container.add_child(graph) 82 | graph_container.move_child(graph, 0) 83 | 84 | # "Make Floating" button 85 | var float_btn = Button.new() 86 | float_btn.name = "MakeFloatingButton" 87 | float_btn.flat = true 88 | float_btn.focus_mode = Control.FOCUS_NONE 89 | float_btn.icon = get_theme_icon("ExternalLink", "EditorIcons") 90 | float_btn.pressed.connect(func(): 91 | emit_signal("make_floating") 92 | ) 93 | graph.get_menu_container().add_child(float_btn) 94 | 95 | # "Toggle Panel" button 96 | var toggle_btn = Button.new() 97 | toggle_btn.name = "TogglePanelButton" 98 | toggle_btn.flat = true 99 | toggle_btn.focus_mode = Control.FOCUS_NONE 100 | toggle_btn.icon = get_theme_icon("Back", "EditorIcons") 101 | toggle_btn.pressed.connect(func(): 102 | item_list.visible = not item_list.visible 103 | var icon_name = "Back" if item_list.visible else "Forward" 104 | toggle_btn.icon = get_theme_icon(icon_name, "EditorIcons") 105 | ) 106 | graph.get_menu_container().add_child(toggle_btn) 107 | graph.get_menu_container().move_child(toggle_btn, 0) 108 | 109 | 110 | func start() -> void: 111 | container.visible = true 112 | message.visible = false 113 | 114 | if first_run: 115 | first_run = false 116 | for delay in [0.0, 0.1, 0.5]: 117 | get_tree().create_timer(delay).timeout.connect(_notify_state) 118 | else: 119 | _notify_state() 120 | 121 | # Auto-detach if enabled in project settings - check every time 122 | if ProjectSettings.get_setting("beehave/debugger/start_detached", false): 123 | emit_signal("make_floating") 124 | 125 | 126 | func _notify_state() -> void: 127 | if not session or not visible or not is_visible_in_tree(): 128 | return 129 | session.send_message("beehave:visibility_changed", [true]) 130 | if active_tree_id != -1: 131 | session.send_message("beehave:activate_tree", [active_tree_id]) 132 | 133 | 134 | func stop() -> void: 135 | container.visible = false 136 | message.visible = true 137 | active_trees.clear() 138 | item_list.clear() 139 | graph.beehave_tree = {} 140 | blackboard_vbox.hide() 141 | 142 | 143 | func register_tree(data: Dictionary) -> void: 144 | var id_str = str(data.id) 145 | if not active_trees.has(id_str): 146 | var idx = item_list.add_item(data.name, TREE_ICON) 147 | item_list.set_item_tooltip(idx, data.path) 148 | item_list.set_item_metadata(idx, data.id) 149 | active_trees[id_str] = data 150 | 151 | if data.id.to_int() == active_tree_id: 152 | _notify_state() 153 | 154 | if item_list.get_selected_items().is_empty(): 155 | _select_item_by_id(id_str) 156 | 157 | 158 | func unregister_tree(instance_id: int) -> void: 159 | var id_str = str(instance_id) 160 | for i in range(item_list.get_item_count()): 161 | if item_list.get_item_metadata(i) == id_str: 162 | item_list.remove_item(i) 163 | break 164 | active_trees.erase(id_str) 165 | if graph.beehave_tree.get("id", "") == id_str: 166 | graph.beehave_tree = {} 167 | blackboard_vbox.hide() 168 | 169 | 170 | func _select_item_by_id(id_str: String) -> void: 171 | for i in range(item_list.get_item_count()): 172 | if item_list.get_item_metadata(i) == id_str: 173 | item_list.select(i) 174 | break 175 | get_tree().create_timer(0.2).timeout.connect(func(): 176 | if item_list.is_inside_tree() and not item_list.get_selected_items().is_empty(): 177 | _on_item_selected(item_list.get_selected_items()[0]) 178 | ) 179 | 180 | 181 | func _on_item_selected(idx: int) -> void: 182 | if idx < 0 or idx >= item_list.get_item_count(): 183 | return 184 | 185 | blackboard_vbox.hide() 186 | for child in blackboard_vbox.get_children(): 187 | child.free() 188 | 189 | var id = item_list.get_item_metadata(idx) 190 | var tree_data = active_trees.get(str(id), {}) 191 | if tree_data.is_empty(): 192 | return 193 | 194 | graph.beehave_tree = tree_data 195 | active_tree_id = id.to_int() 196 | _notify_state() 197 | get_tree().create_timer(0.1).timeout.connect(_notify_state) 198 | 199 | 200 | func _on_graph_node_selected(node: GraphNode) -> void: 201 | for child in blackboard_vbox.get_children(): 202 | child.free() 203 | blackboard_vbox.show() 204 | blackboard_vbox.add_child(Blackboard.new(Utils.get_frames(), node)) 205 | 206 | 207 | func _on_graph_node_deselected(node: GraphNode) -> void: 208 | for child in blackboard_vbox.get_children(): 209 | if child.name == node.name: 210 | child.free() 211 | if blackboard_vbox.get_child_count() == 0: 212 | blackboard_vbox.hide() 213 | 214 | 215 | func _on_visibility_changed() -> void: 216 | _notify_state() 217 | --------------------------------------------------------------------------------