You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			260 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			GDScript
		
	
			
		
		
	
	
			260 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			GDScript
		
	
| @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"
 |