Initial Void Bastion 0.20 prototype

This commit is contained in:
Emporex committed 2026-10-03 21:28:56 +02:00
commit 7362ca0f02
251 files changed
+29688

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
@tool
extends RefCounted
const Coerce := preload("res://addons/agent_tools/tools/_coerce.gd")
# resource.create — create a new .tres file.
# Params: {path, type, script?, properties?, overwrite?: false}
# 'type' must be a built-in Resource subclass (StyleBoxFlat, Theme, Curve, etc.).
# For custom Resource subclasses written in GDScript, pass 'script' pointing at the
# class's .gd file instead of 'type'.
static func create(params: Dictionary) -> Dictionary:
var path: String = params.get("path", "")
var type_name: String = params.get("type", "")
var script_path: String = params.get("script", "")
var properties: Dictionary = params.get("properties", {})
var overwrite: bool = params.get("overwrite", false)
if path == "" or not path.begins_with("res://") or not path.ends_with(".tres"):
return _err(-32602, "'path' must be 'res://...' ending in .tres")
if FileAccess.file_exists(path) and not overwrite:
return _err(-32602, "file exists (pass overwrite:true to replace): %s" % path)
var res: Resource
if script_path != "":
var s := ResourceLoader.load(script_path, "Script") as Script
if s == null:
return _err(-32001, "failed to load script: %s" % script_path)
var inst = s.new()
if not (inst is Resource):
return _err(-32602, "script does not produce a Resource")
res = inst
elif type_name != "":
if not ClassDB.class_exists(type_name):
return _err(-32602, "unknown class: %s (use 'script' for custom Resource types)" % type_name)
if not ClassDB.is_parent_class(type_name, "Resource"):
return _err(-32602, "not a Resource subclass: %s" % type_name)
if not ClassDB.can_instantiate(type_name):
return _err(-32602, "class is not instantiable: %s" % type_name)
res = ClassDB.instantiate(type_name) as Resource
else:
return _err(-32602, "provide either 'type' (built-in Resource class) or 'script' (path to custom Resource .gd)")
var apply_res := _apply_properties(res, properties)
if apply_res.has("error"):
return apply_res
var dir_path := path.get_base_dir()
if not DirAccess.dir_exists_absolute(dir_path):
var derr := DirAccess.make_dir_recursive_absolute(dir_path)
if derr != OK:
return _err(-32001, "mkdir failed (%d): %s" % [derr, dir_path])
var save_err := ResourceSaver.save(res, path)
if save_err != OK:
return _err(-32001, "ResourceSaver.save failed: %d" % save_err)
EditorInterface.get_resource_filesystem().update_file(path)
return _ok({"path": path, "class": res.get_class()})
# resource.set_property — load a .tres, set one property, save it back.
# Params: {path, property, value} — same coercion as scene.set_property.
static func set_property(params: Dictionary) -> Dictionary:
var path: String = params.get("path", "")
var property_name: String = params.get("property", "")
if path == "" or property_name == "":
return _err(-32602, "missing 'path' or 'property'")
var res := ResourceLoader.load(path)
if res == null:
return _err(-32001, "failed to load: %s" % path)
var prop_info: Dictionary = {}
for p in res.get_property_list():
if p.name == property_name:
prop_info = p
break
if prop_info.is_empty():
return _err(-32602, "property not found on %s: %s" % [res.get_class(), property_name])
if int(prop_info.get("usage", 0)) & PROPERTY_USAGE_READ_ONLY:
return _err(-32602, "property '%s' on %s is read-only" % [property_name, res.get_class()])
var coerced = Coerce.coerce(params.get("value"), prop_info.type)
if coerced is Dictionary and coerced.has("_error"):
return _err(-32602, coerced._error)
res.set(property_name, coerced)
var stored = res.get(property_name)
if coerced != null and stored == null and prop_info.type == TYPE_OBJECT:
return _err(-32001,
"property '%s' on %s was not accepted (assignment silently dropped — target expects a %s; passed value type didn't match)" %
[property_name, res.get_class(), prop_info.get("hint_string", "Resource")])
var save_err := ResourceSaver.save(res, path)
if save_err != OK:
return _err(-32001, "save failed: %d" % save_err)
EditorInterface.get_resource_filesystem().update_file(path)
return _ok({"path": path, "property": property_name, "value": Coerce.to_json(stored)})
# resource.call_method — load a .tres, invoke a method, save, return the method's result.
# Useful for helper methods that aren't properties — e.g. StyleBoxFlat.set_border_width_all(4)
# or set_corner_radius_all(14). Args are coerced against the method's declared types.
# Params: {path, method, args?: [], save?: true}
static func call_method(params: Dictionary) -> Dictionary:
var path: String = params.get("path", "")
var method_name: String = params.get("method", "")
var args: Array = params.get("args", [])
var should_save: bool = params.get("save", true)
if path == "" or method_name == "":
return _err(-32602, "missing 'path' or 'method'")
var res := ResourceLoader.load(path)
if res == null:
return _err(-32001, "failed to load: %s" % path)
if not res.has_method(method_name):
return _err(-32602, "method not found: %s.%s" % [res.get_class(), method_name])
var coerced_args = _coerce_args(res, method_name, args)
if coerced_args is Dictionary:
return coerced_args # error passthrough (same reason as scene.call_method: no type annotation)
var result = res.callv(method_name, coerced_args)
if should_save:
var save_err := ResourceSaver.save(res, path)
if save_err != OK:
return _err(-32001, "save failed: %d" % save_err)
EditorInterface.get_resource_filesystem().update_file(path)
return _ok({
"path": path,
"method": method_name,
"return": Coerce.to_json(result),
"saved": should_save,
})
static func _coerce_args(obj: Object, method_name: String, args: Array):
var method_info: Dictionary = {}
for m in obj.get_method_list():
if m.name == method_name:
method_info = m
break
var arg_types: Array = method_info.get("args", [])
var out: Array = []
for i in args.size():
if i < arg_types.size():
var coerced = Coerce.coerce(args[i], arg_types[i].type)
if coerced is Dictionary and coerced.has("_error"):
return _err(-32602, "arg %d: %s" % [i, coerced._error])
out.append(coerced)
else:
out.append(args[i])
return out
static func _apply_properties(res: Resource, props: Dictionary) -> Dictionary:
var by_name: Dictionary = {}
for p in res.get_property_list():
by_name[p.name] = p
for key in props:
if not by_name.has(key):
return _err(-32602, "property not found on %s: %s" % [res.get_class(), key])
var coerced = Coerce.coerce(props[key], by_name[key].type)
if coerced is Dictionary and coerced.has("_error"):
return _err(-32602, "property %s: %s" % [key, coerced._error])
res.set(key, coerced)
return {}
static func _ok(data) -> Dictionary:
return {"data": data}
static func _err(code: int, msg: String) -> Dictionary:
return {"error": {"code": code, "message": msg}}