|  |  |  | @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" |