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
		
	
	
		
			7.4 KiB
		
	
	
	
		
			GDScript
		
	
			
		
		
	
	
			260 lines
		
	
	
		
			7.4 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"
 |