|  |  |  | @tool | 
					
						
							|  |  |  | extends GraphEdit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const BeehaveGraphNode := preload("graph_node.gd") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") | 
					
						
							|  |  |  | const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const PROGRESS_SHIFT: int = 50 | 
					
						
							|  |  |  | const INACTIVE_COLOR: Color = Color("#898989aa") | 
					
						
							|  |  |  | const ACTIVE_COLOR: Color = Color("#ffcc00c8") | 
					
						
							|  |  |  | const SUCCESS_COLOR: Color = Color("#009944c8") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var updating_graph: bool = false | 
					
						
							|  |  |  | var arraging_nodes: bool = false | 
					
						
							|  |  |  | var beehave_tree: Dictionary: | 
					
						
							|  |  |  |     set(value): | 
					
						
							|  |  |  |         if beehave_tree == value: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         beehave_tree = value | 
					
						
							|  |  |  |         active_nodes.clear() | 
					
						
							|  |  |  |         _update_graph() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var horizontal_layout: bool = false: | 
					
						
							|  |  |  |     set(value): | 
					
						
							|  |  |  |         if updating_graph or arraging_nodes: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         if horizontal_layout == value: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         horizontal_layout = value | 
					
						
							|  |  |  |         _update_layout_button() | 
					
						
							|  |  |  |         _update_graph() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var active_nodes: Array[String] | 
					
						
							|  |  |  | var progress: int = 0 | 
					
						
							|  |  |  | var layout_button: Button | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _ready() -> void: | 
					
						
							|  |  |  |     custom_minimum_size = Vector2(100, 300) | 
					
						
							|  |  |  |     #arrange_nodes_button_hidden = true | 
					
						
							|  |  |  |     minimap_enabled = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     layout_button = Button.new() | 
					
						
							|  |  |  |     layout_button.flat = true | 
					
						
							|  |  |  |     layout_button.focus_mode = Control.FOCUS_NONE | 
					
						
							|  |  |  |     layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout) | 
					
						
							|  |  |  |     get_menu_hbox().add_child(layout_button) | 
					
						
							|  |  |  |     _update_layout_button() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _update_graph() -> void: | 
					
						
							|  |  |  |     if updating_graph: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     updating_graph = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     clear_connections() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for child in get_children(): | 
					
						
							|  |  |  |         remove_child(child) | 
					
						
							|  |  |  |         child.queue_free() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if not beehave_tree.is_empty(): | 
					
						
							|  |  |  |         _add_nodes(beehave_tree) | 
					
						
							|  |  |  |         _connect_nodes(beehave_tree) | 
					
						
							|  |  |  |         _arrange_nodes.call_deferred(beehave_tree) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     updating_graph = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _add_nodes(node: Dictionary) -> void: | 
					
						
							|  |  |  |     if node.is_empty(): | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     var gnode := BeehaveGraphNode.new(horizontal_layout) | 
					
						
							|  |  |  |     add_child(gnode) | 
					
						
							|  |  |  |     gnode.title_text = node.name | 
					
						
							|  |  |  |     gnode.name = node.id | 
					
						
							|  |  |  |     gnode.icon = _get_icon(node.type.back()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if node.type.has(&"BeehaveTree"): | 
					
						
							|  |  |  |         gnode.set_slots(false, true) | 
					
						
							|  |  |  |     elif node.type.has(&"Leaf"): | 
					
						
							|  |  |  |         gnode.set_slots(true, false) | 
					
						
							|  |  |  |     elif node.type.has(&"Composite") or node.type.has(&"Decorator"): | 
					
						
							|  |  |  |         gnode.set_slots(true, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for child in node.get("children", []): | 
					
						
							|  |  |  |         _add_nodes(child) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _connect_nodes(node: Dictionary) -> void: | 
					
						
							|  |  |  |     for child in node.get("children", []): | 
					
						
							|  |  |  |         connect_node(node.id, 0, child.id, 0) | 
					
						
							|  |  |  |         _connect_nodes(child) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _arrange_nodes(node: Dictionary) -> void: | 
					
						
							|  |  |  |     if arraging_nodes: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     arraging_nodes = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var tree_node := _create_tree_nodes(node) | 
					
						
							|  |  |  |     tree_node.update_positions(horizontal_layout) | 
					
						
							|  |  |  |     _place_nodes(tree_node) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     arraging_nodes = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode: | 
					
						
							|  |  |  |     var tree_node := TreeNode.new(get_node(node.id), root) | 
					
						
							|  |  |  |     for child in node.get("children", []): | 
					
						
							|  |  |  |         var child_node := _create_tree_nodes(child, tree_node) | 
					
						
							|  |  |  |         tree_node.children.push_back(child_node) | 
					
						
							|  |  |  |     return tree_node | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _place_nodes(node: TreeNode) -> void: | 
					
						
							|  |  |  |     node.item.position_offset = Vector2(node.x, node.y) | 
					
						
							|  |  |  |     for child in node.children: | 
					
						
							|  |  |  |         _place_nodes(child) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _get_icon(type: StringName) -> Texture2D: | 
					
						
							|  |  |  |     var classes := ProjectSettings.get_global_class_list() | 
					
						
							|  |  |  |     for c in classes: | 
					
						
							|  |  |  |         if c["class"] == type: | 
					
						
							|  |  |  |             var icon_path := c.get("icon", String()) | 
					
						
							|  |  |  |             if not icon_path.is_empty(): | 
					
						
							|  |  |  |                 return load(icon_path) | 
					
						
							|  |  |  |     return null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func get_status(status: int) -> String: | 
					
						
							|  |  |  |     if status == 0: | 
					
						
							|  |  |  |         return "SUCCESS" | 
					
						
							|  |  |  |     elif status == 1: | 
					
						
							|  |  |  |         return "FAILURE" | 
					
						
							|  |  |  |     return "RUNNING" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func process_begin(instance_id: int) -> void: | 
					
						
							|  |  |  |     if not _is_same_tree(instance_id): | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for child in get_children(): | 
					
						
							|  |  |  |         child.set_meta("status", -1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func process_tick(instance_id: int, status: int) -> void: | 
					
						
							|  |  |  |     var node := get_node_or_null(str(instance_id)) | 
					
						
							|  |  |  |     if node: | 
					
						
							|  |  |  |         node.text = "Status: %s" % get_status(status) | 
					
						
							|  |  |  |         node.set_status(status) | 
					
						
							|  |  |  |         node.set_meta("status", status) | 
					
						
							|  |  |  |         if status == 0 or status == 2: | 
					
						
							|  |  |  |             if not active_nodes.has(node.name): | 
					
						
							|  |  |  |                 active_nodes.push_back(node.name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func process_end(instance_id: int) -> void: | 
					
						
							|  |  |  |     if not _is_same_tree(instance_id): | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for child in get_children(): | 
					
						
							|  |  |  |         var status := child.get_meta("status", -1) | 
					
						
							|  |  |  |         match status: | 
					
						
							|  |  |  |             0: | 
					
						
							|  |  |  |                 active_nodes.erase(child.name) | 
					
						
							|  |  |  |                 child.set_color(SUCCESS_COLOR) | 
					
						
							|  |  |  |             1: | 
					
						
							|  |  |  |                 active_nodes.erase(child.name) | 
					
						
							|  |  |  |                 child.set_color(INACTIVE_COLOR) | 
					
						
							|  |  |  |             2: | 
					
						
							|  |  |  |                 child.set_color(ACTIVE_COLOR) | 
					
						
							|  |  |  |             _: | 
					
						
							|  |  |  |                 child.text = " " | 
					
						
							|  |  |  |                 child.set_status(status) | 
					
						
							|  |  |  |                 child.set_color(INACTIVE_COLOR) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _is_same_tree(instance_id: int) -> bool: | 
					
						
							|  |  |  |     return str(instance_id) == beehave_tree.get("id", "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: | 
					
						
							|  |  |  |     var points: PackedVector2Array | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     from_position = from_position.round() | 
					
						
							|  |  |  |     to_position = to_position.round() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     points.push_back(from_position) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var mid_position := ((to_position + from_position) / 2).round() | 
					
						
							|  |  |  |     if horizontal_layout: | 
					
						
							|  |  |  |         points.push_back(Vector2(mid_position.x, from_position.y)) | 
					
						
							|  |  |  |         points.push_back(Vector2(mid_position.x, to_position.y)) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         points.push_back(Vector2(from_position.x, mid_position.y)) | 
					
						
							|  |  |  |         points.push_back(Vector2(to_position.x, mid_position.y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     points.push_back(to_position) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return points | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _process(delta: float) -> void: | 
					
						
							|  |  |  |     if not active_nodes.is_empty(): | 
					
						
							|  |  |  |         progress += 10 if delta >= 0.05 else 1 | 
					
						
							|  |  |  |         if progress >= 1000: | 
					
						
							|  |  |  |             progress = 0 | 
					
						
							|  |  |  |         queue_redraw() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _draw() -> void: | 
					
						
							|  |  |  |     if active_nodes.is_empty(): | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var circle_size: float = max(3, 6 * zoom) | 
					
						
							|  |  |  |     var progress_shift: float = PROGRESS_SHIFT * zoom | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var connections := get_connection_list() | 
					
						
							|  |  |  |     for c in connections: | 
					
						
							|  |  |  |         if not c.from in active_nodes or not c.to in active_nodes: | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         var from := get_node(String(c.from)) | 
					
						
							|  |  |  |         var to := get_node(String(c.to)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var line := _get_connection_line(from.position + from.get_connection_output_position(c.from_port), to.position + to.get_connection_input_position(c.to_port)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var curve = Curve2D.new() | 
					
						
							|  |  |  |         for l in line: | 
					
						
							|  |  |  |             curve.add_point(l) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var max_steps := int(curve.get_baked_length()) | 
					
						
							|  |  |  |         var current_shift := progress % max_steps | 
					
						
							|  |  |  |         var p := curve.sample_baked(current_shift) | 
					
						
							|  |  |  |         draw_circle(p, circle_size, ACTIVE_COLOR) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var shift := current_shift - progress_shift | 
					
						
							|  |  |  |         while shift >= 0: | 
					
						
							|  |  |  |             draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) | 
					
						
							|  |  |  |             shift -= progress_shift | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         shift = current_shift + progress_shift | 
					
						
							|  |  |  |         while shift <= curve.get_baked_length(): | 
					
						
							|  |  |  |             draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) | 
					
						
							|  |  |  |             shift += progress_shift | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func _update_layout_button() -> void: | 
					
						
							|  |  |  |     layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON | 
					
						
							|  |  |  |     layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" |