Initial Void Bastion 0.20 prototype
This commit is contained in:
commit
7362ca0f02
251 files changed
+29688
No files matched your search
@@ -0,0 +1,201 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
# editor.reload_filesystem — trigger an editor filesystem rescan.
|
||||
# Run this after external file changes (created/moved/deleted by tools outside the editor)
|
||||
# so load() and the FileSystem dock reflect reality.
|
||||
static func reload_filesystem(_params: Dictionary) -> Dictionary:
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
return {"data": {"scanned": true}}
|
||||
|
||||
|
||||
# editor.save_all_scenes — save every open edited scene.
|
||||
static func save_all_scenes(_params: Dictionary) -> Dictionary:
|
||||
EditorInterface.save_all_scenes()
|
||||
return {"data": {"saved": true}}
|
||||
|
||||
|
||||
# editor.state — consolidated editor + project status in one call.
|
||||
# Replaces piecewise scene.current + project.get_setting + etc.
|
||||
static func state(_params: Dictionary) -> Dictionary:
|
||||
var version := Engine.get_version_info()
|
||||
var version_str: String = "%d.%d.%d-%s" % [version.major, version.minor, version.patch, version.status]
|
||||
|
||||
var current_scene_path := ""
|
||||
var current_scene_class := ""
|
||||
var current_scene_name := ""
|
||||
var root := EditorInterface.get_edited_scene_root()
|
||||
if root != null:
|
||||
current_scene_path = root.scene_file_path
|
||||
current_scene_class = root.get_class()
|
||||
current_scene_name = String(root.name)
|
||||
|
||||
var open_scenes: Array = []
|
||||
for s in EditorInterface.get_open_scenes():
|
||||
open_scenes.append(s)
|
||||
|
||||
var project_name := ""
|
||||
if ProjectSettings.has_setting("application/config/name"):
|
||||
project_name = str(ProjectSettings.get_setting("application/config/name"))
|
||||
|
||||
return {"data": {
|
||||
"godot_version": version_str,
|
||||
"project_name": project_name,
|
||||
"current_scene": {
|
||||
"path": current_scene_path,
|
||||
"root_name": current_scene_name,
|
||||
"root_class": current_scene_class,
|
||||
"open": root != null,
|
||||
},
|
||||
"open_scenes": open_scenes,
|
||||
"playing_scene": EditorInterface.is_playing_scene(),
|
||||
"playing_scene_path": EditorInterface.get_playing_scene() if EditorInterface.is_playing_scene() else "",
|
||||
}}
|
||||
|
||||
|
||||
# editor.selection_get — return the currently-selected nodes in the editor tree dock.
|
||||
# Useful for "operate on whatever I clicked" workflows.
|
||||
static func selection_get(_params: Dictionary) -> Dictionary:
|
||||
var selection := EditorInterface.get_selection()
|
||||
var root := EditorInterface.get_edited_scene_root()
|
||||
var out: Array = []
|
||||
for node in selection.get_selected_nodes():
|
||||
var path_str := ""
|
||||
if root != null and (node == root or root.is_ancestor_of(node)):
|
||||
path_str = String(root.get_path_to(node))
|
||||
out.append({
|
||||
"name": String(node.name),
|
||||
"class": node.get_class(),
|
||||
"node_path": path_str,
|
||||
})
|
||||
return {"data": {"selected": out, "count": out.size()}}
|
||||
|
||||
|
||||
# editor.selection_set — select a specific set of nodes in the editor tree dock.
|
||||
# Params: {node_paths: ["Player", "Enemy"]}
|
||||
static func selection_set(params: Dictionary) -> Dictionary:
|
||||
var paths: Array = params.get("node_paths", [])
|
||||
var root := EditorInterface.get_edited_scene_root()
|
||||
if root == null:
|
||||
return {"error": {"code": -32001, "message": "no scene open"}}
|
||||
|
||||
var selection := EditorInterface.get_selection()
|
||||
selection.clear()
|
||||
|
||||
var resolved: Array = []
|
||||
var missing: Array = []
|
||||
for p in paths:
|
||||
var node: Node = root if p == "." else root.get_node_or_null(p)
|
||||
if node == null:
|
||||
missing.append(p)
|
||||
continue
|
||||
selection.add_node(node)
|
||||
resolved.append(p)
|
||||
|
||||
if not missing.is_empty():
|
||||
return {"error": {"code": -32001, "message": "nodes not found: %s" % str(missing)}}
|
||||
return {"data": {"selected": resolved}}
|
||||
|
||||
|
||||
# editor.game_screenshot — capture the viewport of the currently-running game.
|
||||
# Works when the user has pressed F5 (or equivalent) and a game process is running
|
||||
# with our runtime bridge autoload registered. Writes a request file, polls for
|
||||
# the bridge to produce the PNG, returns the path.
|
||||
#
|
||||
# Params: {output?: "res://.godot/agent_tools/game.png", timeout_ms?: 5000}
|
||||
static func game_screenshot(params: Dictionary) -> Dictionary:
|
||||
var output_path: String = params.get("output", "res://.godot/agent_tools/game_screenshot.png")
|
||||
var timeout_ms: int = int(params.get("timeout_ms", 5000))
|
||||
|
||||
if not EditorInterface.is_playing_scene():
|
||||
return {"error": {"code": -32001,
|
||||
"message": "no game running — press F5 (or equivalent) to start the game, then call again. Alternative for arbitrary scenes: run.scene_headless with 'screenshot'."}}
|
||||
|
||||
var bridge_dir := "res://.godot/agent_tools/bridge"
|
||||
if not DirAccess.dir_exists_absolute(bridge_dir):
|
||||
var derr := DirAccess.make_dir_recursive_absolute(bridge_dir)
|
||||
if derr != OK:
|
||||
return {"error": {"code": -32001, "message": "mkdir failed: %d" % derr}}
|
||||
|
||||
# Wipe stale output so we can detect when the bridge has written fresh data.
|
||||
if FileAccess.file_exists(output_path):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.remove_absolute(output_path)
|
||||
|
||||
var request_path := bridge_dir + "/request.json"
|
||||
var request_id := str(Time.get_ticks_msec())
|
||||
var req := {
|
||||
"id": request_id,
|
||||
"type": "screenshot",
|
||||
"output": output_path,
|
||||
}
|
||||
var f := FileAccess.open(request_path, FileAccess.WRITE)
|
||||
if f == null:
|
||||
return {"error": {"code": -32001, "message": "could not write bridge request"}}
|
||||
f.store_string(JSON.stringify(req))
|
||||
f.close()
|
||||
|
||||
# Poll for response — bridge writes response.json with matching id when done.
|
||||
var response_path := bridge_dir + "/response.json"
|
||||
var deadline_usec: int = Time.get_ticks_usec() + timeout_ms * 1000
|
||||
while Time.get_ticks_usec() < deadline_usec:
|
||||
if FileAccess.file_exists(response_path):
|
||||
var rf := FileAccess.open(response_path, FileAccess.READ)
|
||||
if rf:
|
||||
var body := rf.get_as_text()
|
||||
rf.close()
|
||||
var parsed = JSON.parse_string(body)
|
||||
if typeof(parsed) == TYPE_DICTIONARY and parsed.get("id", "") == request_id:
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.remove_absolute(response_path)
|
||||
if parsed.has("error"):
|
||||
return {"error": {"code": -32001, "message": "bridge: %s" % parsed.error}}
|
||||
return {"data": {
|
||||
"path": output_path,
|
||||
"captured": FileAccess.file_exists(output_path),
|
||||
}}
|
||||
OS.delay_msec(50)
|
||||
|
||||
# Clean up stale request if bridge never picked it up.
|
||||
if FileAccess.file_exists(request_path):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.remove_absolute(request_path)
|
||||
return {"error": {"code": -32001,
|
||||
"message": "bridge timed out after %dms — is the running game using the _MCPGameBridge autoload? Re-enable the plugin to re-register it, then press F5 again." % timeout_ms}}
|
||||
|
||||
|
||||
# logs.read — pull recent log lines captured by the game-bridge autoload.
|
||||
# Params: {clear?: false, max_lines?: 200}
|
||||
static func logs_read(params: Dictionary) -> Dictionary:
|
||||
var clear: bool = params.get("clear", false)
|
||||
var max_lines: int = int(params.get("max_lines", 200))
|
||||
var logs_path := "res://.godot/agent_tools/bridge/logs.json"
|
||||
if not FileAccess.file_exists(logs_path):
|
||||
return {"data": {
|
||||
"entries": [],
|
||||
"count": 0,
|
||||
"note": "no log buffer found — the game may not be running or the autoload isn't initialized. Press F5 and try again.",
|
||||
}}
|
||||
var f := FileAccess.open(logs_path, FileAccess.READ)
|
||||
if f == null:
|
||||
return {"error": {"code": -32001, "message": "could not read logs"}}
|
||||
var parsed = JSON.parse_string(f.get_as_text())
|
||||
f.close()
|
||||
if typeof(parsed) != TYPE_ARRAY:
|
||||
parsed = []
|
||||
var entries: Array = parsed
|
||||
if entries.size() > max_lines:
|
||||
entries = entries.slice(entries.size() - max_lines)
|
||||
if clear:
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.remove_absolute(logs_path)
|
||||
return {"data": {"entries": entries, "count": entries.size()}}
|
||||
|
||||
|
||||
# logs.clear — drop the log buffer regardless of read state.
|
||||
static func logs_clear(_params: Dictionary) -> Dictionary:
|
||||
var logs_path := "res://.godot/agent_tools/bridge/logs.json"
|
||||
if FileAccess.file_exists(logs_path):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.remove_absolute(logs_path)
|
||||
return {"data": {"cleared": true}}
|
||||
Reference in new issue
Block a user