generated from krampus/template-godot4
Added Beehave as addon
This commit is contained in:
parent
138a3e5264
commit
7e577e6c4d
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 bitbrain
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,51 @@
|
|||
@icon("icons/blackboard.svg")
|
||||
class_name Blackboard extends Node
|
||||
|
||||
const DEFAULT = "default"
|
||||
|
||||
## The blackboard is an object that can be used to store and access data between
|
||||
## multiple nodes of the behavior tree.
|
||||
@export var blackboard: Dictionary = {}:
|
||||
set(b):
|
||||
blackboard = b
|
||||
_data[DEFAULT] = blackboard
|
||||
|
||||
var _data: Dictionary = {}
|
||||
|
||||
|
||||
func _ready():
|
||||
_data[DEFAULT] = blackboard
|
||||
|
||||
|
||||
func keys() -> Array[String]:
|
||||
var keys: Array[String]
|
||||
keys.assign(_data.keys().duplicate())
|
||||
return keys
|
||||
|
||||
|
||||
func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void:
|
||||
if not _data.has(blackboard_name):
|
||||
_data[blackboard_name] = {}
|
||||
|
||||
_data[blackboard_name][key] = value
|
||||
|
||||
|
||||
func get_value(
|
||||
key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT
|
||||
) -> Variant:
|
||||
if has_value(key, blackboard_name):
|
||||
return _data[blackboard_name].get(key, default_value)
|
||||
return default_value
|
||||
|
||||
|
||||
func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool:
|
||||
return (
|
||||
_data.has(blackboard_name)
|
||||
and _data[blackboard_name].has(key)
|
||||
and _data[blackboard_name][key] != null
|
||||
)
|
||||
|
||||
|
||||
func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void:
|
||||
if _data.has(blackboard_name):
|
||||
_data[blackboard_name][key] = null
|
|
@ -0,0 +1,96 @@
|
|||
@tool
|
||||
extends EditorDebuggerPlugin
|
||||
|
||||
const DebuggerTab := preload("debugger_tab.gd")
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
var debugger_tab := DebuggerTab.new()
|
||||
var floating_window: Window
|
||||
var session: EditorDebuggerSession
|
||||
|
||||
|
||||
func _has_capture(prefix: String) -> bool:
|
||||
return prefix == "beehave"
|
||||
|
||||
|
||||
func _capture(message: String, data: Array, session_id: int) -> bool:
|
||||
# in case the behavior tree has invalid setup this might be null
|
||||
if debugger_tab == null:
|
||||
return false
|
||||
|
||||
if message == "beehave:register_tree":
|
||||
debugger_tab.register_tree(data[0])
|
||||
return true
|
||||
if message == "beehave:unregister_tree":
|
||||
debugger_tab.unregister_tree(data[0])
|
||||
return true
|
||||
if message == "beehave:process_tick":
|
||||
debugger_tab.graph.process_tick(data[0], data[1])
|
||||
return true
|
||||
if message == "beehave:process_begin":
|
||||
debugger_tab.graph.process_begin(data[0])
|
||||
return true
|
||||
if message == "beehave:process_end":
|
||||
debugger_tab.graph.process_end(data[0])
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _setup_session(session_id: int) -> void:
|
||||
session = get_session(session_id)
|
||||
session.started.connect(debugger_tab.start)
|
||||
session.stopped.connect(debugger_tab.stop)
|
||||
|
||||
debugger_tab.name = "🐝 Beehave"
|
||||
debugger_tab.make_floating.connect(_on_make_floating)
|
||||
debugger_tab.session = session
|
||||
session.add_session_tab(debugger_tab)
|
||||
|
||||
|
||||
func _on_make_floating() -> void:
|
||||
var plugin := BeehaveUtils.get_plugin()
|
||||
if not plugin:
|
||||
return
|
||||
if floating_window:
|
||||
_on_window_close_requested()
|
||||
return
|
||||
|
||||
var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale()
|
||||
var editor_interface: EditorInterface = plugin.get_editor_interface()
|
||||
var editor_main_screen = editor_interface.get_editor_main_screen()
|
||||
debugger_tab.get_parent().remove_child(debugger_tab)
|
||||
|
||||
floating_window = Window.new()
|
||||
|
||||
var panel := Panel.new()
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel",
|
||||
editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles")
|
||||
)
|
||||
panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
floating_window.add_child(panel)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_child(debugger_tab)
|
||||
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
margin.add_theme_constant_override("margin_right", border_size.x)
|
||||
margin.add_theme_constant_override("margin_left", border_size.x)
|
||||
margin.add_theme_constant_override("margin_top", border_size.y)
|
||||
margin.add_theme_constant_override("margin_bottom", border_size.y)
|
||||
panel.add_child(margin)
|
||||
|
||||
floating_window.title = "🐝 Beehave"
|
||||
floating_window.wrap_controls = true
|
||||
floating_window.min_size = Vector2i(600, 350)
|
||||
floating_window.size = debugger_tab.size
|
||||
floating_window.position = editor_main_screen.global_position
|
||||
floating_window.transient = true
|
||||
floating_window.close_requested.connect(_on_window_close_requested)
|
||||
editor_interface.get_base_control().add_child(floating_window)
|
||||
|
||||
|
||||
func _on_window_close_requested() -> void:
|
||||
debugger_tab.get_parent().remove_child(debugger_tab)
|
||||
session.add_session_tab(debugger_tab)
|
||||
floating_window.queue_free()
|
||||
floating_window = null
|
|
@ -0,0 +1,30 @@
|
|||
class_name BeehaveDebuggerMessages
|
||||
|
||||
|
||||
static func can_send_message() -> bool:
|
||||
return not Engine.is_editor_hint() and OS.has_feature("editor")
|
||||
|
||||
|
||||
static func register_tree(beehave_tree: Dictionary) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:register_tree", [beehave_tree])
|
||||
|
||||
|
||||
static func unregister_tree(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:unregister_tree", [instance_id])
|
||||
|
||||
|
||||
static func process_tick(instance_id: int, status: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_tick", [instance_id, status])
|
||||
|
||||
|
||||
static func process_begin(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_begin", [instance_id])
|
||||
|
||||
|
||||
static func process_end(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_end", [instance_id])
|
|
@ -0,0 +1,125 @@
|
|||
@tool
|
||||
class_name BeehaveDebuggerTab extends PanelContainer
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
signal make_floating
|
||||
|
||||
const OldBeehaveGraphEdit := preload("old_graph_edit.gd")
|
||||
const NewBeehaveGraphEdit := preload("new_graph_edit.gd")
|
||||
|
||||
const TREE_ICON := preload("../icons/tree.svg")
|
||||
|
||||
var graph
|
||||
var container: HSplitContainer
|
||||
var item_list: ItemList
|
||||
var message: Label
|
||||
|
||||
var active_trees: Dictionary
|
||||
var active_tree_id: int = -1
|
||||
var session: EditorDebuggerSession
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
container = HSplitContainer.new()
|
||||
add_child(container)
|
||||
|
||||
item_list = ItemList.new()
|
||||
item_list.custom_minimum_size = Vector2(200, 0)
|
||||
item_list.item_selected.connect(_on_item_selected)
|
||||
container.add_child(item_list)
|
||||
if Engine.get_version_info().minor >= 2:
|
||||
graph = NewBeehaveGraphEdit.new(BeehaveUtils.get_frames())
|
||||
else:
|
||||
graph = OldBeehaveGraphEdit.new(BeehaveUtils.get_frames())
|
||||
|
||||
container.add_child(graph)
|
||||
|
||||
message = Label.new()
|
||||
message.text = "Run Project for debugging"
|
||||
message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
message.set_anchors_preset(Control.PRESET_CENTER)
|
||||
add_child(message)
|
||||
|
||||
var button := Button.new()
|
||||
button.flat = true
|
||||
button.name = "MakeFloatingButton"
|
||||
button.icon = get_theme_icon(&"ExternalLink", &"EditorIcons")
|
||||
button.pressed.connect(func(): make_floating.emit())
|
||||
button.tooltip_text = "Make floating"
|
||||
button.focus_mode = Control.FOCUS_NONE
|
||||
graph.get_menu_container().add_child(button)
|
||||
|
||||
var toggle_button := Button.new()
|
||||
toggle_button.flat = true
|
||||
toggle_button.name = "TogglePanelButton"
|
||||
toggle_button.icon = get_theme_icon(&"Back", &"EditorIcons")
|
||||
toggle_button.pressed.connect(_on_toggle_button_pressed.bind(toggle_button))
|
||||
toggle_button.tooltip_text = "Toggle Panel"
|
||||
toggle_button.focus_mode = Control.FOCUS_NONE
|
||||
graph.get_menu_container().add_child(toggle_button)
|
||||
graph.get_menu_container().move_child(toggle_button, 0)
|
||||
|
||||
stop()
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
|
||||
func start() -> void:
|
||||
container.visible = true
|
||||
message.visible = false
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
container.visible = false
|
||||
message.visible = true
|
||||
|
||||
active_trees.clear()
|
||||
item_list.clear()
|
||||
graph.beehave_tree = {}
|
||||
|
||||
|
||||
func register_tree(data: Dictionary) -> void:
|
||||
if not active_trees.has(data.id):
|
||||
var idx := item_list.add_item(data.name, TREE_ICON)
|
||||
item_list.set_item_tooltip(idx, data.path)
|
||||
item_list.set_item_metadata(idx, data.id)
|
||||
|
||||
active_trees[data.id] = data
|
||||
|
||||
if active_tree_id == data.id.to_int():
|
||||
graph.beehave_tree = data
|
||||
|
||||
|
||||
func unregister_tree(instance_id: int) -> void:
|
||||
var id := str(instance_id)
|
||||
for i in item_list.item_count:
|
||||
if item_list.get_item_metadata(i) == id:
|
||||
item_list.remove_item(i)
|
||||
break
|
||||
|
||||
active_trees.erase(id)
|
||||
|
||||
if graph.beehave_tree.get("id", "") == id:
|
||||
graph.beehave_tree = {}
|
||||
|
||||
|
||||
func _on_toggle_button_pressed(toggle_button: Button) -> void:
|
||||
item_list.visible = !item_list.visible
|
||||
toggle_button.icon = get_theme_icon(
|
||||
&"Back" if item_list.visible else &"Forward", &"EditorIcons"
|
||||
)
|
||||
|
||||
|
||||
func _on_item_selected(idx: int) -> void:
|
||||
var id: StringName = item_list.get_item_metadata(idx)
|
||||
graph.beehave_tree = active_trees.get(id, {})
|
||||
|
||||
active_tree_id = id.to_int()
|
||||
if session != null:
|
||||
session.send_message("beehave:activate_tree", [active_tree_id])
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if session != null:
|
||||
session.send_message("beehave:visibility_changed", [visible and is_visible_in_tree()])
|
|
@ -0,0 +1,38 @@
|
|||
extends Node
|
||||
|
||||
var _registered_trees: Dictionary
|
||||
var _active_tree
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
EngineDebugger.register_message_capture("beehave", _on_debug_message)
|
||||
|
||||
|
||||
func _on_debug_message(message: String, data: Array) -> bool:
|
||||
if message == "activate_tree":
|
||||
_set_active_tree(data[0])
|
||||
return true
|
||||
if message == "visibility_changed":
|
||||
if _active_tree && is_instance_valid(_active_tree):
|
||||
_active_tree._can_send_message = data[0]
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _set_active_tree(tree_id: int) -> void:
|
||||
var tree = _registered_trees.get(tree_id, null)
|
||||
if not tree:
|
||||
return
|
||||
|
||||
if _active_tree && is_instance_valid(_active_tree):
|
||||
_active_tree._can_send_message = false
|
||||
_active_tree = tree
|
||||
_active_tree._can_send_message = true
|
||||
|
||||
|
||||
func register_tree(tree) -> void:
|
||||
_registered_trees[tree.get_instance_id()] = tree
|
||||
|
||||
|
||||
func unregister_tree(tree) -> void:
|
||||
_registered_trees.erase(tree.get_instance_id())
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bah77esichnyx"
|
||||
path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/horizontal_layout.svg"
|
||||
dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://da3b236rjbqns"
|
||||
path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_bottom.svg"
|
||||
dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bnufc8p6spdtn"
|
||||
path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_left.svg"
|
||||
dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bbmd6vk23ympm"
|
||||
path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_right.svg"
|
||||
dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bw8wmxdfom8eh"
|
||||
path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_top.svg"
|
||||
dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bpyxu6i1dx5qh"
|
||||
path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/vertical_layout.svg"
|
||||
dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
|
@ -0,0 +1,69 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
|
||||
const SUCCESS_COLOR := Color("#07783a")
|
||||
const NORMAL_COLOR := Color("#15181e")
|
||||
const FAILURE_COLOR := Color("#82010b")
|
||||
const RUNNING_COLOR := Color("#c29c06")
|
||||
|
||||
var panel_normal: StyleBoxFlat
|
||||
var panel_success: StyleBoxFlat
|
||||
var panel_failure: StyleBoxFlat
|
||||
var panel_running: StyleBoxFlat
|
||||
|
||||
var titlebar_normal: StyleBoxFlat
|
||||
var titlebar_success: StyleBoxFlat
|
||||
var titlebar_failure: StyleBoxFlat
|
||||
var titlebar_running: StyleBoxFlat
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var plugin := BeehaveUtils.get_plugin()
|
||||
if not plugin:
|
||||
return
|
||||
|
||||
|
||||
titlebar_normal = (
|
||||
plugin
|
||||
.get_editor_interface()
|
||||
.get_base_control()
|
||||
.get_theme_stylebox(&"titlebar", &"GraphNode")\
|
||||
.duplicate()
|
||||
)
|
||||
titlebar_success = titlebar_normal.duplicate()
|
||||
titlebar_failure = titlebar_normal.duplicate()
|
||||
titlebar_running = titlebar_normal.duplicate()
|
||||
|
||||
titlebar_success.bg_color = SUCCESS_COLOR
|
||||
titlebar_failure.bg_color = FAILURE_COLOR
|
||||
titlebar_running.bg_color = RUNNING_COLOR
|
||||
|
||||
titlebar_success.border_color = SUCCESS_COLOR
|
||||
titlebar_failure.border_color = FAILURE_COLOR
|
||||
titlebar_running.border_color = RUNNING_COLOR
|
||||
|
||||
|
||||
panel_normal = (
|
||||
plugin
|
||||
.get_editor_interface()
|
||||
.get_base_control()
|
||||
.get_theme_stylebox(&"panel", &"GraphNode")
|
||||
.duplicate()
|
||||
)
|
||||
panel_success = (
|
||||
plugin
|
||||
.get_editor_interface()
|
||||
.get_base_control()
|
||||
.get_theme_stylebox(&"panel_selected", &"GraphNode")
|
||||
.duplicate()
|
||||
)
|
||||
panel_failure = panel_success.duplicate()
|
||||
panel_running = panel_success.duplicate()
|
||||
|
||||
panel_success.border_color = SUCCESS_COLOR
|
||||
panel_failure.border_color = FAILURE_COLOR
|
||||
panel_running.border_color = RUNNING_COLOR
|
|
@ -0,0 +1,296 @@
|
|||
@tool
|
||||
extends GraphEdit
|
||||
|
||||
const BeehaveGraphNode := preload("new_graph_node.gd")
|
||||
|
||||
const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
|
||||
const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
|
||||
|
||||
const PROGRESS_SHIFT: int = 50
|
||||
const INACTIVE_COLOR: Color = Color("#898989")
|
||||
const ACTIVE_COLOR: Color = Color("#c29c06")
|
||||
const SUCCESS_COLOR: Color = Color("#07783a")
|
||||
|
||||
|
||||
var updating_graph: bool = false
|
||||
var arraging_nodes: bool = false
|
||||
var beehave_tree: Dictionary:
|
||||
set(value):
|
||||
if beehave_tree == value:
|
||||
return
|
||||
beehave_tree = value
|
||||
active_nodes.clear()
|
||||
_update_graph()
|
||||
|
||||
var horizontal_layout: bool = false:
|
||||
set(value):
|
||||
if updating_graph or arraging_nodes:
|
||||
return
|
||||
if horizontal_layout == value:
|
||||
return
|
||||
horizontal_layout = value
|
||||
_update_layout_button()
|
||||
_update_graph()
|
||||
|
||||
|
||||
var frames:RefCounted
|
||||
var active_nodes: Array[String]
|
||||
var progress: int = 0
|
||||
var layout_button: Button
|
||||
|
||||
|
||||
func _init(frames:RefCounted) -> void:
|
||||
self.frames = frames
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(100, 300)
|
||||
set("show_arrange_button", true)
|
||||
minimap_enabled = false
|
||||
layout_button = Button.new()
|
||||
layout_button.flat = true
|
||||
layout_button.focus_mode = Control.FOCUS_NONE
|
||||
layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
|
||||
get_menu_container().add_child(layout_button)
|
||||
_update_layout_button()
|
||||
|
||||
|
||||
func _update_graph() -> void:
|
||||
if updating_graph:
|
||||
return
|
||||
|
||||
updating_graph = true
|
||||
|
||||
clear_connections()
|
||||
|
||||
for child in _get_child_nodes():
|
||||
remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
if not beehave_tree.is_empty():
|
||||
_add_nodes(beehave_tree)
|
||||
_connect_nodes(beehave_tree)
|
||||
_arrange_nodes.call_deferred(beehave_tree)
|
||||
|
||||
updating_graph = false
|
||||
|
||||
|
||||
func _add_nodes(node: Dictionary) -> void:
|
||||
if node.is_empty():
|
||||
return
|
||||
var gnode := BeehaveGraphNode.new(frames, horizontal_layout)
|
||||
add_child(gnode)
|
||||
gnode.title_text = node.name
|
||||
gnode.name = node.id
|
||||
gnode.icon = _get_icon(node.type.back())
|
||||
|
||||
if node.type.has(&"BeehaveTree"):
|
||||
gnode.set_slots(false, true)
|
||||
elif node.type.has(&"Leaf"):
|
||||
gnode.set_slots(true, false)
|
||||
elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
|
||||
gnode.set_slots(true, true)
|
||||
|
||||
for child in node.get("children", []):
|
||||
_add_nodes(child)
|
||||
|
||||
|
||||
func _connect_nodes(node: Dictionary) -> void:
|
||||
for child in node.get("children", []):
|
||||
connect_node(node.id, 0, child.id, 0)
|
||||
_connect_nodes(child)
|
||||
|
||||
|
||||
func _arrange_nodes(node: Dictionary) -> void:
|
||||
if arraging_nodes:
|
||||
return
|
||||
|
||||
arraging_nodes = true
|
||||
|
||||
var tree_node := _create_tree_nodes(node)
|
||||
tree_node.update_positions(horizontal_layout)
|
||||
_place_nodes(tree_node)
|
||||
|
||||
arraging_nodes = false
|
||||
|
||||
|
||||
func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
|
||||
var tree_node := TreeNode.new(get_node(node.id), root)
|
||||
for child in node.get("children", []):
|
||||
var child_node := _create_tree_nodes(child, tree_node)
|
||||
tree_node.children.push_back(child_node)
|
||||
return tree_node
|
||||
|
||||
|
||||
func _place_nodes(node: TreeNode) -> void:
|
||||
node.item.position_offset = Vector2(node.x, node.y)
|
||||
for child in node.children:
|
||||
_place_nodes(child)
|
||||
|
||||
|
||||
func _get_icon(type: StringName) -> Texture2D:
|
||||
var classes := ProjectSettings.get_global_class_list()
|
||||
for c in classes:
|
||||
if c["class"] == type:
|
||||
var icon_path := c.get("icon", String())
|
||||
if not icon_path.is_empty():
|
||||
return load(icon_path)
|
||||
return null
|
||||
|
||||
|
||||
func get_menu_container() -> Control:
|
||||
return call("get_menu_hbox")
|
||||
|
||||
|
||||
func get_status(status: int) -> String:
|
||||
if status == 0:
|
||||
return "SUCCESS"
|
||||
elif status == 1:
|
||||
return "FAILURE"
|
||||
return "RUNNING"
|
||||
|
||||
|
||||
func process_begin(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in _get_child_nodes():
|
||||
child.set_meta("status", -1)
|
||||
|
||||
|
||||
func process_tick(instance_id: int, status: int) -> void:
|
||||
var node := get_node_or_null(str(instance_id))
|
||||
if node:
|
||||
node.text = "Status: %s" % get_status(status)
|
||||
node.set_status(status)
|
||||
node.set_meta("status", status)
|
||||
if status == 0 or status == 2:
|
||||
if not active_nodes.has(node.name):
|
||||
active_nodes.push_back(node.name)
|
||||
|
||||
|
||||
func process_end(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in _get_child_nodes():
|
||||
var status := child.get_meta("status", -1)
|
||||
match status:
|
||||
0:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(SUCCESS_COLOR)
|
||||
1:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
2:
|
||||
child.set_color(ACTIVE_COLOR)
|
||||
_:
|
||||
child.text = " "
|
||||
child.set_status(status)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
|
||||
|
||||
func _is_same_tree(instance_id: int) -> bool:
|
||||
return str(instance_id) == beehave_tree.get("id", "")
|
||||
|
||||
|
||||
func _get_child_nodes() -> Array[Node]:
|
||||
return get_children().filter(func(child): return child is BeehaveGraphNode)
|
||||
|
||||
|
||||
func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
|
||||
for child in _get_child_nodes():
|
||||
for port in child.get_input_port_count():
|
||||
if not (child.position_offset + child.get_input_port_position(port)).is_equal_approx(to_position):
|
||||
continue
|
||||
to_position = child.position_offset + child.get_custom_input_port_position(horizontal_layout)
|
||||
for port in child.get_output_port_count():
|
||||
if not (child.position_offset + child.get_output_port_position(port)).is_equal_approx(from_position):
|
||||
continue
|
||||
from_position = child.position_offset + child.get_custom_output_port_position(horizontal_layout)
|
||||
return _get_elbow_connection_line(from_position, to_position)
|
||||
|
||||
|
||||
func _get_elbow_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
|
||||
var points: PackedVector2Array
|
||||
|
||||
points.push_back(from_position)
|
||||
|
||||
var mid_position := ((to_position + from_position) / 2).round()
|
||||
if horizontal_layout:
|
||||
points.push_back(Vector2(mid_position.x, from_position.y))
|
||||
points.push_back(Vector2(mid_position.x, to_position.y))
|
||||
else:
|
||||
points.push_back(Vector2(from_position.x, mid_position.y))
|
||||
points.push_back(Vector2(to_position.x, mid_position.y))
|
||||
|
||||
points.push_back(to_position)
|
||||
|
||||
return points
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not active_nodes.is_empty():
|
||||
progress += 10 if delta >= 0.05 else 1
|
||||
if progress >= 1000:
|
||||
progress = 0
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if active_nodes.is_empty():
|
||||
return
|
||||
|
||||
var circle_size: float = max(3, 6 * zoom)
|
||||
var progress_shift: float = PROGRESS_SHIFT * zoom
|
||||
|
||||
var connections := get_connection_list()
|
||||
for c in connections:
|
||||
var from_node: StringName
|
||||
var to_node: StringName
|
||||
|
||||
from_node = c.from_node
|
||||
to_node = c.to_node
|
||||
|
||||
if not from_node in active_nodes or not c.to_node in active_nodes:
|
||||
continue
|
||||
|
||||
var from := get_node(String(from_node))
|
||||
var to := get_node(String(to_node))
|
||||
|
||||
if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
|
||||
return
|
||||
|
||||
var output_port_position: Vector2
|
||||
var input_port_position: Vector2
|
||||
|
||||
var scale_factor: float = from.get_rect().size.x / from.size.x
|
||||
|
||||
var line := _get_elbow_connection_line(
|
||||
from.position + from.get_custom_output_port_position(horizontal_layout) * scale_factor,
|
||||
to.position + to.get_custom_input_port_position(horizontal_layout) * scale_factor
|
||||
)
|
||||
|
||||
var curve = Curve2D.new()
|
||||
for l in line:
|
||||
curve.add_point(l)
|
||||
|
||||
var max_steps := int(curve.get_baked_length())
|
||||
var current_shift := progress % max_steps
|
||||
var p := curve.sample_baked(current_shift)
|
||||
draw_circle(p, circle_size, ACTIVE_COLOR)
|
||||
|
||||
var shift := current_shift - progress_shift
|
||||
while shift >= 0:
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift -= progress_shift
|
||||
|
||||
shift = current_shift + progress_shift
|
||||
while shift <= curve.get_baked_length():
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift += progress_shift
|
||||
|
||||
|
||||
func _update_layout_button() -> void:
|
||||
layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
|
||||
layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
|
|
@ -0,0 +1,155 @@
|
|||
@tool
|
||||
extends GraphNode
|
||||
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
const PORT_TOP_ICON := preload("icons/port_top.svg")
|
||||
const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
|
||||
const PORT_LEFT_ICON := preload("icons/port_left.svg")
|
||||
const PORT_RIGHT_ICON := preload("icons/port_right.svg")
|
||||
|
||||
|
||||
@export var title_text: String:
|
||||
set(value):
|
||||
title_text = value
|
||||
if title_label:
|
||||
title_label.text = value
|
||||
|
||||
@export var text: String:
|
||||
set(value):
|
||||
text = value
|
||||
if label:
|
||||
label.text = " " if text.is_empty() else text
|
||||
|
||||
@export var icon: Texture2D:
|
||||
set(value):
|
||||
icon = value
|
||||
if icon_rect:
|
||||
icon_rect.texture = value
|
||||
|
||||
var layout_size: float:
|
||||
get:
|
||||
return size.y if horizontal else size.x
|
||||
|
||||
|
||||
var icon_rect: TextureRect
|
||||
var title_label: Label
|
||||
var label: Label
|
||||
var titlebar_hbox: HBoxContainer
|
||||
|
||||
var frames: RefCounted
|
||||
var horizontal: bool = false
|
||||
|
||||
|
||||
func _init(frames:RefCounted, horizontal: bool = false) -> void:
|
||||
self.frames = frames
|
||||
self.horizontal = horizontal
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
|
||||
draggable = false
|
||||
|
||||
add_theme_color_override("close_color", Color.TRANSPARENT)
|
||||
add_theme_icon_override("close", ImageTexture.new())
|
||||
|
||||
# For top port
|
||||
var top_port: Control = Control.new()
|
||||
add_child(top_port)
|
||||
|
||||
icon_rect = TextureRect.new()
|
||||
icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
|
||||
titlebar_hbox = get_titlebar_hbox()
|
||||
titlebar_hbox.get_child(0).queue_free()
|
||||
titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
titlebar_hbox.add_child(icon_rect)
|
||||
|
||||
title_label = Label.new()
|
||||
title_label.add_theme_color_override("font_color", Color.WHITE)
|
||||
var title_font: Font = get_theme_font("title_font").duplicate()
|
||||
if title_font is FontVariation:
|
||||
title_font.variation_embolden = 1
|
||||
elif title_font is FontFile:
|
||||
title_font.font_weight = 700
|
||||
title_label.add_theme_font_override("font", title_font)
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_label.text = title_text
|
||||
titlebar_hbox.add_child(title_label)
|
||||
|
||||
label = Label.new()
|
||||
label.text = " " if text.is_empty() else text
|
||||
add_child(label)
|
||||
|
||||
# For bottom port
|
||||
add_child(Control.new())
|
||||
|
||||
minimum_size_changed.connect(_on_size_changed)
|
||||
_on_size_changed.call_deferred()
|
||||
|
||||
|
||||
func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void:
|
||||
if horizontal:
|
||||
if is_slot_enabled_left(1):
|
||||
draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-4, -5), color)
|
||||
if is_slot_enabled_right(1):
|
||||
draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-5, -4.5), color)
|
||||
else:
|
||||
if slot_index == 0 and is_slot_enabled_left(0):
|
||||
draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-4.5, -7), color)
|
||||
elif slot_index == 1:
|
||||
draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-4.5, -5), color)
|
||||
|
||||
|
||||
func get_custom_input_port_position(horizontal: bool) -> Vector2:
|
||||
if horizontal:
|
||||
return Vector2(0, size.y / 2)
|
||||
else:
|
||||
return Vector2(size.x/2, 0)
|
||||
|
||||
|
||||
func get_custom_output_port_position(horizontal: bool) -> Vector2:
|
||||
if horizontal:
|
||||
return Vector2(size.x, size.y / 2)
|
||||
else:
|
||||
return Vector2(size.x / 2, size.y)
|
||||
|
||||
|
||||
func set_status(status: int) -> void:
|
||||
match status:
|
||||
0: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success)
|
||||
1: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure)
|
||||
2: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running)
|
||||
_: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal)
|
||||
|
||||
|
||||
func set_slots(left_enabled: bool, right_enabled: bool) -> void:
|
||||
if horizontal:
|
||||
set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON)
|
||||
else:
|
||||
set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null)
|
||||
set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, Color.WHITE, null, PORT_BOTTOM_ICON)
|
||||
|
||||
|
||||
func set_color(color: Color) -> void:
|
||||
set_input_color(color)
|
||||
set_output_color(color)
|
||||
|
||||
|
||||
func set_input_color(color: Color) -> void:
|
||||
set_slot_color_left(1 if horizontal else 0, color)
|
||||
|
||||
|
||||
func set_output_color(color: Color) -> void:
|
||||
set_slot_color_right(1 if horizontal else 2, color)
|
||||
|
||||
|
||||
func _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void:
|
||||
add_theme_stylebox_override("panel", panel_stylebox)
|
||||
add_theme_stylebox_override("titlebar", titlebar_stylebox)
|
||||
|
||||
|
||||
func _on_size_changed():
|
||||
add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x))
|
|
@ -0,0 +1,47 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
const SUCCESS_COLOR := Color("#009944c8")
|
||||
const NORMAL_COLOR := Color("#15181e")
|
||||
const FAILURE_COLOR := Color("#cf000f80")
|
||||
const RUNNING_COLOR := Color("#ffcc00c8")
|
||||
|
||||
var empty: StyleBoxEmpty
|
||||
var normal: StyleBoxFlat
|
||||
var success: StyleBoxFlat
|
||||
var failure: StyleBoxFlat
|
||||
var running: StyleBoxFlat
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var plugin := BeehaveUtils.get_plugin()
|
||||
if not plugin:
|
||||
return
|
||||
|
||||
var editor_scale := BeehaveUtils.get_editor_scale()
|
||||
|
||||
empty = StyleBoxEmpty.new()
|
||||
|
||||
normal = (
|
||||
plugin
|
||||
. get_editor_interface()
|
||||
. get_base_control()
|
||||
. get_theme_stylebox(&"frame", &"GraphNode")
|
||||
. duplicate()
|
||||
)
|
||||
|
||||
success = (
|
||||
plugin
|
||||
. get_editor_interface()
|
||||
. get_base_control()
|
||||
. get_theme_stylebox(&"selected_frame", &"GraphNode")
|
||||
. duplicate()
|
||||
)
|
||||
failure = success.duplicate()
|
||||
running = success.duplicate()
|
||||
|
||||
success.border_color = SUCCESS_COLOR
|
||||
failure.border_color = FAILURE_COLOR
|
||||
running.border_color = RUNNING_COLOR
|
|
@ -0,0 +1,286 @@
|
|||
@tool
|
||||
extends GraphEdit
|
||||
|
||||
const BeehaveGraphNode := preload("old_graph_node.gd")
|
||||
|
||||
const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
|
||||
const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
|
||||
|
||||
const PROGRESS_SHIFT: int = 50
|
||||
const INACTIVE_COLOR: Color = Color("#898989aa")
|
||||
const ACTIVE_COLOR: Color = Color("#ffcc00c8")
|
||||
const SUCCESS_COLOR: Color = Color("#009944c8")
|
||||
|
||||
var updating_graph: bool = false
|
||||
var arraging_nodes: bool = false
|
||||
var beehave_tree: Dictionary:
|
||||
set(value):
|
||||
if beehave_tree == value:
|
||||
return
|
||||
beehave_tree = value
|
||||
active_nodes.clear()
|
||||
_update_graph()
|
||||
|
||||
var horizontal_layout: bool = false:
|
||||
set(value):
|
||||
if updating_graph or arraging_nodes:
|
||||
return
|
||||
if horizontal_layout == value:
|
||||
return
|
||||
horizontal_layout = value
|
||||
_update_layout_button()
|
||||
_update_graph()
|
||||
|
||||
var frames: RefCounted
|
||||
var active_nodes: Array[String]
|
||||
var progress: int = 0
|
||||
var layout_button: Button
|
||||
|
||||
|
||||
func _init(frames: RefCounted) -> void:
|
||||
self.frames = frames
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(100, 300)
|
||||
set("arrange_nodes_button_hidden", true)
|
||||
minimap_enabled = false
|
||||
layout_button = Button.new()
|
||||
layout_button.flat = true
|
||||
layout_button.focus_mode = Control.FOCUS_NONE
|
||||
layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
|
||||
get_menu_container().add_child(layout_button)
|
||||
_update_layout_button()
|
||||
|
||||
|
||||
func _update_graph() -> void:
|
||||
if updating_graph:
|
||||
return
|
||||
|
||||
updating_graph = true
|
||||
|
||||
clear_connections()
|
||||
|
||||
for child in _get_child_nodes():
|
||||
remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
if not beehave_tree.is_empty():
|
||||
_add_nodes(beehave_tree)
|
||||
_connect_nodes(beehave_tree)
|
||||
_arrange_nodes.call_deferred(beehave_tree)
|
||||
|
||||
updating_graph = false
|
||||
|
||||
|
||||
func _add_nodes(node: Dictionary) -> void:
|
||||
if node.is_empty():
|
||||
return
|
||||
var gnode := BeehaveGraphNode.new(frames, horizontal_layout)
|
||||
add_child(gnode)
|
||||
gnode.title_text = node.name
|
||||
gnode.name = node.id
|
||||
gnode.icon = _get_icon(node.type.back())
|
||||
|
||||
if node.type.has(&"BeehaveTree"):
|
||||
gnode.set_slots(false, true)
|
||||
elif node.type.has(&"Leaf"):
|
||||
gnode.set_slots(true, false)
|
||||
elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
|
||||
gnode.set_slots(true, true)
|
||||
|
||||
for child in node.get("children", []):
|
||||
_add_nodes(child)
|
||||
|
||||
|
||||
func _connect_nodes(node: Dictionary) -> void:
|
||||
for child in node.get("children", []):
|
||||
connect_node(node.id, 0, child.id, 0)
|
||||
_connect_nodes(child)
|
||||
|
||||
|
||||
func _arrange_nodes(node: Dictionary) -> void:
|
||||
if arraging_nodes:
|
||||
return
|
||||
|
||||
arraging_nodes = true
|
||||
|
||||
var tree_node := _create_tree_nodes(node)
|
||||
tree_node.update_positions(horizontal_layout)
|
||||
_place_nodes(tree_node)
|
||||
|
||||
arraging_nodes = false
|
||||
|
||||
|
||||
func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
|
||||
var tree_node := TreeNode.new(get_node(node.id), root)
|
||||
for child in node.get("children", []):
|
||||
var child_node := _create_tree_nodes(child, tree_node)
|
||||
tree_node.children.push_back(child_node)
|
||||
return tree_node
|
||||
|
||||
|
||||
func _place_nodes(node: TreeNode) -> void:
|
||||
node.item.position_offset = Vector2(node.x, node.y)
|
||||
for child in node.children:
|
||||
_place_nodes(child)
|
||||
|
||||
|
||||
func _get_icon(type: StringName) -> Texture2D:
|
||||
var classes := ProjectSettings.get_global_class_list()
|
||||
for c in classes:
|
||||
if c["class"] == type:
|
||||
var icon_path := c.get("icon", String())
|
||||
if not icon_path.is_empty():
|
||||
return load(icon_path)
|
||||
return null
|
||||
|
||||
|
||||
func get_menu_container() -> Control:
|
||||
return call("get_zoom_hbox")
|
||||
|
||||
|
||||
func get_status(status: int) -> String:
|
||||
if status == 0:
|
||||
return "SUCCESS"
|
||||
elif status == 1:
|
||||
return "FAILURE"
|
||||
return "RUNNING"
|
||||
|
||||
|
||||
func process_begin(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in _get_child_nodes():
|
||||
child.set_meta("status", -1)
|
||||
|
||||
|
||||
func process_tick(instance_id: int, status: int) -> void:
|
||||
var node := get_node_or_null(str(instance_id))
|
||||
if node:
|
||||
node.text = "Status: %s" % get_status(status)
|
||||
node.set_status(status)
|
||||
node.set_meta("status", status)
|
||||
if status == 0 or status == 2:
|
||||
if not active_nodes.has(node.name):
|
||||
active_nodes.push_back(node.name)
|
||||
|
||||
|
||||
func process_end(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in _get_child_nodes():
|
||||
var status := child.get_meta("status", -1)
|
||||
match status:
|
||||
0:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(SUCCESS_COLOR)
|
||||
1:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
2:
|
||||
child.set_color(ACTIVE_COLOR)
|
||||
_:
|
||||
child.text = " "
|
||||
child.set_status(status)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
|
||||
|
||||
func _is_same_tree(instance_id: int) -> bool:
|
||||
return str(instance_id) == beehave_tree.get("id", "")
|
||||
|
||||
|
||||
func _get_child_nodes() -> Array[Node]:
|
||||
return get_children().filter(func(child): return child is BeehaveGraphNode)
|
||||
|
||||
|
||||
func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
|
||||
var points: PackedVector2Array
|
||||
|
||||
from_position = from_position.round()
|
||||
to_position = to_position.round()
|
||||
|
||||
points.push_back(from_position)
|
||||
|
||||
var mid_position := ((to_position + from_position) / 2).round()
|
||||
if horizontal_layout:
|
||||
points.push_back(Vector2(mid_position.x, from_position.y))
|
||||
points.push_back(Vector2(mid_position.x, to_position.y))
|
||||
else:
|
||||
points.push_back(Vector2(from_position.x, mid_position.y))
|
||||
points.push_back(Vector2(to_position.x, mid_position.y))
|
||||
|
||||
points.push_back(to_position)
|
||||
|
||||
return points
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not active_nodes.is_empty():
|
||||
progress += 10 if delta >= 0.05 else 1
|
||||
if progress >= 1000:
|
||||
progress = 0
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if active_nodes.is_empty():
|
||||
return
|
||||
|
||||
var circle_size: float = max(3, 6 * zoom)
|
||||
var progress_shift: float = PROGRESS_SHIFT * zoom
|
||||
|
||||
var connections := get_connection_list()
|
||||
for c in connections:
|
||||
var from_node: StringName
|
||||
var to_node: StringName
|
||||
|
||||
from_node = c.from
|
||||
to_node = c.to
|
||||
|
||||
if not from_node in active_nodes or not c.to_node in active_nodes:
|
||||
continue
|
||||
|
||||
var from := get_node(String(from_node))
|
||||
var to := get_node(String(to_node))
|
||||
|
||||
if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
|
||||
return
|
||||
|
||||
var output_port_position: Vector2
|
||||
var input_port_position: Vector2
|
||||
|
||||
output_port_position = (
|
||||
from.position + from.call("get_connection_output_position", c.from_port)
|
||||
)
|
||||
input_port_position = to.position + to.call("get_connection_input_position", c.to_port)
|
||||
|
||||
var line := _get_connection_line(output_port_position, input_port_position)
|
||||
|
||||
var curve = Curve2D.new()
|
||||
for l in line:
|
||||
curve.add_point(l)
|
||||
|
||||
var max_steps := int(curve.get_baked_length())
|
||||
var current_shift := progress % max_steps
|
||||
var p := curve.sample_baked(current_shift)
|
||||
draw_circle(p, circle_size, ACTIVE_COLOR)
|
||||
|
||||
var shift := current_shift - progress_shift
|
||||
while shift >= 0:
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift -= progress_shift
|
||||
|
||||
shift = current_shift + progress_shift
|
||||
while shift <= curve.get_baked_length():
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift += progress_shift
|
||||
|
||||
|
||||
func _update_layout_button() -> void:
|
||||
layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
|
||||
layout_button.tooltip_text = (
|
||||
"Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
|
||||
)
|
|
@ -0,0 +1,166 @@
|
|||
@tool
|
||||
extends GraphNode
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
const DEFAULT_COLOR := Color("#dad4cb")
|
||||
|
||||
const PORT_TOP_ICON := preload("icons/port_top.svg")
|
||||
const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
|
||||
const PORT_LEFT_ICON := preload("icons/port_left.svg")
|
||||
const PORT_RIGHT_ICON := preload("icons/port_right.svg")
|
||||
|
||||
@export var title_text: String:
|
||||
set(value):
|
||||
title_text = value
|
||||
if title_label:
|
||||
title_label.text = value
|
||||
|
||||
@export var text: String:
|
||||
set(value):
|
||||
text = value
|
||||
if label:
|
||||
label.text = " " if text.is_empty() else text
|
||||
|
||||
@export var icon: Texture2D:
|
||||
set(value):
|
||||
icon = value
|
||||
if icon_rect:
|
||||
icon_rect.texture = value
|
||||
|
||||
var layout_size: float:
|
||||
get:
|
||||
return size.y if horizontal else size.x
|
||||
|
||||
var panel: PanelContainer
|
||||
var icon_rect: TextureRect
|
||||
var title_label: Label
|
||||
var container: VBoxContainer
|
||||
var label: Label
|
||||
|
||||
var frames: RefCounted
|
||||
var horizontal: bool = false
|
||||
|
||||
|
||||
func _init(frames: RefCounted, horizontal: bool = false) -> void:
|
||||
self.frames = frames
|
||||
self.horizontal = horizontal
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
|
||||
draggable = false
|
||||
|
||||
add_theme_stylebox_override("frame", frames.empty if frames != null else null)
|
||||
add_theme_stylebox_override("selected_frame", frames.empty if frames != null else null)
|
||||
add_theme_color_override("close_color", Color.TRANSPARENT)
|
||||
add_theme_icon_override("close", ImageTexture.new())
|
||||
|
||||
# For top port
|
||||
add_child(Control.new())
|
||||
|
||||
panel = PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
panel.add_theme_stylebox_override("panel", frames.normal if frames != null else null)
|
||||
add_child(panel)
|
||||
|
||||
var vbox_container := VBoxContainer.new()
|
||||
panel.add_child(vbox_container)
|
||||
|
||||
var title_size := 24 * BeehaveUtils.get_editor_scale()
|
||||
var margin_container := MarginContainer.new()
|
||||
margin_container.add_theme_constant_override(
|
||||
"margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale()
|
||||
)
|
||||
margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
vbox_container.add_child(margin_container)
|
||||
|
||||
var title_container := HBoxContainer.new()
|
||||
title_container.add_child(Control.new())
|
||||
title_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
margin_container.add_child(title_container)
|
||||
|
||||
icon_rect = TextureRect.new()
|
||||
icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
title_container.add_child(icon_rect)
|
||||
|
||||
title_label = Label.new()
|
||||
title_label.add_theme_color_override("font_color", DEFAULT_COLOR)
|
||||
title_label.add_theme_font_override("font", get_theme_font("title_font"))
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_label.text = title_text
|
||||
title_container.add_child(title_label)
|
||||
|
||||
title_container.add_child(Control.new())
|
||||
|
||||
container = VBoxContainer.new()
|
||||
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel.add_child(container)
|
||||
|
||||
label = Label.new()
|
||||
label.text = " " if text.is_empty() else text
|
||||
container.add_child(label)
|
||||
|
||||
# For bottom port
|
||||
add_child(Control.new())
|
||||
|
||||
minimum_size_changed.connect(_on_size_changed)
|
||||
_on_size_changed.call_deferred()
|
||||
|
||||
|
||||
func set_status(status: int) -> void:
|
||||
panel.add_theme_stylebox_override("panel", _get_stylebox(status))
|
||||
|
||||
|
||||
func _get_stylebox(status: int) -> StyleBox:
|
||||
match status:
|
||||
0:
|
||||
return frames.success
|
||||
1:
|
||||
return frames.failure
|
||||
2:
|
||||
return frames.running
|
||||
_:
|
||||
return frames.normal
|
||||
|
||||
|
||||
func set_slots(left_enabled: bool, right_enabled: bool) -> void:
|
||||
if horizontal:
|
||||
set_slot(
|
||||
1,
|
||||
left_enabled,
|
||||
0,
|
||||
Color.WHITE,
|
||||
right_enabled,
|
||||
0,
|
||||
Color.WHITE,
|
||||
PORT_LEFT_ICON,
|
||||
PORT_RIGHT_ICON
|
||||
)
|
||||
else:
|
||||
set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null)
|
||||
set_slot(
|
||||
2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON
|
||||
)
|
||||
|
||||
|
||||
func set_color(color: Color) -> void:
|
||||
set_input_color(color)
|
||||
set_output_color(color)
|
||||
|
||||
|
||||
func set_input_color(color: Color) -> void:
|
||||
set_slot_color_left(1 if horizontal else 0, color)
|
||||
|
||||
|
||||
func set_output_color(color: Color) -> void:
|
||||
set_slot_color_right(1 if horizontal else 2, color)
|
||||
|
||||
|
||||
func _on_size_changed():
|
||||
add_theme_constant_override(
|
||||
"port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0)
|
||||
)
|
|
@ -0,0 +1,275 @@
|
|||
class_name TreeNode
|
||||
extends RefCounted
|
||||
|
||||
# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/
|
||||
|
||||
const SIBLING_DISTANCE: float = 20.0
|
||||
const LEVEL_DISTANCE: float = 40.0
|
||||
|
||||
const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd")
|
||||
|
||||
var x: float
|
||||
var y: float
|
||||
var mod: float
|
||||
var parent: TreeNode
|
||||
var children: Array[TreeNode]
|
||||
|
||||
var item: GraphNode
|
||||
|
||||
|
||||
func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void:
|
||||
parent = p_parent
|
||||
item = p_item
|
||||
|
||||
|
||||
func is_leaf() -> bool:
|
||||
return children.is_empty()
|
||||
|
||||
|
||||
func is_most_left() -> bool:
|
||||
if not parent:
|
||||
return true
|
||||
return parent.children.front() == self
|
||||
|
||||
|
||||
func is_most_right() -> bool:
|
||||
if not parent:
|
||||
return true
|
||||
return parent.children.back() == self
|
||||
|
||||
|
||||
func get_previous_sibling() -> TreeNode:
|
||||
if not parent or is_most_left():
|
||||
return null
|
||||
return parent.children[parent.children.find(self) - 1]
|
||||
|
||||
|
||||
func get_next_sibling() -> TreeNode:
|
||||
if not parent or is_most_right():
|
||||
return null
|
||||
return parent.children[parent.children.find(self) + 1]
|
||||
|
||||
|
||||
func get_most_left_sibling() -> TreeNode:
|
||||
if not parent:
|
||||
return null
|
||||
|
||||
if is_most_left():
|
||||
return self
|
||||
|
||||
return parent.children.front()
|
||||
|
||||
|
||||
func get_most_left_child() -> TreeNode:
|
||||
if children.is_empty():
|
||||
return null
|
||||
return children.front()
|
||||
|
||||
|
||||
func get_most_right_child() -> TreeNode:
|
||||
if children.is_empty():
|
||||
return null
|
||||
return children.back()
|
||||
|
||||
|
||||
func update_positions(horizontally: bool = false) -> void:
|
||||
_initialize_nodes(self, 0)
|
||||
_calculate_initial_x(self)
|
||||
|
||||
_check_all_children_on_screen(self)
|
||||
_calculate_final_positions(self, 0)
|
||||
|
||||
if horizontally:
|
||||
_swap_x_y(self)
|
||||
_calculate_x(self, 0)
|
||||
else:
|
||||
_calculate_y(self, 0)
|
||||
|
||||
|
||||
func _initialize_nodes(node: TreeNode, depth: int) -> void:
|
||||
node.x = -1
|
||||
node.y = depth
|
||||
node.mod = 0
|
||||
|
||||
for child in node.children:
|
||||
_initialize_nodes(child, depth + 1)
|
||||
|
||||
|
||||
func _calculate_initial_x(node: TreeNode) -> void:
|
||||
for child in node.children:
|
||||
_calculate_initial_x(child)
|
||||
if node.is_leaf():
|
||||
if not node.is_most_left():
|
||||
node.x = (
|
||||
node.get_previous_sibling().x
|
||||
+ node.get_previous_sibling().item.layout_size
|
||||
+ SIBLING_DISTANCE
|
||||
)
|
||||
else:
|
||||
node.x = 0
|
||||
else:
|
||||
var mid: float
|
||||
if node.children.size() == 1:
|
||||
var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2
|
||||
mid = node.children.front().x + offset
|
||||
else:
|
||||
var left_child := node.get_most_left_child()
|
||||
var right_child := node.get_most_right_child()
|
||||
mid = (
|
||||
(
|
||||
left_child.x
|
||||
+ right_child.x
|
||||
+ right_child.item.layout_size
|
||||
- node.item.layout_size
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
|
||||
if node.is_most_left():
|
||||
node.x = mid
|
||||
else:
|
||||
node.x = (
|
||||
node.get_previous_sibling().x
|
||||
+ node.get_previous_sibling().item.layout_size
|
||||
+ SIBLING_DISTANCE
|
||||
)
|
||||
node.mod = node.x - mid
|
||||
|
||||
if not node.is_leaf() and not node.is_most_left():
|
||||
_check_for_conflicts(node)
|
||||
|
||||
|
||||
func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void:
|
||||
node.x += mod_sum
|
||||
mod_sum += node.mod
|
||||
|
||||
for child in node.children:
|
||||
_calculate_final_positions(child, mod_sum)
|
||||
|
||||
|
||||
func _check_all_children_on_screen(node: TreeNode) -> void:
|
||||
var node_contour: Dictionary = {}
|
||||
_get_left_contour(node, 0, node_contour)
|
||||
|
||||
var shift_amount: float = 0
|
||||
for y in node_contour.keys():
|
||||
if node_contour[y] + shift_amount < 0:
|
||||
shift_amount = (node_contour[y] * -1)
|
||||
|
||||
if shift_amount > 0:
|
||||
node.x += shift_amount
|
||||
node.mod += shift_amount
|
||||
|
||||
|
||||
func _check_for_conflicts(node: TreeNode) -> void:
|
||||
var min_distance := SIBLING_DISTANCE
|
||||
var shift_value: float = 0
|
||||
var shift_sibling: TreeNode = null
|
||||
|
||||
var node_contour: Dictionary = {} # { int, float }
|
||||
_get_left_contour(node, 0, node_contour)
|
||||
|
||||
var sibling := node.get_most_left_sibling()
|
||||
while sibling != null and sibling != node:
|
||||
var sibling_contour: Dictionary = {}
|
||||
_get_right_contour(sibling, 0, sibling_contour)
|
||||
|
||||
for level in range(
|
||||
node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1
|
||||
):
|
||||
var distance: float = node_contour[level] - sibling_contour[level]
|
||||
if distance + shift_value < min_distance:
|
||||
shift_value = min_distance - distance
|
||||
shift_sibling = sibling
|
||||
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
if shift_value > 0:
|
||||
node.x += shift_value
|
||||
node.mod += shift_value
|
||||
_center_nodes_between(shift_sibling, node)
|
||||
|
||||
|
||||
func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void:
|
||||
var left_index := left_node.parent.children.find(left_node)
|
||||
var right_index := left_node.parent.children.find(right_node)
|
||||
|
||||
var num_nodes_between: int = (right_index - left_index) - 1
|
||||
if num_nodes_between > 0:
|
||||
# The extra distance that needs to be split into num_nodes_between + 1
|
||||
# in order to find the new node spacing so that nodes are equally spaced
|
||||
var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size
|
||||
# Subtract sizes on nodes in between
|
||||
for i in range(left_index + 1, right_index):
|
||||
distance_to_allocate -= left_node.parent.children[i].item.layout_size
|
||||
# Divide space equally
|
||||
var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1)
|
||||
|
||||
var prev_node := left_node
|
||||
var middle_node := left_node.get_next_sibling()
|
||||
while middle_node != right_node:
|
||||
var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes
|
||||
var offset := desire_x - middle_node.x
|
||||
middle_node.x += offset
|
||||
middle_node.mod += offset
|
||||
prev_node = middle_node
|
||||
middle_node = middle_node.get_next_sibling()
|
||||
|
||||
|
||||
func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
|
||||
var node_left: float = node.x + mod_sum
|
||||
var depth := int(node.y)
|
||||
if not values.has(depth):
|
||||
values[depth] = node_left
|
||||
else:
|
||||
values[depth] = min(values[depth], node_left)
|
||||
|
||||
mod_sum += node.mod
|
||||
for child in node.children:
|
||||
_get_left_contour(child, mod_sum, values)
|
||||
|
||||
|
||||
func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
|
||||
var node_right: float = node.x + mod_sum + node.item.layout_size
|
||||
var depth := int(node.y)
|
||||
if not values.has(depth):
|
||||
values[depth] = node_right
|
||||
else:
|
||||
values[depth] = max(values[depth], node_right)
|
||||
|
||||
mod_sum += node.mod
|
||||
for child in node.children:
|
||||
_get_right_contour(child, mod_sum, values)
|
||||
|
||||
|
||||
func _swap_x_y(node: TreeNode) -> void:
|
||||
for child in node.children:
|
||||
_swap_x_y(child)
|
||||
|
||||
var temp := node.x
|
||||
node.x = node.y
|
||||
node.y = temp
|
||||
|
||||
|
||||
func _calculate_x(node: TreeNode, offset: int) -> void:
|
||||
node.x = offset
|
||||
var sibling := node.get_most_left_sibling()
|
||||
var max_size: int = node.item.size.x
|
||||
while sibling != null:
|
||||
max_size = max(sibling.item.size.x, max_size)
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
for child in node.children:
|
||||
_calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
|
||||
|
||||
|
||||
func _calculate_y(node: TreeNode, offset: int) -> void:
|
||||
node.y = offset
|
||||
var sibling := node.get_most_left_sibling()
|
||||
var max_size: int = node.item.size.y
|
||||
while sibling != null:
|
||||
max_size = max(sibling.item.size.y, max_size)
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
for child in node.children:
|
||||
_calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btrq8e0kyxthg"
|
||||
path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/action.svg"
|
||||
dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dw7rom0hiff6c"
|
||||
path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/blackboard.svg"
|
||||
dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://qpdd6ue7x82h"
|
||||
path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/category_bt.svg"
|
||||
dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://863s568sneja"
|
||||
path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/category_composite.svg"
|
||||
dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2ie8m4ddawlb"
|
||||
path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/category_decorator.svg"
|
||||
dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://eq0sp4g3s75r"
|
||||
path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/category_leaf.svg"
|
||||
dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ck4toqx0nggiu"
|
||||
path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/condition.svg"
|
||||
dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cb1g6e3mmj40d"
|
||||
path="res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/cooldown.svg"
|
||||
dest_files=["res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btc5ynpdytj7j"
|
||||
path="res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/delayer.svg"
|
||||
dest_files=["res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2fj7htaqvcud"
|
||||
path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/failer.svg"
|
||||
dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cffmoc3og8hux"
|
||||
path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/inverter.svg"
|
||||
dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c7akxvsg0f2by"
|
||||
path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/limiter.svg"
|
||||
dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxxmf535lln2p"
|
||||
path="res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/repeater.svg"
|
||||
dest_files=["res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b2c5d20doh4sp"
|
||||
path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/selector.svg"
|
||||
dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmnkcmk7bkdjd"
|
||||
path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/selector_random.svg"
|
||||
dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://crkbov0h8sb8l"
|
||||
path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/selector_reactive.svg"
|
||||
dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c5gw354thiofm"
|
||||
path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/sequence.svg"
|
||||
dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bat8ptdw5qt1d"
|
||||
path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/sequence_random.svg"
|
||||
dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://rmiu1slwfkh7"
|
||||
path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/sequence_reactive.svg"
|
||||
dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bvinjswsagdbc"
|
||||
path="res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/simple_parallel.svg"
|
||||
dest_files=["res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dl6wo332kglbe"
|
||||
path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/succeeder.svg"
|
||||
dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://deryyg2hbmaaw"
|
||||
path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/tree.svg"
|
||||
dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5b63h80o8din"
|
||||
path="res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/icons/until_fail.svg"
|
||||
dest_files=["res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
|
@ -0,0 +1,54 @@
|
|||
extends Node
|
||||
|
||||
var _tree_count: int = 0
|
||||
var _active_tree_count: int = 0
|
||||
var _registered_trees: Array = []
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
Performance.add_custom_monitor("beehave/total_trees", _get_total_trees)
|
||||
Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees)
|
||||
|
||||
|
||||
func register_tree(tree) -> void:
|
||||
if _registered_trees.has(tree):
|
||||
return
|
||||
|
||||
_registered_trees.append(tree)
|
||||
_tree_count += 1
|
||||
|
||||
if tree.enabled:
|
||||
_active_tree_count += 1
|
||||
|
||||
tree.tree_enabled.connect(_on_tree_enabled)
|
||||
tree.tree_disabled.connect(_on_tree_disabled)
|
||||
|
||||
|
||||
func unregister_tree(tree) -> void:
|
||||
if not _registered_trees.has(tree):
|
||||
return
|
||||
|
||||
_registered_trees.erase(tree)
|
||||
_tree_count -= 1
|
||||
|
||||
if tree.enabled:
|
||||
_active_tree_count -= 1
|
||||
|
||||
tree.tree_enabled.disconnect(_on_tree_enabled)
|
||||
tree.tree_disabled.disconnect(_on_tree_disabled)
|
||||
|
||||
|
||||
func _get_total_trees() -> int:
|
||||
return _tree_count
|
||||
|
||||
|
||||
func _get_total_enabled_trees() -> int:
|
||||
return _active_tree_count
|
||||
|
||||
|
||||
func _on_tree_enabled() -> void:
|
||||
_active_tree_count += 1
|
||||
|
||||
|
||||
func _on_tree_disabled() -> void:
|
||||
_active_tree_count -= 1
|
|
@ -0,0 +1,46 @@
|
|||
@tool
|
||||
class_name BeehaveNode extends Node
|
||||
|
||||
## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or
|
||||
## `RUNNING` when ticked.
|
||||
|
||||
enum { SUCCESS, FAILURE, RUNNING }
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings: PackedStringArray = []
|
||||
|
||||
if get_children().any(func(x): return not (x is BeehaveNode)):
|
||||
warnings.append("All children of this node should inherit from BeehaveNode class.")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
## Executes this node and returns a status code.
|
||||
## This method must be overwritten.
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
return SUCCESS
|
||||
|
||||
|
||||
## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS.
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Called before the first time it ticks by the parent.
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Called after the last time it ticks and returns
|
||||
## [code]SUCCESS[/code] or [code]FAILURE[/code].
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
return [&"BeehaveNode"]
|
||||
|
||||
|
||||
func can_send_message(blackboard: Blackboard) -> bool:
|
||||
return blackboard.get_value("can_send_message", false)
|
|
@ -0,0 +1,329 @@
|
|||
@tool
|
||||
@icon("../icons/tree.svg")
|
||||
class_name BeehaveTree extends Node
|
||||
|
||||
## Controls the flow of execution of the entire behavior tree.
|
||||
|
||||
enum { SUCCESS, FAILURE, RUNNING }
|
||||
|
||||
enum ProcessThread { IDLE, PHYSICS }
|
||||
|
||||
signal tree_enabled
|
||||
signal tree_disabled
|
||||
|
||||
## Whether this behavior tree should be enabled or not.
|
||||
@export var enabled: bool = true:
|
||||
set(value):
|
||||
enabled = value
|
||||
set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
|
||||
set_process(enabled and process_thread == ProcessThread.IDLE)
|
||||
if value:
|
||||
tree_enabled.emit()
|
||||
else:
|
||||
interrupt()
|
||||
tree_disabled.emit()
|
||||
|
||||
get:
|
||||
return enabled
|
||||
|
||||
## How often the tree should tick, in frames. The default value of 1 means
|
||||
## tick() runs every frame.
|
||||
@export var tick_rate: int = 1
|
||||
|
||||
## An optional node path this behavior tree should apply to.
|
||||
@export_node_path var actor_node_path: NodePath:
|
||||
set(anp):
|
||||
actor_node_path = anp
|
||||
if actor_node_path != null and str(actor_node_path) != "..":
|
||||
actor = get_node(actor_node_path)
|
||||
else:
|
||||
actor = get_parent()
|
||||
if Engine.is_editor_hint():
|
||||
update_configuration_warnings()
|
||||
|
||||
## Whether to run this tree in a physics or idle thread.
|
||||
@export var process_thread: ProcessThread = ProcessThread.PHYSICS:
|
||||
set(value):
|
||||
process_thread = value
|
||||
set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
|
||||
set_process(enabled and process_thread == ProcessThread.IDLE)
|
||||
|
||||
## Custom blackboard node. An internal blackboard will be used
|
||||
## if no blackboard is provided explicitly.
|
||||
@export var blackboard: Blackboard:
|
||||
set(b):
|
||||
blackboard = b
|
||||
if blackboard and _internal_blackboard:
|
||||
remove_child(_internal_blackboard)
|
||||
_internal_blackboard.free()
|
||||
_internal_blackboard = null
|
||||
elif not blackboard and not _internal_blackboard:
|
||||
_internal_blackboard = Blackboard.new()
|
||||
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
|
||||
get:
|
||||
# in case blackboard is accessed before this node is,
|
||||
# we need to ensure that the internal blackboard is used.
|
||||
if not blackboard and not _internal_blackboard:
|
||||
_internal_blackboard = Blackboard.new()
|
||||
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
|
||||
return blackboard if blackboard else _internal_blackboard
|
||||
|
||||
## When enabled, this tree is tracked individually
|
||||
## as a custom monitor.
|
||||
@export var custom_monitor = false:
|
||||
set(b):
|
||||
custom_monitor = b
|
||||
if custom_monitor and _process_time_metric_name != "":
|
||||
Performance.add_custom_monitor(
|
||||
_process_time_metric_name, _get_process_time_metric_value
|
||||
)
|
||||
_get_global_metrics().register_tree(self)
|
||||
else:
|
||||
if _process_time_metric_name != "":
|
||||
# Remove tree metric from the engine
|
||||
Performance.remove_custom_monitor(_process_time_metric_name)
|
||||
_get_global_metrics().unregister_tree(self)
|
||||
|
||||
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
|
||||
|
||||
@export var actor: Node:
|
||||
set(a):
|
||||
actor = a
|
||||
if actor == null:
|
||||
actor = get_parent()
|
||||
if Engine.is_editor_hint():
|
||||
update_configuration_warnings()
|
||||
|
||||
var status: int = -1
|
||||
var last_tick: int = 0
|
||||
|
||||
var _internal_blackboard: Blackboard
|
||||
var _process_time_metric_name: String
|
||||
var _process_time_metric_value: float = 0.0
|
||||
var _can_send_message: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var connect_scene_tree_signal = func(signal_name: String, is_added: bool):
|
||||
if not get_tree().is_connected(signal_name, _on_scene_tree_node_added_removed.bind(is_added)):
|
||||
get_tree().connect(signal_name, _on_scene_tree_node_added_removed.bind(is_added))
|
||||
connect_scene_tree_signal.call("node_added", true)
|
||||
connect_scene_tree_signal.call("node_removed", false)
|
||||
|
||||
if not process_thread:
|
||||
process_thread = ProcessThread.PHYSICS
|
||||
|
||||
if not actor:
|
||||
if actor_node_path:
|
||||
actor = get_node(actor_node_path)
|
||||
else:
|
||||
actor = get_parent()
|
||||
|
||||
if not blackboard:
|
||||
# invoke setter to auto-initialise the blackboard.
|
||||
self.blackboard = null
|
||||
|
||||
# Get the name of the parent node name for metric
|
||||
_process_time_metric_name = (
|
||||
"beehave [microseconds]/process_time_%s-%s" % [actor.name, get_instance_id()]
|
||||
)
|
||||
|
||||
set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
|
||||
set_process(enabled and process_thread == ProcessThread.IDLE)
|
||||
|
||||
# Register custom metric to the engine
|
||||
if custom_monitor and not Engine.is_editor_hint():
|
||||
Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
|
||||
_get_global_metrics().register_tree(self)
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
update_configuration_warnings.call_deferred()
|
||||
else:
|
||||
_get_global_debugger().register_tree(self)
|
||||
BeehaveDebuggerMessages.register_tree(_get_debugger_data(self))
|
||||
|
||||
# Randomize at what frames tick() will happen to avoid stutters
|
||||
last_tick = randi_range(0, tick_rate - 1)
|
||||
|
||||
|
||||
func _on_scene_tree_node_added_removed(node: Node, is_added: bool) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if node is BeehaveNode and is_ancestor_of(node):
|
||||
var sgnal := node.ready if is_added else node.tree_exited
|
||||
if is_added:
|
||||
sgnal.connect(
|
||||
func() -> void: BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
else:
|
||||
sgnal.connect(
|
||||
func() -> void:
|
||||
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
|
||||
request_ready()
|
||||
)
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
_process_internally()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_process_internally()
|
||||
|
||||
|
||||
func _process_internally() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if last_tick < tick_rate - 1:
|
||||
last_tick += 1
|
||||
return
|
||||
|
||||
last_tick = 0
|
||||
|
||||
# Start timing for metric
|
||||
var start_time = Time.get_ticks_usec()
|
||||
|
||||
blackboard.set_value("can_send_message", _can_send_message)
|
||||
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_begin(get_instance_id())
|
||||
|
||||
if self.get_child_count() == 1:
|
||||
tick()
|
||||
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_end(get_instance_id())
|
||||
|
||||
# Check the cost for this frame and save it for metric report
|
||||
_process_time_metric_value = Time.get_ticks_usec() - start_time
|
||||
|
||||
|
||||
func tick() -> int:
|
||||
if actor == null or get_child_count() == 0:
|
||||
return FAILURE
|
||||
var child := self.get_child(0)
|
||||
if status != RUNNING:
|
||||
child.before_run(actor, blackboard)
|
||||
|
||||
status = child.tick(actor, blackboard)
|
||||
if _can_send_message:
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status)
|
||||
BeehaveDebuggerMessages.process_tick(get_instance_id(), status)
|
||||
|
||||
# Clear running action if nothing is running
|
||||
if status != RUNNING:
|
||||
blackboard.set_value("running_action", null, str(actor.get_instance_id()))
|
||||
child.after_run(actor, blackboard)
|
||||
|
||||
return status
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings: PackedStringArray = []
|
||||
|
||||
if actor == null:
|
||||
warnings.append("Configure target node on tree")
|
||||
|
||||
if get_children().any(func(x): return not (x is BeehaveNode)):
|
||||
warnings.append("All children of this node should inherit from BeehaveNode class.")
|
||||
|
||||
if get_child_count() != 1:
|
||||
warnings.append("BeehaveTree should have exactly one child node.")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
## Returns the currently running action
|
||||
func get_running_action() -> ActionLeaf:
|
||||
return blackboard.get_value("running_action", null, str(actor.get_instance_id()))
|
||||
|
||||
|
||||
## Returns the last condition that was executed
|
||||
func get_last_condition() -> ConditionLeaf:
|
||||
return blackboard.get_value("last_condition", null, str(actor.get_instance_id()))
|
||||
|
||||
|
||||
## Returns the status of the last executed condition
|
||||
func get_last_condition_status() -> String:
|
||||
if blackboard.has_value("last_condition_status", str(actor.get_instance_id())):
|
||||
var status = blackboard.get_value(
|
||||
"last_condition_status", null, str(actor.get_instance_id())
|
||||
)
|
||||
if status == SUCCESS:
|
||||
return "SUCCESS"
|
||||
elif status == FAILURE:
|
||||
return "FAILURE"
|
||||
else:
|
||||
return "RUNNING"
|
||||
return ""
|
||||
|
||||
|
||||
## interrupts this tree if anything was running
|
||||
func interrupt() -> void:
|
||||
if self.get_child_count() != 0:
|
||||
var first_child = self.get_child(0)
|
||||
if "interrupt" in first_child:
|
||||
first_child.interrupt(actor, blackboard)
|
||||
|
||||
|
||||
## Enables this tree.
|
||||
func enable() -> void:
|
||||
self.enabled = true
|
||||
|
||||
|
||||
## Disables this tree.
|
||||
func disable() -> void:
|
||||
self.enabled = false
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if custom_monitor:
|
||||
if _process_time_metric_name != "":
|
||||
# Remove tree metric from the engine
|
||||
Performance.remove_custom_monitor(_process_time_metric_name)
|
||||
_get_global_metrics().unregister_tree(self)
|
||||
|
||||
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
|
||||
|
||||
|
||||
# Called by the engine to profile this tree
|
||||
func _get_process_time_metric_value() -> int:
|
||||
return int(_process_time_metric_value)
|
||||
|
||||
|
||||
func _get_debugger_data(node: Node) -> Dictionary:
|
||||
if not (node is BeehaveTree or node is BeehaveNode):
|
||||
return {}
|
||||
|
||||
var data := {
|
||||
path = node.get_path(),
|
||||
name = node.name,
|
||||
type = node.get_class_name(),
|
||||
id = str(node.get_instance_id())
|
||||
}
|
||||
if node.get_child_count() > 0:
|
||||
data.children = []
|
||||
for child in node.get_children():
|
||||
var child_data := _get_debugger_data(child)
|
||||
if not child_data.is_empty():
|
||||
data.children.push_back(child_data)
|
||||
return data
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
return [&"BeehaveTree"]
|
||||
|
||||
|
||||
# required to avoid lifecycle issues on initial load
|
||||
# due to loading order problems with autoloads
|
||||
func _get_global_metrics() -> Node:
|
||||
return get_tree().root.get_node("BeehaveGlobalMetrics")
|
||||
|
||||
|
||||
# required to avoid lifecycle issues on initial load
|
||||
# due to loading order problems with autoloads
|
||||
func _get_global_debugger() -> Node:
|
||||
return get_tree().root.get_node("BeehaveGlobalDebugger")
|
|
@ -0,0 +1,34 @@
|
|||
@tool
|
||||
@icon("../../icons/category_composite.svg")
|
||||
class_name Composite extends BeehaveNode
|
||||
|
||||
## A Composite node controls the flow of execution of its children in a specific manner.
|
||||
|
||||
var running_child: BeehaveNode = null
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings: PackedStringArray = super._get_configuration_warnings()
|
||||
|
||||
if get_children().filter(func(x): return x is BeehaveNode).size() < 2:
|
||||
warnings.append(
|
||||
"Any composite node should have at least two children. Otherwise it is not useful."
|
||||
)
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if running_child != null:
|
||||
running_child.interrupt(actor, blackboard)
|
||||
running_child = null
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
running_child = null
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"Composite")
|
||||
return classes
|
|
@ -0,0 +1,176 @@
|
|||
@tool
|
||||
class_name RandomizedComposite extends Composite
|
||||
|
||||
const WEIGHTS_PREFIX = "Weights/"
|
||||
|
||||
## Sets a predicable seed
|
||||
@export var random_seed: int = 0:
|
||||
set(rs):
|
||||
random_seed = rs
|
||||
if random_seed != 0:
|
||||
seed(random_seed)
|
||||
else:
|
||||
randomize()
|
||||
|
||||
## Wether to use weights for every child or not.
|
||||
@export var use_weights: bool:
|
||||
set(value):
|
||||
use_weights = value
|
||||
if use_weights:
|
||||
_update_weights(get_children())
|
||||
_connect_children_changing_signals()
|
||||
notify_property_list_changed()
|
||||
|
||||
var _weights: Dictionary
|
||||
var _exiting_tree: bool
|
||||
|
||||
|
||||
func _ready():
|
||||
_connect_children_changing_signals()
|
||||
|
||||
|
||||
func _connect_children_changing_signals():
|
||||
if not child_entered_tree.is_connected(_on_child_entered_tree):
|
||||
child_entered_tree.connect(_on_child_entered_tree)
|
||||
|
||||
if not child_exiting_tree.is_connected(_on_child_exiting_tree):
|
||||
child_exiting_tree.connect(_on_child_exiting_tree)
|
||||
|
||||
|
||||
func get_shuffled_children() -> Array[Node]:
|
||||
var children_bag: Array[Node] = get_children().duplicate()
|
||||
if use_weights:
|
||||
var weights: Array[int]
|
||||
weights.assign(children_bag.map(func(child): return _weights[child.name]))
|
||||
children_bag.assign(_weighted_shuffle(children_bag, weights))
|
||||
else:
|
||||
children_bag.shuffle()
|
||||
return children_bag
|
||||
|
||||
|
||||
## Returns a shuffled version of a given array using the supplied array of weights.
|
||||
## Think of weights as the chance of a given item being the first in the array.
|
||||
func _weighted_shuffle(items: Array, weights: Array[int]) -> Array:
|
||||
if len(items) != len(weights):
|
||||
push_error(
|
||||
(
|
||||
"items and weights size mismatch: expected %d weights, got %d instead."
|
||||
% [len(items), len(weights)]
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
# This method is based on the weighted random sampling algorithm
|
||||
# by Efraimidis, Spirakis; 2005. This runs in O(n log(n)).
|
||||
|
||||
# For each index, it will calculate random_value^(1/weight).
|
||||
var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])]
|
||||
var random_distribuition = range(len(items)).map(chance_calc)
|
||||
|
||||
# Now we just have to order by the calculated value, descending.
|
||||
random_distribuition.sort_custom(func(a, b): return a[1] > b[1])
|
||||
|
||||
return random_distribuition.map(func(dist): return items[dist[0]])
|
||||
|
||||
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
|
||||
if use_weights:
|
||||
for key in _weights.keys():
|
||||
properties.append(
|
||||
{
|
||||
"name": WEIGHTS_PREFIX + key,
|
||||
"type": TYPE_INT,
|
||||
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
|
||||
"hint": PROPERTY_HINT_RANGE,
|
||||
"hint_string": "1,100"
|
||||
}
|
||||
)
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
func _set(property: StringName, value: Variant) -> bool:
|
||||
if property.begins_with(WEIGHTS_PREFIX):
|
||||
var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
|
||||
_weights[weight_name] = value
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _get(property: StringName):
|
||||
if property.begins_with(WEIGHTS_PREFIX):
|
||||
var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
|
||||
return _weights[weight_name]
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _update_weights(children: Array[Node]) -> void:
|
||||
var new_weights = {}
|
||||
for c in children:
|
||||
if _weights.has(c.name):
|
||||
new_weights[c.name] = _weights[c.name]
|
||||
else:
|
||||
new_weights[c.name] = 1
|
||||
_weights = new_weights
|
||||
notify_property_list_changed()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_exiting_tree = true
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_exiting_tree = false
|
||||
|
||||
|
||||
func _on_child_entered_tree(node: Node):
|
||||
_update_weights(get_children())
|
||||
|
||||
var renamed_callable = _on_child_renamed.bind(node.name, node)
|
||||
if not node.renamed.is_connected(renamed_callable):
|
||||
node.renamed.connect(renamed_callable)
|
||||
|
||||
if not node.tree_exited.is_connected(_on_child_tree_exited):
|
||||
node.tree_exited.connect(_on_child_tree_exited.bind(node))
|
||||
|
||||
|
||||
func _on_child_exiting_tree(node: Node):
|
||||
var renamed_callable = _on_child_renamed.bind(node.name, node)
|
||||
if node.renamed.is_connected(renamed_callable):
|
||||
node.renamed.disconnect(renamed_callable)
|
||||
|
||||
|
||||
func _on_child_tree_exited(node: Node) -> void:
|
||||
# don't erase the individual child if the whole tree is exiting together
|
||||
if not _exiting_tree:
|
||||
var children = get_children()
|
||||
children.erase(node)
|
||||
_update_weights(children)
|
||||
|
||||
if node.tree_exited.is_connected(_on_child_tree_exited):
|
||||
node.tree_exited.disconnect(_on_child_tree_exited)
|
||||
|
||||
|
||||
func _on_child_renamed(old_name: String, renamed_child: Node):
|
||||
if old_name == renamed_child.name:
|
||||
return # No need to update the weights.
|
||||
|
||||
# Disconnect signal with old name...
|
||||
renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child))
|
||||
# ...and connect with the new name.
|
||||
renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child))
|
||||
|
||||
var original_weight = _weights[old_name]
|
||||
_weights.erase(old_name)
|
||||
_weights[renamed_child.name] = original_weight
|
||||
notify_property_list_changed()
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"RandomizedComposite")
|
||||
return classes
|
|
@ -0,0 +1,69 @@
|
|||
@tool
|
||||
@icon("../../icons/selector.svg")
|
||||
class_name SelectorComposite extends Composite
|
||||
|
||||
## Selector nodes will attempt to execute each of its children until one of
|
||||
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
|
||||
## return `FAILURE`.
|
||||
## If a child returns `RUNNING` it will tick again.
|
||||
|
||||
var last_execution_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < last_execution_index:
|
||||
continue
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_cleanup_running_task(c, actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
_cleanup_running_task(c, actor, blackboard)
|
||||
last_execution_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
last_execution_index = 0
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
last_execution_index = 0
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
## Changes `running_action` and `running_child` after the node finishes executing.
|
||||
func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
|
||||
var blackboard_name = str(actor.get_instance_id())
|
||||
if finished_action == running_child:
|
||||
running_child = null
|
||||
if finished_action == blackboard.get_value("running_action", null, blackboard_name):
|
||||
blackboard.set_value("running_action", null, blackboard_name)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorComposite")
|
||||
return classes
|
|
@ -0,0 +1,82 @@
|
|||
@tool
|
||||
@icon("../../icons/selector_random.svg")
|
||||
class_name SelectorRandomComposite extends RandomizedComposite
|
||||
|
||||
## This node will attempt to execute all of its children just like a
|
||||
## [code]SelectorStar[/code] would, with the exception that the children
|
||||
## will be executed in a random order.
|
||||
|
||||
## A shuffled list of the children that will be executed in reverse order.
|
||||
var _children_bag: Array[Node] = []
|
||||
var c: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
super()
|
||||
if random_seed == 0:
|
||||
randomize()
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
if _children_bag.is_empty():
|
||||
_reset()
|
||||
|
||||
# We need to traverse the array in reverse since we will be manipulating it.
|
||||
for i in _get_reversed_indexes():
|
||||
c = _children_bag[i]
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _get_reversed_indexes() -> Array[int]:
|
||||
var reversed: Array[int]
|
||||
reversed.assign(range(_children_bag.size()))
|
||||
reversed.reverse()
|
||||
return reversed
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
var new_order = get_shuffled_children()
|
||||
_children_bag = new_order.duplicate()
|
||||
_children_bag.reverse() # It needs to run the children in reverse order.
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorRandomComposite")
|
||||
return classes
|
|
@ -0,0 +1,47 @@
|
|||
@tool
|
||||
@icon("../../icons/selector_reactive.svg")
|
||||
class_name SelectorReactiveComposite extends Composite
|
||||
|
||||
## Selector Reactive nodes will attempt to execute each of its children until one of
|
||||
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
|
||||
## return `FAILURE`.
|
||||
## If a child returns `RUNNING` it will restart.
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
# Interrupt any child that was RUNNING before.
|
||||
if c != running_child:
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
FAILURE:
|
||||
c.after_run(actor, blackboard)
|
||||
RUNNING:
|
||||
if c != running_child:
|
||||
interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return FAILURE
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SelectorReactiveComposite")
|
||||
return classes
|
|
@ -0,0 +1,75 @@
|
|||
@tool
|
||||
@icon("../../icons/sequence.svg")
|
||||
class_name SequenceComposite extends Composite
|
||||
|
||||
## Sequence nodes will attempt to execute all of its children and report
|
||||
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
|
||||
## If at least one child reports a `FAILURE` status code, this node will also
|
||||
## return `FAILURE` and restart.
|
||||
## In case a child returns `RUNNING` this node will tick again.
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_cleanup_running_task(c, actor, blackboard)
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
_cleanup_running_task(c, actor, blackboard)
|
||||
# Interrupt any child that was RUNNING before.
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
if c != running_child:
|
||||
if running_child != null:
|
||||
running_child.interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
## Changes `running_action` and `running_child` after the node finishes executing.
|
||||
func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
|
||||
var blackboard_name = str(actor.get_instance_id())
|
||||
if finished_action == running_child:
|
||||
running_child = null
|
||||
if finished_action == blackboard.get_value("running_action", null, blackboard_name):
|
||||
blackboard.set_value("running_action", null, blackboard_name)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceComposite")
|
||||
return classes
|
|
@ -0,0 +1,96 @@
|
|||
@tool
|
||||
@icon("../../icons/sequence_random.svg")
|
||||
class_name SequenceRandomComposite extends RandomizedComposite
|
||||
|
||||
## This node will attempt to execute all of its children just like a
|
||||
## [code]SequenceStar[/code] would, with the exception that the children
|
||||
## will be executed in a random order.
|
||||
|
||||
# Emitted whenever the children are shuffled.
|
||||
signal reset(new_order: Array[Node])
|
||||
|
||||
## Whether the sequence should start where it left off after a previous failure.
|
||||
@export var resume_on_failure: bool = false
|
||||
## Whether the sequence should start where it left off after a previous interruption.
|
||||
@export var resume_on_interrupt: bool = false
|
||||
|
||||
## A shuffled list of the children that will be executed in reverse order.
|
||||
var _children_bag: Array[Node] = []
|
||||
var c: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
super()
|
||||
if random_seed == 0:
|
||||
randomize()
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
if _children_bag.is_empty():
|
||||
_reset()
|
||||
|
||||
# We need to traverse the array in reverse since we will be manipulating it.
|
||||
for i in _get_reversed_indexes():
|
||||
c = _children_bag[i]
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
_children_bag.erase(c)
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
_children_bag.erase(c)
|
||||
# Interrupt any child that was RUNNING before
|
||||
# but do not reset!
|
||||
super.interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
if not resume_on_failure:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if not resume_on_interrupt:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _get_reversed_indexes() -> Array[int]:
|
||||
var reversed: Array[int]
|
||||
reversed.assign(range(_children_bag.size()))
|
||||
reversed.reverse()
|
||||
return reversed
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
var new_order = get_shuffled_children()
|
||||
_children_bag = new_order.duplicate()
|
||||
_children_bag.reverse() # It needs to run the children in reverse order.
|
||||
reset.emit(new_order)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceRandomComposite")
|
||||
return classes
|
|
@ -0,0 +1,63 @@
|
|||
@tool
|
||||
@icon("../../icons/sequence_reactive.svg")
|
||||
class_name SequenceReactiveComposite extends Composite
|
||||
|
||||
## Reactive Sequence nodes will attempt to execute all of its children and report
|
||||
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
|
||||
## If at least one child reports a `FAILURE` status code, this node will also
|
||||
## return `FAILURE` and restart.
|
||||
## In case a child returns `RUNNING` this node will restart.
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
# Interrupt any child that was RUNNING before.
|
||||
interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
_reset()
|
||||
if running_child != c:
|
||||
interrupt(actor, blackboard)
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceReactiveComposite")
|
||||
return classes
|
|
@ -0,0 +1,61 @@
|
|||
@tool
|
||||
@icon("../../icons/sequence_reactive.svg")
|
||||
class_name SequenceStarComposite extends Composite
|
||||
|
||||
## Sequence Star nodes will attempt to execute all of its children and report
|
||||
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
|
||||
## If at least one child reports a `FAILURE` status code, this node will also
|
||||
## return `FAILURE` and tick again.
|
||||
## In case a child returns `RUNNING` this node will tick again.
|
||||
|
||||
var successful_index: int = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
for c in get_children():
|
||||
if c.get_index() < successful_index:
|
||||
continue
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
successful_index += 1
|
||||
c.after_run(actor, blackboard)
|
||||
FAILURE:
|
||||
# Interrupt any child that was RUNNING before
|
||||
# but do not reset!
|
||||
super.interrupt(actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_reset()
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
successful_index = 0
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SequenceStarComposite")
|
||||
return classes
|
|
@ -0,0 +1,122 @@
|
|||
@tool
|
||||
@icon("../../icons/simple_parallel.svg")
|
||||
class_name SimpleParallelComposite extends Composite
|
||||
|
||||
## Simple Parallel nodes will attampt to execute all chidren at same time and
|
||||
## can only have exactly two children. First child as primary node, second
|
||||
## child as secondary node.
|
||||
## This node will always report primary node's state, and continue tick while
|
||||
## primary node return 'RUNNING'. The state of secondary node will be ignored
|
||||
## and executed like a subtree.
|
||||
## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt
|
||||
## secondary node and return primary node's result.
|
||||
## If this node is running under delay mode, it will wait seconday node
|
||||
## finish its action after primary node terminates.
|
||||
|
||||
#how many times should secondary node repeat, zero means loop forever
|
||||
@export var secondary_node_repeat_count: int = 0
|
||||
|
||||
#wether to wait secondary node finish its current action after primary node finished
|
||||
@export var delay_mode: bool = false
|
||||
|
||||
var delayed_result := SUCCESS
|
||||
var main_task_finished: bool = false
|
||||
var secondary_node_running: bool = false
|
||||
var secondary_node_repeat_left: int = 0
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings: PackedStringArray = super._get_configuration_warnings()
|
||||
|
||||
if get_child_count() != 2:
|
||||
warnings.append("SimpleParallel should have exactly two child nodes.")
|
||||
|
||||
if not get_child(0) is ActionLeaf:
|
||||
warnings.append("SimpleParallel should have an action leaf node as first child node.")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
func tick(actor, blackboard: Blackboard):
|
||||
for c in get_children():
|
||||
var node_index = c.get_index()
|
||||
if node_index == 0 and not main_task_finished:
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
delayed_result = response
|
||||
match response:
|
||||
SUCCESS, FAILURE:
|
||||
_cleanup_running_task(c, actor, blackboard)
|
||||
c.after_run(actor, blackboard)
|
||||
main_task_finished = true
|
||||
if not delay_mode:
|
||||
if secondary_node_running:
|
||||
get_child(1).interrupt(actor, blackboard)
|
||||
_reset()
|
||||
return delayed_result
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
|
||||
elif node_index == 1:
|
||||
if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0:
|
||||
if not secondary_node_running:
|
||||
c.before_run(actor, blackboard)
|
||||
var subtree_response = c.tick(actor, blackboard)
|
||||
if subtree_response != RUNNING:
|
||||
secondary_node_running = false
|
||||
c.after_run(actor, blackboard)
|
||||
if delay_mode and main_task_finished:
|
||||
_reset()
|
||||
return delayed_result
|
||||
elif secondary_node_repeat_left > 0:
|
||||
secondary_node_repeat_left -= 1
|
||||
else:
|
||||
secondary_node_running = true
|
||||
|
||||
return RUNNING
|
||||
|
||||
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
secondary_node_repeat_left = secondary_node_repeat_count
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if not main_task_finished:
|
||||
get_child(0).interrupt(actor, blackboard)
|
||||
if secondary_node_running:
|
||||
get_child(1).interrupt(actor, blackboard)
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
_reset()
|
||||
super(actor, blackboard)
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
main_task_finished = false
|
||||
secondary_node_running = false
|
||||
|
||||
|
||||
## Changes `running_action` and `running_child` after the node finishes executing.
|
||||
func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
|
||||
var blackboard_name = str(actor.get_instance_id())
|
||||
if finished_action == running_child:
|
||||
running_child = null
|
||||
if finished_action == blackboard.get_value("running_action", null, blackboard_name):
|
||||
blackboard.set_value("running_action", null, blackboard_name)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"SimpleParallelComposite")
|
||||
return classes
|
|
@ -0,0 +1,49 @@
|
|||
@tool
|
||||
@icon("../../icons/cooldown.svg")
|
||||
extends Decorator
|
||||
class_name CooldownDecorator
|
||||
|
||||
## The Cooldown Decorator will return 'FAILURE' for a set amount of time
|
||||
## after executing its child.
|
||||
## The timer resets the next time its child is executed and it is not `RUNNING`
|
||||
|
||||
## The wait time in seconds
|
||||
@export var wait_time := 0.0
|
||||
|
||||
@onready var cache_key = "cooldown_%s" % self.get_instance_id()
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
var remaining_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
|
||||
var response
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
if remaining_time > 0:
|
||||
response = FAILURE
|
||||
|
||||
remaining_time -= get_physics_process_delta_time()
|
||||
blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id()))
|
||||
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
|
||||
else:
|
||||
response = c.tick(actor, blackboard)
|
||||
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING and c is ActionLeaf:
|
||||
running_child = c
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
|
||||
if response != RUNNING:
|
||||
blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id()))
|
||||
|
||||
return response
|
|
@ -0,0 +1,33 @@
|
|||
@tool
|
||||
@icon("../../icons/category_decorator.svg")
|
||||
class_name Decorator extends BeehaveNode
|
||||
|
||||
## Decorator nodes are used to transform the result received by its child.
|
||||
## Must only have one child.
|
||||
|
||||
var running_child: BeehaveNode = null
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings: PackedStringArray = super._get_configuration_warnings()
|
||||
|
||||
if get_child_count() != 1:
|
||||
warnings.append("Decorator should have exactly one child node.")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
func interrupt(actor: Node, blackboard: Blackboard) -> void:
|
||||
if running_child != null:
|
||||
running_child.interrupt(actor, blackboard)
|
||||
running_child = null
|
||||
|
||||
|
||||
func after_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
running_child = null
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"Decorator")
|
||||
return classes
|
|
@ -0,0 +1,49 @@
|
|||
@tool
|
||||
@icon("../../icons/delayer.svg")
|
||||
extends Decorator
|
||||
class_name DelayDecorator
|
||||
|
||||
## The Delay Decorator will return 'RUNNING' for a set amount of time
|
||||
## before executing its child.
|
||||
## The timer resets when both it and its child are not `RUNNING`
|
||||
|
||||
## The wait time in seconds
|
||||
@export var wait_time := 0.0
|
||||
|
||||
@onready var cache_key = "time_limiter_%s" % self.get_instance_id()
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
var total_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
|
||||
var response
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
if total_time < wait_time:
|
||||
response = RUNNING
|
||||
|
||||
total_time += get_physics_process_delta_time()
|
||||
blackboard.set_value(cache_key, total_time, str(actor.get_instance_id()))
|
||||
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response)
|
||||
else:
|
||||
response = c.tick(actor, blackboard)
|
||||
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING and c is ActionLeaf:
|
||||
running_child = c
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
|
||||
if response != RUNNING:
|
||||
blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id()))
|
||||
|
||||
return response
|
|
@ -0,0 +1,35 @@
|
|||
@tool
|
||||
@icon("../../icons/failer.svg")
|
||||
class_name AlwaysFailDecorator extends Decorator
|
||||
|
||||
## A Failer node will always return a `FAILURE` status code.
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
else:
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"AlwaysFailDecorator")
|
||||
return classes
|
|
@ -0,0 +1,43 @@
|
|||
@tool
|
||||
@icon("../../icons/inverter.svg")
|
||||
class_name InverterDecorator extends Decorator
|
||||
|
||||
## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
|
||||
## code or `SUCCESS` in case its child returns a `FAILURE` status code.
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
match response:
|
||||
SUCCESS:
|
||||
c.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
FAILURE:
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
_:
|
||||
push_error("This should be unreachable")
|
||||
return -1
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"InverterDecorator")
|
||||
return classes
|
|
@ -0,0 +1,60 @@
|
|||
@tool
|
||||
@icon("../../icons/limiter.svg")
|
||||
class_name LimiterDecorator extends Decorator
|
||||
|
||||
## The limiter will execute its `RUNNING` child `x` amount of times. When the number of
|
||||
## maximum ticks is reached, it will return a `FAILURE` status code.
|
||||
## The count resets the next time that a child is not `RUNNING`
|
||||
|
||||
@onready var cache_key = "limiter_%s" % self.get_instance_id()
|
||||
|
||||
@export var max_count: float = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
if not get_child_count() == 1:
|
||||
return FAILURE
|
||||
|
||||
var child = get_child(0)
|
||||
var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id()))
|
||||
|
||||
if current_count < max_count:
|
||||
blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id()))
|
||||
var response = child.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
|
||||
|
||||
if child is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if child is ActionLeaf and response == RUNNING:
|
||||
running_child = child
|
||||
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
|
||||
|
||||
if response != RUNNING:
|
||||
child.after_run(actor, blackboard)
|
||||
|
||||
return response
|
||||
else:
|
||||
interrupt(actor, blackboard)
|
||||
child.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
blackboard.set_value(cache_key, 0, str(actor.get_instance_id()))
|
||||
if get_child_count() > 0:
|
||||
get_child(0).before_run(actor, blackboard)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"LimiterDecorator")
|
||||
return classes
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
if not get_child_count() == 1:
|
||||
return ["Requires exactly one child node"]
|
||||
return []
|
|
@ -0,0 +1,58 @@
|
|||
## The repeater will execute its child until it returns `SUCCESS` a certain amount of times.
|
||||
## When the number of maximum ticks is reached, it will return a `SUCCESS` status code.
|
||||
## If the child returns `FAILURE`, the repeater will return `FAILURE` immediately.
|
||||
@tool
|
||||
@icon("../../icons/repeater.svg")
|
||||
class_name RepeaterDecorator extends Decorator
|
||||
|
||||
@export var repetitions: int = 1
|
||||
var current_count: int = 0
|
||||
|
||||
|
||||
func before_run(actor: Node, blackboard: Blackboard):
|
||||
current_count = 0
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var child = get_child(0)
|
||||
|
||||
if current_count < repetitions:
|
||||
if running_child == null:
|
||||
child.before_run(actor, blackboard)
|
||||
|
||||
var response = child.tick(actor, blackboard)
|
||||
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
|
||||
|
||||
if child is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = child
|
||||
if child is ActionLeaf:
|
||||
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
|
||||
current_count += 1
|
||||
child.after_run(actor, blackboard)
|
||||
|
||||
if running_child != null:
|
||||
running_child = null
|
||||
|
||||
if response == FAILURE:
|
||||
return FAILURE
|
||||
|
||||
if current_count >= repetitions:
|
||||
return SUCCESS
|
||||
|
||||
return RUNNING
|
||||
else:
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"LimiterDecorator")
|
||||
return classes
|
|
@ -0,0 +1,35 @@
|
|||
@tool
|
||||
@icon("../../icons/succeeder.svg")
|
||||
class_name AlwaysSucceedDecorator extends Decorator
|
||||
|
||||
## A succeeder node will always return a `SUCCESS` status code.
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
else:
|
||||
c.after_run(actor, blackboard)
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"AlwaysSucceedDecorator")
|
||||
return classes
|
|
@ -0,0 +1,60 @@
|
|||
@tool
|
||||
@icon("../../icons/limiter.svg")
|
||||
class_name TimeLimiterDecorator extends Decorator
|
||||
|
||||
## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish
|
||||
## before interrupting it and return a `FAILURE` status code.
|
||||
## The timer resets the next time that a child is not `RUNNING`
|
||||
|
||||
@export var wait_time := 0.0
|
||||
|
||||
@onready var cache_key = "time_limiter_%s" % self.get_instance_id()
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
if not get_child_count() == 1:
|
||||
return FAILURE
|
||||
|
||||
var child = self.get_child(0)
|
||||
var time_left = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id()))
|
||||
|
||||
if time_left < wait_time:
|
||||
time_left += get_physics_process_delta_time()
|
||||
blackboard.set_value(cache_key, time_left, str(actor.get_instance_id()))
|
||||
var response = child.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
|
||||
|
||||
if child is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = child
|
||||
if child is ActionLeaf:
|
||||
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
|
||||
else:
|
||||
child.after_run(actor, blackboard)
|
||||
return response
|
||||
else:
|
||||
interrupt(actor, blackboard)
|
||||
child.after_run(actor, blackboard)
|
||||
return FAILURE
|
||||
|
||||
|
||||
func before_run(actor: Node, blackboard: Blackboard) -> void:
|
||||
blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id()))
|
||||
if get_child_count() > 0:
|
||||
get_child(0).before_run(actor, blackboard)
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"TimeLimiterDecorator")
|
||||
return classes
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
if not get_child_count() == 1:
|
||||
return ["Requires exactly one child node"]
|
||||
return []
|
|
@ -0,0 +1,33 @@
|
|||
@tool
|
||||
@icon("../../icons/until_fail.svg")
|
||||
class_name UntilFailDecorator
|
||||
extends Decorator
|
||||
|
||||
## The UntilFail Decorator will return `RUNNING` if its child returns
|
||||
## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns
|
||||
## `FAILURE`
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var c = get_child(0)
|
||||
|
||||
if c != running_child:
|
||||
c.before_run(actor, blackboard)
|
||||
|
||||
var response = c.tick(actor, blackboard)
|
||||
if can_send_message(blackboard):
|
||||
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
|
||||
|
||||
if c is ConditionLeaf:
|
||||
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
|
||||
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
|
||||
|
||||
if response == RUNNING:
|
||||
running_child = c
|
||||
if c is ActionLeaf:
|
||||
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
|
||||
return RUNNING
|
||||
if response == SUCCESS:
|
||||
return RUNNING
|
||||
|
||||
return SUCCESS
|
|
@ -0,0 +1,14 @@
|
|||
@tool
|
||||
@icon("../../icons/action.svg")
|
||||
class_name ActionLeaf extends Leaf
|
||||
|
||||
## Actions are leaf nodes that define a task to be performed by an actor.
|
||||
## Their execution can be long running, potentially being called across multiple
|
||||
## frame executions. In this case, the node should return `RUNNING` until the
|
||||
## action is completed.
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"ActionLeaf")
|
||||
return classes
|
|
@ -0,0 +1,65 @@
|
|||
@tool
|
||||
class_name BlackboardCompareCondition extends ConditionLeaf
|
||||
|
||||
## Compares two values using the specified comparison operator.
|
||||
## Returns [code]FAILURE[/code] if any of the expression fails or the
|
||||
## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code].
|
||||
|
||||
enum Operators {
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
GREATER,
|
||||
LESS,
|
||||
GREATER_EQUAL,
|
||||
LESS_EQUAL,
|
||||
}
|
||||
|
||||
## Expression represetning left operand.
|
||||
## This value can be any valid GDScript expression.
|
||||
## In order to use the existing blackboard keys for comparison,
|
||||
## use get_value("key_name") e.g. get_value("direction").length()
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = ""
|
||||
## Comparison operator.
|
||||
@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0
|
||||
## Expression represetning right operand.
|
||||
## This value can be any valid GDScript expression.
|
||||
## In order to use the existing blackboard keys for comparison,
|
||||
## use get_value("key_name") e.g. get_value("direction").length()
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = ""
|
||||
|
||||
@onready var _left_expression: Expression = _parse_expression(left_operand)
|
||||
@onready var _right_expression: Expression = _parse_expression(right_operand)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var left: Variant = _left_expression.execute([], blackboard)
|
||||
|
||||
if _left_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var right: Variant = _right_expression.execute([], blackboard)
|
||||
|
||||
if _right_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var result: bool = false
|
||||
|
||||
match operator:
|
||||
Operators.EQUAL:
|
||||
result = left == right
|
||||
Operators.NOT_EQUAL:
|
||||
result = left != right
|
||||
Operators.GREATER:
|
||||
result = left > right
|
||||
Operators.LESS:
|
||||
result = left < right
|
||||
Operators.GREATER_EQUAL:
|
||||
result = left >= right
|
||||
Operators.LESS_EQUAL:
|
||||
result = left <= right
|
||||
|
||||
return SUCCESS if result else FAILURE
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [left_operand, right_operand]
|
|
@ -0,0 +1,25 @@
|
|||
@tool
|
||||
class_name BlackboardEraseAction extends ActionLeaf
|
||||
|
||||
## Erases the specified key from the blackboard.
|
||||
## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
blackboard.erase_value(key_value)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key]
|
|
@ -0,0 +1,23 @@
|
|||
@tool
|
||||
class_name BlackboardHasCondition extends ConditionLeaf
|
||||
|
||||
## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist.
|
||||
## Returns [code]SUCCESS[/code] if blackboard has the specified key.
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
return SUCCESS if blackboard.has_value(key_value) else FAILURE
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key]
|
|
@ -0,0 +1,33 @@
|
|||
@tool
|
||||
class_name BlackboardSetAction extends ActionLeaf
|
||||
|
||||
## Sets the specified key to the specified value.
|
||||
## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code].
|
||||
|
||||
## Expression representing a blackboard key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = ""
|
||||
## Expression representing a blackboard value to assign to the specified key.
|
||||
@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = ""
|
||||
|
||||
@onready var _key_expression: Expression = _parse_expression(key)
|
||||
@onready var _value_expression: Expression = _parse_expression(value)
|
||||
|
||||
|
||||
func tick(actor: Node, blackboard: Blackboard) -> int:
|
||||
var key_value: Variant = _key_expression.execute([], blackboard)
|
||||
|
||||
if _key_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
var value_value: Variant = _value_expression.execute([], blackboard)
|
||||
|
||||
if _value_expression.has_execute_failed():
|
||||
return FAILURE
|
||||
|
||||
blackboard.set_value(key_value, value_value)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
func _get_expression_sources() -> Array[String]:
|
||||
return [key, value]
|
|
@ -0,0 +1,12 @@
|
|||
@tool
|
||||
@icon("../../icons/condition.svg")
|
||||
class_name ConditionLeaf extends Leaf
|
||||
|
||||
## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
|
||||
## a single simple condition. They should never return `RUNNING`.
|
||||
|
||||
|
||||
func get_class_name() -> Array[StringName]:
|
||||
var classes := super()
|
||||
classes.push_back(&"ConditionLeaf")
|
||||
return classes
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue