add beehave
This commit is contained in:
@@ -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 == BeehaveNode.SUCCESS or status == BeehaveNode.RUNNING:
|
||||
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:
|
||||
BeehaveNode.SUCCESS:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(SUCCESS_COLOR)
|
||||
BeehaveNode.FAILURE:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
BeehaveNode.RUNNING:
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user