├── 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 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/blackboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/delayer.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/tree.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/failer.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/selector.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/condition.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------
/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 |
41 |
--------------------------------------------------------------------------------
/addons/beehave/icons/action.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/category_leaf.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/category_composite.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/category_decorator.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
39 |
--------------------------------------------------------------------------------
/addons/beehave/icons/until_fail.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------