Files
Void-Bastion/addons/agent_tools/tools/signal_tools.gd
T

149 lines
5.3 KiB
GDScript

@tool
extends RefCounted
# signal.connect — operates on the currently-edited scene.
# Params: {from: "Player", signal: "died", to: ".", method: "_on_player_died"}
# Uses CONNECT_PERSIST so the connection is serialized into the .tscn as a [connection] block.
static func connect_signal(params: Dictionary) -> Dictionary:
var from_path: String = params.get("from", "")
var signal_name: String = params.get("signal", "")
var to_path: String = params.get("to", "")
var method_name: String = params.get("method", "")
for required in ["from", "signal", "to", "method"]:
if params.get(required, "") == "":
return _err(-32602, "missing '%s'" % required)
var root := EditorInterface.get_edited_scene_root()
if root == null:
return _err(-32001, "no scene open")
var source: Node = root if from_path == "." else root.get_node_or_null(from_path)
if source == null:
return _err(-32001, "source node not found: %s" % from_path)
var target: Node = root if to_path == "." else root.get_node_or_null(to_path)
if target == null:
return _err(-32001, "target node not found: %s" % to_path)
if not source.has_signal(signal_name):
return _err(-32602, "%s has no signal '%s'" % [source.get_class(), signal_name])
if not target.has_method(method_name):
return _err(-32602, "target has no method '%s' — attach a script with it first" % method_name)
# Arity check: number of non-default signal args must be <= target method arg count.
var sig_info := _find_signal(source, signal_name)
var method_info := _find_method(target, method_name)
if sig_info and method_info:
var sig_args: int = (sig_info.args as Array).size()
var method_args: int = (method_info.args as Array).size()
if method_args < sig_args:
return _err(-32602, "arity mismatch: signal '%s' emits %d args, method '%s' accepts %d" %
[signal_name, sig_args, method_name, method_args])
var callable := Callable(target, method_name)
if source.is_connected(signal_name, callable):
return _ok({"already_connected": true})
var err := source.connect(signal_name, callable, CONNECT_PERSIST)
if err != OK:
return _err(-32001, "connect() returned %d" % err)
EditorInterface.mark_scene_as_unsaved()
return _ok({
"from": from_path,
"signal": signal_name,
"to": to_path,
"method": method_name,
})
# signal.disconnect — remove a specific wiring in the currently-edited scene.
# Params: {from, signal, to, method} — same shape as signal.connect.
static func disconnect_signal(params: Dictionary) -> Dictionary:
for required in ["from", "signal", "to", "method"]:
if params.get(required, "") == "":
return _err(-32602, "missing '%s'" % required)
var root := EditorInterface.get_edited_scene_root()
if root == null:
return _err(-32001, "no scene open")
var from_path: String = params["from"]
var to_path: String = params["to"]
var source: Node = root if from_path == "." else root.get_node_or_null(from_path)
var target: Node = root if to_path == "." else root.get_node_or_null(to_path)
if source == null:
return _err(-32001, "source not found: %s" % from_path)
if target == null:
return _err(-32001, "target not found: %s" % to_path)
var callable := Callable(target, params["method"])
if not source.is_connected(params["signal"], callable):
return _err(-32001, "no such connection")
source.disconnect(params["signal"], callable)
EditorInterface.mark_scene_as_unsaved()
return _ok({"disconnected": true})
# signal.list — list outgoing connections on a node in the currently-edited scene.
# Params: {node_path, persistent_only?: false}
# Returns connections with their flags so callers can distinguish editor-serialized
# connections from runtime ones.
static func list_signals(params: Dictionary) -> Dictionary:
var node_path: String = params.get("node_path", "")
var persistent_only: bool = params.get("persistent_only", false)
if node_path == "":
return _err(-32602, "missing 'node_path'")
var root := EditorInterface.get_edited_scene_root()
if root == null:
return _err(-32001, "no scene open")
var node: Node = root if node_path == "." else root.get_node_or_null(node_path)
if node == null:
return _err(-32001, "node not found: %s" % node_path)
var outgoing: Array = []
for sig in node.get_signal_list():
var sig_name: String = sig.name
for c in node.get_signal_connection_list(sig_name):
var flags: int = c.flags
var is_persistent: bool = bool(flags & CONNECT_PERSIST)
if persistent_only and not is_persistent:
continue
var cb: Callable = c.callable
var target: Object = cb.get_object()
var target_path := ""
if target is Node and (target == root or root.is_ancestor_of(target)):
target_path = String(root.get_path_to(target))
outgoing.append({
"signal": sig_name,
"to": target_path,
"to_object": str(target) if target_path == "" else "",
"method": cb.get_method(),
"flags": flags,
"persistent": is_persistent,
})
return _ok({"node_path": node_path, "outgoing": outgoing})
static func _find_signal(obj: Object, name: String) -> Dictionary:
for s in obj.get_signal_list():
if s.name == name:
return s
return {}
static func _find_method(obj: Object, name: String) -> Dictionary:
for m in obj.get_method_list():
if m.name == name:
return m
return {}
static func _ok(data) -> Dictionary:
return {"data": data}
static func _err(code: int, msg: String) -> Dictionary:
return {"error": {"code": code, "message": msg}}