@beremaran/godot-agent-loop 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +925 -0
- package/addons/godot_agent_loop/LICENSE +23 -0
- package/addons/godot_agent_loop/README.md +31 -0
- package/addons/godot_agent_loop/plugin.cfg +8 -0
- package/addons/godot_agent_loop/plugin.gd +417 -0
- package/agent-plugin/.claude-plugin/plugin.json +19 -0
- package/agent-plugin/.codex-plugin/plugin.json +37 -0
- package/agent-plugin/.mcp.json +14 -0
- package/agent-plugin/adapter-manifest.json +45 -0
- package/agent-plugin/pi/extension.ts +136 -0
- package/agent-plugin/skills/build-godot-game/SKILL.md +41 -0
- package/agent-plugin/skills/debug-godot-game/SKILL.md +37 -0
- package/agent-plugin/skills/ship-godot-game/SKILL.md +46 -0
- package/agent-plugin/skills/ship-godot-game/agents/openai.yaml +4 -0
- package/agent-plugin/skills/verify-godot-change/SKILL.md +35 -0
- package/build/authoring-session-manager.js +237 -0
- package/build/domain-tool-registries.js +207 -0
- package/build/editor-bridge-protocol.js +1 -0
- package/build/editor-connection.js +112 -0
- package/build/editor-mutation-guard.js +30 -0
- package/build/editor-plugin-installer.js +220 -0
- package/build/engine-api/extension_api-4.7.stable.official.5b4e0cb0f.json +356830 -0
- package/build/game-command-service.js +49 -0
- package/build/game-connection.js +337 -0
- package/build/godot-executable.js +156 -0
- package/build/godot-process-manager.js +112 -0
- package/build/godot-subprocess.js +23 -0
- package/build/headless-operation-runner.js +68 -0
- package/build/headless-operation-service.js +65 -0
- package/build/index.js +478 -0
- package/build/interaction-server-installer.js +234 -0
- package/build/opencode-setup.js +199 -0
- package/build/project-support.js +219 -0
- package/build/runtime-protocol.js +202 -0
- package/build/scripts/godot_operations.gd +1534 -0
- package/build/scripts/mcp_editor_plugin.gd +417 -0
- package/build/scripts/mcp_interaction_server.gd +1255 -0
- package/build/scripts/mcp_runtime/audio_animation_domain.gd +568 -0
- package/build/scripts/mcp_runtime/command_params.gd +339 -0
- package/build/scripts/mcp_runtime/core_domain.gd +591 -0
- package/build/scripts/mcp_runtime/input_domain.gd +695 -0
- package/build/scripts/mcp_runtime/networking_domain.gd +356 -0
- package/build/scripts/mcp_runtime/physics_domain.gd +653 -0
- package/build/scripts/mcp_runtime/privileged_command_policy.gd +81 -0
- package/build/scripts/mcp_runtime/rendering_domain.gd +807 -0
- package/build/scripts/mcp_runtime/runtime_domain.gd +108 -0
- package/build/scripts/mcp_runtime/scene_2d_domain.gd +527 -0
- package/build/scripts/mcp_runtime/scene_3d_domain.gd +573 -0
- package/build/scripts/mcp_runtime/system_domain.gd +277 -0
- package/build/scripts/mcp_runtime/ui_domain.gd +619 -0
- package/build/scripts/mcp_runtime/variant_codec.gd +335 -0
- package/build/scripts/validate_script.gd +28 -0
- package/build/server-instructions.js +11 -0
- package/build/session-timing.js +20 -0
- package/build/tool-argument-validation.js +120 -0
- package/build/tool-definitions.js +2727 -0
- package/build/tool-handlers/game-tool-handlers.js +1345 -0
- package/build/tool-handlers/lifecycle-tool-handlers.js +346 -0
- package/build/tool-handlers/project-handler-services.js +1458 -0
- package/build/tool-handlers/project-tool-handlers.js +1506 -0
- package/build/tool-handlers/visual-regression-service.js +139 -0
- package/build/tool-manifest.js +1193 -0
- package/build/tool-mutation-policy.js +122 -0
- package/build/tool-registry.js +34 -0
- package/build/tool-surface.js +70 -0
- package/build/utils.js +273 -0
- package/package.json +110 -0
- package/product.json +52 -0
- package/scripts/prepare-package.js +42 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
extends "res://mcp_runtime/runtime_domain.gd"
|
|
2
|
+
|
|
3
|
+
# Rendering and environment domain. Stateful visual-shader graphs and debug-draw
|
|
4
|
+
# objects live here with the handlers that own them.
|
|
5
|
+
|
|
6
|
+
func register_commands() -> void:
|
|
7
|
+
register_command("get_camera", _cmd_get_camera)
|
|
8
|
+
register_command("set_camera", _cmd_set_camera)
|
|
9
|
+
register_command("camera_attributes", _cmd_camera_attributes)
|
|
10
|
+
register_command("set_shader_param", _cmd_set_shader_param)
|
|
11
|
+
register_command("visual_shader", _cmd_visual_shader)
|
|
12
|
+
register_command("environment", _cmd_environment)
|
|
13
|
+
register_command("set_particles", _cmd_set_particles)
|
|
14
|
+
register_command("viewport", _cmd_viewport)
|
|
15
|
+
register_command("debug_draw", _cmd_debug_draw)
|
|
16
|
+
register_command("render_settings", _cmd_render_settings)
|
|
17
|
+
register_command("sky", _cmd_sky)
|
|
18
|
+
register_command("gi", _cmd_gi)
|
|
19
|
+
register_command("video", _cmd_video)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
func _exit_tree() -> void:
|
|
23
|
+
_clear_debug_draw()
|
|
24
|
+
_visual_shaders.clear()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func _process(_delta: float) -> void:
|
|
28
|
+
for index in range(_debug_meshes.size() - 1, -1, -1):
|
|
29
|
+
var entry: Dictionary = _debug_meshes[index]
|
|
30
|
+
var frames_left: int = entry.get("frames_left", 0)
|
|
31
|
+
if frames_left <= 0:
|
|
32
|
+
continue
|
|
33
|
+
frames_left -= 1
|
|
34
|
+
if frames_left == 0:
|
|
35
|
+
var drawn: Variant = entry.get("node")
|
|
36
|
+
if drawn is Node and is_instance_valid(drawn):
|
|
37
|
+
var node: Node = drawn
|
|
38
|
+
node.queue_free()
|
|
39
|
+
_debug_meshes.remove_at(index)
|
|
40
|
+
else:
|
|
41
|
+
entry["frames_left"] = frames_left
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
func _as_environment(value: Variant) -> Environment:
|
|
45
|
+
if value is Environment:
|
|
46
|
+
return value
|
|
47
|
+
return null
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
func _clear_debug_draw() -> void:
|
|
51
|
+
for entry: Dictionary in _debug_meshes:
|
|
52
|
+
var drawn: Variant = entry.get("node")
|
|
53
|
+
if drawn is Node and is_instance_valid(drawn):
|
|
54
|
+
var node: Node = drawn
|
|
55
|
+
node.queue_free()
|
|
56
|
+
_debug_meshes.clear()
|
|
57
|
+
if _debug_draw_node != null and is_instance_valid(_debug_draw_node):
|
|
58
|
+
_debug_draw_node.queue_free()
|
|
59
|
+
_debug_draw_node = null
|
|
60
|
+
|
|
61
|
+
func _cmd_get_camera(_params: Dictionary) -> void:
|
|
62
|
+
var result: Dictionary = {"success": true}
|
|
63
|
+
|
|
64
|
+
var cam2d: Camera2D = get_viewport().get_camera_2d()
|
|
65
|
+
if cam2d != null:
|
|
66
|
+
result["camera_2d"] = {
|
|
67
|
+
"position": {"x": cam2d.global_position.x, "y": cam2d.global_position.y},
|
|
68
|
+
"rotation": cam2d.global_rotation,
|
|
69
|
+
"zoom": {"x": cam2d.zoom.x, "y": cam2d.zoom.y},
|
|
70
|
+
"path": str(cam2d.get_path())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var cam3d: Camera3D = get_viewport().get_camera_3d()
|
|
74
|
+
if cam3d != null:
|
|
75
|
+
result["camera_3d"] = {
|
|
76
|
+
"position": {"x": cam3d.global_position.x, "y": cam3d.global_position.y, "z": cam3d.global_position.z},
|
|
77
|
+
"rotation": {"x": rad_to_deg(cam3d.global_rotation.x), "y": rad_to_deg(cam3d.global_rotation.y), "z": rad_to_deg(cam3d.global_rotation.z)},
|
|
78
|
+
"fov": cam3d.fov,
|
|
79
|
+
"path": str(cam3d.get_path())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if cam2d == null and cam3d == null:
|
|
83
|
+
result["error"] = "No active camera found"
|
|
84
|
+
result["success"] = false
|
|
85
|
+
|
|
86
|
+
respond(result)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# --- Set Camera ---
|
|
90
|
+
func _cmd_set_camera(params: Dictionary) -> void:
|
|
91
|
+
var cam2d: Camera2D = get_viewport().get_camera_2d()
|
|
92
|
+
var cam3d: Camera3D = get_viewport().get_camera_3d()
|
|
93
|
+
|
|
94
|
+
if cam2d == null and cam3d == null:
|
|
95
|
+
respond({"error": "No active camera found"})
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if cam2d != null:
|
|
99
|
+
if params.has("position"):
|
|
100
|
+
var pos: Dictionary = params["position"]
|
|
101
|
+
cam2d.global_position = Vector2(CommandParams.json_float(pos, "x", cam2d.global_position.x), CommandParams.json_float(pos, "y", cam2d.global_position.y))
|
|
102
|
+
if params.has("rotation"):
|
|
103
|
+
var rot: Dictionary = params["rotation"]
|
|
104
|
+
cam2d.global_rotation = deg_to_rad(CommandParams.json_float(rot, "z", rad_to_deg(cam2d.global_rotation)))
|
|
105
|
+
if params.has("zoom"):
|
|
106
|
+
var z: Dictionary = params["zoom"]
|
|
107
|
+
cam2d.zoom = Vector2(CommandParams.json_float(z, "x", cam2d.zoom.x), CommandParams.json_float(z, "y", cam2d.zoom.y))
|
|
108
|
+
respond({"success": true, "camera": "2d", "position": variant_to_json(cam2d.global_position), "zoom": variant_to_json(cam2d.zoom)})
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
if cam3d != null:
|
|
112
|
+
if params.has("position"):
|
|
113
|
+
var pos: Dictionary = params["position"]
|
|
114
|
+
cam3d.global_position = Vector3(CommandParams.json_float(pos, "x", cam3d.global_position.x), CommandParams.json_float(pos, "y", cam3d.global_position.y), CommandParams.json_float(pos, "z", cam3d.global_position.z))
|
|
115
|
+
if params.has("rotation"):
|
|
116
|
+
var rot: Dictionary = params["rotation"]
|
|
117
|
+
cam3d.global_rotation = Vector3(deg_to_rad(CommandParams.json_float(rot, "x", rad_to_deg(cam3d.global_rotation.x))), deg_to_rad(CommandParams.json_float(rot, "y", rad_to_deg(cam3d.global_rotation.y))), deg_to_rad(CommandParams.json_float(rot, "z", rad_to_deg(cam3d.global_rotation.z))))
|
|
118
|
+
if params.has("fov"):
|
|
119
|
+
cam3d.fov = CommandParams.to_float(params["fov"])
|
|
120
|
+
respond({"success": true, "camera": "3d", "position": variant_to_json(cam3d.global_position), "rotation": variant_to_json(cam3d.global_rotation)})
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# --- Get Audio ---
|
|
125
|
+
func _cmd_set_shader_param(params: Dictionary) -> void:
|
|
126
|
+
var reader := CommandParams.new(params)
|
|
127
|
+
var node: Node = require_node(reader)
|
|
128
|
+
var param_name: String = reader.required_string("param_name")
|
|
129
|
+
if params_invalid(reader):
|
|
130
|
+
return
|
|
131
|
+
var node_path: String = str(node.get_path())
|
|
132
|
+
|
|
133
|
+
var material: Material = null
|
|
134
|
+
# Try material_override first (MeshInstance3D/2D)
|
|
135
|
+
if node.get("material_override") != null:
|
|
136
|
+
material = node.get("material_override")
|
|
137
|
+
# Try surface override material (MeshInstance3D)
|
|
138
|
+
elif node is MeshInstance3D:
|
|
139
|
+
var mesh_instance: MeshInstance3D = node
|
|
140
|
+
material = mesh_instance.get_surface_override_material(0)
|
|
141
|
+
# Try material property (CanvasItem, e.g. Sprite2D)
|
|
142
|
+
elif node.get("material") != null:
|
|
143
|
+
material = node.get("material")
|
|
144
|
+
|
|
145
|
+
if material == null or not material is ShaderMaterial:
|
|
146
|
+
respond({"error": "No ShaderMaterial found on node: %s" % node_path})
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
var shader_mat: ShaderMaterial = material as ShaderMaterial
|
|
150
|
+
var raw_value: Variant = params.get("value", null)
|
|
151
|
+
var type_hint: String = params.get("type_hint", "")
|
|
152
|
+
var value: Variant = json_to_variant(raw_value, type_hint)
|
|
153
|
+
shader_mat.set_shader_parameter(param_name, value)
|
|
154
|
+
respond({"success": true, "node_path": node_path, "param_name": param_name, "value": variant_to_json(shader_mat.get_shader_parameter(param_name))})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# --- Visual Shader ---
|
|
158
|
+
# Shaders built through the visual_shader command live here until applied to a
|
|
159
|
+
# node; ids let a client build several graphs. Edits target the fragment
|
|
160
|
+
# function, which is where the tool's node/connection workflow operates.
|
|
161
|
+
var _visual_shaders: Dictionary = {}
|
|
162
|
+
var _next_visual_shader_id: int = 1
|
|
163
|
+
|
|
164
|
+
const VISUAL_SHADER_MODES: Dictionary = {
|
|
165
|
+
"spatial": Shader.MODE_SPATIAL,
|
|
166
|
+
"canvas_item": Shader.MODE_CANVAS_ITEM,
|
|
167
|
+
"particles": Shader.MODE_PARTICLES,
|
|
168
|
+
"sky": Shader.MODE_SKY,
|
|
169
|
+
"fog": Shader.MODE_FOG,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func _cmd_visual_shader(params: Dictionary) -> void:
|
|
173
|
+
var reader := CommandParams.new(params)
|
|
174
|
+
var action: String = reader.required_enum("action", ["create", "add_node", "connect", "disconnect", "get_nodes", "apply"])
|
|
175
|
+
if params_invalid(reader):
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if action == "create":
|
|
179
|
+
var shader_type: String = params.get("shader_type", "spatial")
|
|
180
|
+
if not VISUAL_SHADER_MODES.has(shader_type):
|
|
181
|
+
respond({"error": "Unknown shader_type: %s" % shader_type})
|
|
182
|
+
return
|
|
183
|
+
var created: VisualShader = VisualShader.new()
|
|
184
|
+
var mode: Shader.Mode = VISUAL_SHADER_MODES[shader_type]
|
|
185
|
+
created.set_mode(mode)
|
|
186
|
+
var shader_id: int = _next_visual_shader_id
|
|
187
|
+
_next_visual_shader_id += 1
|
|
188
|
+
_visual_shaders[shader_id] = created
|
|
189
|
+
respond({"success": true, "shader_id": shader_id, "shader_type": shader_type})
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Every other action edits an existing graph: the one named by shader_id,
|
|
193
|
+
# or the most recently created one.
|
|
194
|
+
var target_id: int = reader.optional_int("shader_id", _next_visual_shader_id - 1, 1)
|
|
195
|
+
if params_invalid(reader):
|
|
196
|
+
return
|
|
197
|
+
var shader: VisualShader = _visual_shaders.get(target_id)
|
|
198
|
+
if shader == null:
|
|
199
|
+
respond({"error": "No visual shader with id %s; use action create first" % target_id})
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
match action:
|
|
203
|
+
"add_node":
|
|
204
|
+
var node_class: String = reader.required_string("node_class")
|
|
205
|
+
if params_invalid(reader):
|
|
206
|
+
return
|
|
207
|
+
if not ClassDB.class_exists(node_class) or not ClassDB.is_parent_class(node_class, "VisualShaderNode"):
|
|
208
|
+
respond({"error": "Class '%s' is not a VisualShaderNode type" % node_class})
|
|
209
|
+
return
|
|
210
|
+
var instantiated: Variant = ClassDB.instantiate(node_class)
|
|
211
|
+
if not instantiated is VisualShaderNode:
|
|
212
|
+
respond({"error": "Failed to instantiate: %s" % node_class})
|
|
213
|
+
return
|
|
214
|
+
var graph_node: VisualShaderNode = instantiated
|
|
215
|
+
var position: Dictionary = CommandParams.json_dictionary(params, "position")
|
|
216
|
+
var node_id: int = shader.get_valid_node_id(VisualShader.TYPE_FRAGMENT)
|
|
217
|
+
shader.add_node(VisualShader.TYPE_FRAGMENT, graph_node, CommandParams.to_vector2(position), node_id)
|
|
218
|
+
respond({"success": true, "shader_id": target_id, "node_id": node_id, "node_class": node_class})
|
|
219
|
+
"connect":
|
|
220
|
+
var from_node: int = reader.required_int("from_node", 0)
|
|
221
|
+
var from_port: int = reader.required_int("from_port", 0)
|
|
222
|
+
var to_node: int = reader.required_int("to_node", 0)
|
|
223
|
+
var to_port: int = reader.required_int("to_port", 0)
|
|
224
|
+
if params_invalid(reader):
|
|
225
|
+
return
|
|
226
|
+
var err: int = shader.connect_nodes(VisualShader.TYPE_FRAGMENT, from_node, from_port, to_node, to_port)
|
|
227
|
+
if err != OK:
|
|
228
|
+
respond({"error": "Failed to connect nodes (error %d)" % err})
|
|
229
|
+
return
|
|
230
|
+
respond({"success": true, "shader_id": target_id})
|
|
231
|
+
"disconnect":
|
|
232
|
+
var from_node: int = reader.required_int("from_node", 0)
|
|
233
|
+
var from_port: int = reader.required_int("from_port", 0)
|
|
234
|
+
var to_node: int = reader.required_int("to_node", 0)
|
|
235
|
+
var to_port: int = reader.required_int("to_port", 0)
|
|
236
|
+
if params_invalid(reader):
|
|
237
|
+
return
|
|
238
|
+
if not shader.is_node_connection(VisualShader.TYPE_FRAGMENT, from_node, from_port, to_node, to_port):
|
|
239
|
+
respond({"error": "Visual shader connection does not exist"})
|
|
240
|
+
return
|
|
241
|
+
shader.disconnect_nodes(VisualShader.TYPE_FRAGMENT, from_node, from_port, to_node, to_port)
|
|
242
|
+
respond({"success": true, "shader_id": target_id})
|
|
243
|
+
"get_nodes":
|
|
244
|
+
var nodes: Array = []
|
|
245
|
+
for node_id in shader.get_node_list(VisualShader.TYPE_FRAGMENT):
|
|
246
|
+
var graph_node: VisualShaderNode = shader.get_node(VisualShader.TYPE_FRAGMENT, node_id)
|
|
247
|
+
var node_position: Vector2 = shader.get_node_position(VisualShader.TYPE_FRAGMENT, node_id)
|
|
248
|
+
nodes.append({"id": node_id, "class": graph_node.get_class(), "position": {"x": node_position.x, "y": node_position.y}})
|
|
249
|
+
respond({"success": true, "shader_id": target_id, "nodes": nodes})
|
|
250
|
+
"apply":
|
|
251
|
+
var node_path: String = reader.required_node_path("node_path")
|
|
252
|
+
if params_invalid(reader):
|
|
253
|
+
return
|
|
254
|
+
var node: Node = get_tree().root.get_node_or_null(node_path)
|
|
255
|
+
if node == null:
|
|
256
|
+
respond({"error": "Node not found: %s" % node_path})
|
|
257
|
+
return
|
|
258
|
+
var material: ShaderMaterial = ShaderMaterial.new()
|
|
259
|
+
material.shader = shader
|
|
260
|
+
if "material_override" in node:
|
|
261
|
+
node.set("material_override", material)
|
|
262
|
+
elif "material" in node:
|
|
263
|
+
node.set("material", material)
|
|
264
|
+
else:
|
|
265
|
+
respond({"error": "Node has no material property: %s" % node_path})
|
|
266
|
+
return
|
|
267
|
+
respond({"success": true, "shader_id": target_id, "node_path": node_path})
|
|
268
|
+
_:
|
|
269
|
+
respond({"error": "Unknown action: %s" % action})
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# --- Audio Play ---
|
|
273
|
+
func _cmd_environment(params: Dictionary) -> void:
|
|
274
|
+
var reader := CommandParams.new(params)
|
|
275
|
+
var action: String = reader.optional_enum("action", "set", ["get", "set"])
|
|
276
|
+
if params_invalid(reader):
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# Find existing WorldEnvironment or Camera3D environment
|
|
280
|
+
var env: Environment = null
|
|
281
|
+
var world_env: Node = null
|
|
282
|
+
|
|
283
|
+
# Search for WorldEnvironment node
|
|
284
|
+
var found: Array[Node] = get_tree().root.find_children("*", "WorldEnvironment", true, false)
|
|
285
|
+
if found.size() > 0:
|
|
286
|
+
world_env = found[0]
|
|
287
|
+
if world_env != null:
|
|
288
|
+
env = _as_environment(world_env.get("environment"))
|
|
289
|
+
|
|
290
|
+
# Fallback: check Camera3D
|
|
291
|
+
if env == null:
|
|
292
|
+
var cam3d: Camera3D = get_viewport().get_camera_3d()
|
|
293
|
+
if cam3d != null:
|
|
294
|
+
env = _as_environment(cam3d.get("environment"))
|
|
295
|
+
|
|
296
|
+
if action == "get":
|
|
297
|
+
if env == null:
|
|
298
|
+
respond({"error": "No Environment resource found"})
|
|
299
|
+
return
|
|
300
|
+
respond(_get_environment_state(env))
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
# action == "set": create if needed
|
|
304
|
+
if env == null:
|
|
305
|
+
env = Environment.new()
|
|
306
|
+
var we: WorldEnvironment = WorldEnvironment.new()
|
|
307
|
+
we.environment = env
|
|
308
|
+
get_tree().root.add_child(we)
|
|
309
|
+
world_env = we
|
|
310
|
+
|
|
311
|
+
# Apply settings
|
|
312
|
+
if params.has("background_mode"):
|
|
313
|
+
env.background_mode = CommandParams.to_int(params["background_mode"]) as Environment.BGMode
|
|
314
|
+
if params.has("background_color"):
|
|
315
|
+
var c: Dictionary = params["background_color"]
|
|
316
|
+
env.background_color = Color(CommandParams.json_float(c, "r", 0), CommandParams.json_float(c, "g", 0), CommandParams.json_float(c, "b", 0), CommandParams.json_float(c, "a", 1))
|
|
317
|
+
if params.has("ambient_light_color"):
|
|
318
|
+
var c: Dictionary = params["ambient_light_color"]
|
|
319
|
+
env.ambient_light_color = Color(CommandParams.json_float(c, "r", 0), CommandParams.json_float(c, "g", 0), CommandParams.json_float(c, "b", 0), CommandParams.json_float(c, "a", 1))
|
|
320
|
+
if params.has("ambient_light_energy"):
|
|
321
|
+
env.ambient_light_energy = CommandParams.to_float(params["ambient_light_energy"])
|
|
322
|
+
if params.has("fog_enabled"):
|
|
323
|
+
env.fog_enabled = CommandParams.to_bool(params["fog_enabled"])
|
|
324
|
+
if params.has("fog_density"):
|
|
325
|
+
env.fog_density = CommandParams.to_float(params["fog_density"])
|
|
326
|
+
if params.has("fog_light_color"):
|
|
327
|
+
var c: Dictionary = params["fog_light_color"]
|
|
328
|
+
env.fog_light_color = Color(CommandParams.json_float(c, "r", 0), CommandParams.json_float(c, "g", 0), CommandParams.json_float(c, "b", 0), CommandParams.json_float(c, "a", 1))
|
|
329
|
+
if params.has("glow_enabled"):
|
|
330
|
+
env.glow_enabled = CommandParams.to_bool(params["glow_enabled"])
|
|
331
|
+
if params.has("glow_intensity"):
|
|
332
|
+
env.glow_intensity = CommandParams.to_float(params["glow_intensity"])
|
|
333
|
+
if params.has("glow_bloom"):
|
|
334
|
+
env.glow_bloom = CommandParams.to_float(params["glow_bloom"])
|
|
335
|
+
if params.has("tonemap_mode"):
|
|
336
|
+
env.tonemap_mode = CommandParams.to_int(params["tonemap_mode"]) as Environment.ToneMapper
|
|
337
|
+
if params.has("ssao_enabled"):
|
|
338
|
+
env.ssao_enabled = CommandParams.to_bool(params["ssao_enabled"])
|
|
339
|
+
if params.has("ssao_radius"):
|
|
340
|
+
env.ssao_radius = CommandParams.to_float(params["ssao_radius"])
|
|
341
|
+
if params.has("ssao_intensity"):
|
|
342
|
+
env.ssao_intensity = CommandParams.to_float(params["ssao_intensity"])
|
|
343
|
+
if params.has("ssr_enabled"):
|
|
344
|
+
env.ssr_enabled = CommandParams.to_bool(params["ssr_enabled"])
|
|
345
|
+
if params.has("brightness"):
|
|
346
|
+
env.adjustment_enabled = true
|
|
347
|
+
env.adjustment_brightness = CommandParams.to_float(params["brightness"])
|
|
348
|
+
if params.has("contrast"):
|
|
349
|
+
env.adjustment_enabled = true
|
|
350
|
+
env.adjustment_contrast = CommandParams.to_float(params["contrast"])
|
|
351
|
+
if params.has("saturation"):
|
|
352
|
+
env.adjustment_enabled = true
|
|
353
|
+
env.adjustment_saturation = CommandParams.to_float(params["saturation"])
|
|
354
|
+
|
|
355
|
+
respond(_get_environment_state(env))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
func _get_environment_state(env: Environment) -> Dictionary:
|
|
359
|
+
return {
|
|
360
|
+
"success": true,
|
|
361
|
+
"background_mode": env.background_mode,
|
|
362
|
+
"background_color": variant_to_json(env.background_color),
|
|
363
|
+
"ambient_light_color": variant_to_json(env.ambient_light_color),
|
|
364
|
+
"ambient_light_energy": env.ambient_light_energy,
|
|
365
|
+
"fog_enabled": env.fog_enabled,
|
|
366
|
+
"fog_density": env.fog_density,
|
|
367
|
+
"fog_light_color": variant_to_json(env.fog_light_color),
|
|
368
|
+
"glow_enabled": env.glow_enabled,
|
|
369
|
+
"glow_intensity": env.glow_intensity,
|
|
370
|
+
"glow_bloom": env.glow_bloom,
|
|
371
|
+
"tonemap_mode": env.tonemap_mode,
|
|
372
|
+
"ssao_enabled": env.ssao_enabled,
|
|
373
|
+
"ssao_radius": env.ssao_radius,
|
|
374
|
+
"ssao_intensity": env.ssao_intensity,
|
|
375
|
+
"ssr_enabled": env.ssr_enabled,
|
|
376
|
+
"brightness": env.adjustment_brightness,
|
|
377
|
+
"contrast": env.adjustment_contrast,
|
|
378
|
+
"saturation": env.adjustment_saturation
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# --- Manage Group ---
|
|
383
|
+
func _cmd_set_particles(params: Dictionary) -> void:
|
|
384
|
+
var node_path: String = params.get("node_path", "")
|
|
385
|
+
if node_path.is_empty():
|
|
386
|
+
respond({"error": "node_path is required"})
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
var node: Node = get_tree().root.get_node_or_null(node_path)
|
|
390
|
+
if node == null:
|
|
391
|
+
respond({"error": "Node not found: %s" % node_path})
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
if not (node is GPUParticles2D or node is GPUParticles3D):
|
|
395
|
+
respond({"error": "Node is not a GPUParticles node: %s (is %s)" % [node_path, node.get_class()]})
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
# Set direct particle properties
|
|
399
|
+
if params.has("emitting"):
|
|
400
|
+
node.set("emitting", CommandParams.to_bool(params["emitting"]))
|
|
401
|
+
if params.has("amount"):
|
|
402
|
+
node.set("amount", CommandParams.to_int(params["amount"]))
|
|
403
|
+
if params.has("lifetime"):
|
|
404
|
+
node.set("lifetime", CommandParams.to_float(params["lifetime"]))
|
|
405
|
+
if params.has("one_shot"):
|
|
406
|
+
node.set("one_shot", CommandParams.to_bool(params["one_shot"]))
|
|
407
|
+
if params.has("speed_scale"):
|
|
408
|
+
node.set("speed_scale", CommandParams.to_float(params["speed_scale"]))
|
|
409
|
+
if params.has("explosiveness"):
|
|
410
|
+
node.set("explosiveness", CommandParams.to_float(params["explosiveness"]))
|
|
411
|
+
if params.has("randomness"):
|
|
412
|
+
node.set("randomness", CommandParams.to_float(params["randomness"]))
|
|
413
|
+
|
|
414
|
+
# Configure process material
|
|
415
|
+
if params.has("process_material"):
|
|
416
|
+
var mat_params: Dictionary = params["process_material"]
|
|
417
|
+
var process_material: Variant = node.get("process_material")
|
|
418
|
+
var mat: ParticleProcessMaterial = null
|
|
419
|
+
if process_material is ParticleProcessMaterial:
|
|
420
|
+
mat = process_material
|
|
421
|
+
if mat == null:
|
|
422
|
+
mat = ParticleProcessMaterial.new()
|
|
423
|
+
node.set("process_material", mat)
|
|
424
|
+
if mat_params.has("direction"):
|
|
425
|
+
var d: Dictionary = mat_params["direction"]
|
|
426
|
+
mat.direction = Vector3(CommandParams.json_float(d, "x", 0), CommandParams.json_float(d, "y", -1), CommandParams.json_float(d, "z", 0))
|
|
427
|
+
if mat_params.has("spread"):
|
|
428
|
+
mat.spread = CommandParams.to_float(mat_params["spread"])
|
|
429
|
+
if mat_params.has("gravity"):
|
|
430
|
+
var g: Dictionary = mat_params["gravity"]
|
|
431
|
+
mat.gravity = Vector3(CommandParams.json_float(g, "x", 0), CommandParams.json_float(g, "y", -9.8), CommandParams.json_float(g, "z", 0))
|
|
432
|
+
if mat_params.has("initial_velocity_min"):
|
|
433
|
+
mat.initial_velocity_min = CommandParams.to_float(mat_params["initial_velocity_min"])
|
|
434
|
+
if mat_params.has("initial_velocity_max"):
|
|
435
|
+
mat.initial_velocity_max = CommandParams.to_float(mat_params["initial_velocity_max"])
|
|
436
|
+
if mat_params.has("color"):
|
|
437
|
+
var c: Dictionary = mat_params["color"]
|
|
438
|
+
mat.color = Color(CommandParams.json_float(c, "r", 1), CommandParams.json_float(c, "g", 1), CommandParams.json_float(c, "b", 1), CommandParams.json_float(c, "a", 1))
|
|
439
|
+
if mat_params.has("scale_min"):
|
|
440
|
+
mat.scale_min = CommandParams.to_float(mat_params["scale_min"])
|
|
441
|
+
if mat_params.has("scale_max"):
|
|
442
|
+
mat.scale_max = CommandParams.to_float(mat_params["scale_max"])
|
|
443
|
+
|
|
444
|
+
respond({
|
|
445
|
+
"success": true, "node_path": node_path,
|
|
446
|
+
"emitting": node.get("emitting"), "amount": node.get("amount"),
|
|
447
|
+
"lifetime": node.get("lifetime"), "one_shot": node.get("one_shot"),
|
|
448
|
+
"speed_scale": node.get("speed_scale")
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
# --- Create Animation ---
|
|
453
|
+
func _cmd_viewport(params: Dictionary) -> void:
|
|
454
|
+
var reader := CommandParams.new(params)
|
|
455
|
+
var action: String = reader.optional_enum("action", "create", ["create", "configure", "get"])
|
|
456
|
+
if params_invalid(reader):
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
match action:
|
|
460
|
+
"create":
|
|
461
|
+
var parent_path: String = params.get("parent_path", "/root")
|
|
462
|
+
var parent: Node = get_tree().root.get_node_or_null(parent_path)
|
|
463
|
+
if parent == null:
|
|
464
|
+
respond({"error": "Parent node not found: %s" % parent_path})
|
|
465
|
+
return
|
|
466
|
+
var viewport: SubViewport = SubViewport.new()
|
|
467
|
+
if params.has("width") and params.has("height"):
|
|
468
|
+
viewport.size = Vector2i(CommandParams.to_int(params["width"]), CommandParams.to_int(params["height"]))
|
|
469
|
+
if params.has("transparent_bg"):
|
|
470
|
+
viewport.transparent_bg = CommandParams.to_bool(params["transparent_bg"])
|
|
471
|
+
if params.has("msaa"):
|
|
472
|
+
viewport.msaa_2d = CommandParams.to_int(params["msaa"]) as Viewport.MSAA
|
|
473
|
+
viewport.msaa_3d = CommandParams.to_int(params["msaa"]) as Viewport.MSAA
|
|
474
|
+
var custom_name: String = CommandParams.json_string(params, "name")
|
|
475
|
+
if not custom_name.is_empty():
|
|
476
|
+
viewport.name = custom_name
|
|
477
|
+
var container: SubViewportContainer = SubViewportContainer.new()
|
|
478
|
+
container.add_child(viewport)
|
|
479
|
+
parent.add_child(container)
|
|
480
|
+
respond({"success": true, "action": "create", "viewport_path": str(viewport.get_path()), "container_path": str(container.get_path()), "size": variant_to_json(viewport.size)})
|
|
481
|
+
"configure":
|
|
482
|
+
var node_path: String = params.get("node_path", "")
|
|
483
|
+
if node_path.is_empty():
|
|
484
|
+
respond({"error": "node_path is required for configure"})
|
|
485
|
+
return
|
|
486
|
+
var vp: Node = get_tree().root.get_node_or_null(node_path)
|
|
487
|
+
if vp == null or not vp is SubViewport:
|
|
488
|
+
respond({"error": "SubViewport not found: %s" % node_path})
|
|
489
|
+
return
|
|
490
|
+
var sv: SubViewport = vp as SubViewport
|
|
491
|
+
if params.has("width") and params.has("height"):
|
|
492
|
+
sv.size = Vector2i(CommandParams.to_int(params["width"]), CommandParams.to_int(params["height"]))
|
|
493
|
+
if params.has("transparent_bg"):
|
|
494
|
+
sv.transparent_bg = CommandParams.to_bool(params["transparent_bg"])
|
|
495
|
+
if params.has("msaa"):
|
|
496
|
+
sv.msaa_2d = CommandParams.to_int(params["msaa"]) as Viewport.MSAA
|
|
497
|
+
sv.msaa_3d = CommandParams.to_int(params["msaa"]) as Viewport.MSAA
|
|
498
|
+
respond({"success": true, "action": "configure", "size": variant_to_json(sv.size), "transparent_bg": sv.transparent_bg})
|
|
499
|
+
"get":
|
|
500
|
+
var node_path: String = params.get("node_path", "")
|
|
501
|
+
if node_path.is_empty():
|
|
502
|
+
respond({"error": "node_path is required for get"})
|
|
503
|
+
return
|
|
504
|
+
var vp: Node = get_tree().root.get_node_or_null(node_path)
|
|
505
|
+
if vp == null or not vp is SubViewport:
|
|
506
|
+
respond({"error": "SubViewport not found: %s" % node_path})
|
|
507
|
+
return
|
|
508
|
+
var sv: SubViewport = vp as SubViewport
|
|
509
|
+
respond({"success": true, "action": "get", "size": variant_to_json(sv.size), "transparent_bg": sv.transparent_bg, "msaa_2d": sv.msaa_2d, "msaa_3d": sv.msaa_3d})
|
|
510
|
+
_:
|
|
511
|
+
respond({"error": "Unknown viewport action: %s. Use create, configure, or get" % action})
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# --- Debug Draw ---
|
|
515
|
+
var _debug_draw_node: Node = null
|
|
516
|
+
var _debug_meshes: Array = []
|
|
517
|
+
|
|
518
|
+
func _cmd_debug_draw(params: Dictionary) -> void:
|
|
519
|
+
var reader := CommandParams.new(params)
|
|
520
|
+
var action: String = reader.optional_enum("action", "line", ["line", "sphere", "box", "clear"])
|
|
521
|
+
var duration: int = reader.optional_int("duration", 0, 0)
|
|
522
|
+
if params_invalid(reader):
|
|
523
|
+
return
|
|
524
|
+
var color_dict: Dictionary = params.get("color", {"r": 1.0, "g": 0.0, "b": 0.0})
|
|
525
|
+
var color: Color = Color(CommandParams.json_float(color_dict, "r", 1), CommandParams.json_float(color_dict, "g", 0), CommandParams.json_float(color_dict, "b", 0), CommandParams.json_float(color_dict, "a", 1))
|
|
526
|
+
if action == "clear":
|
|
527
|
+
_clear_debug_draw()
|
|
528
|
+
respond({"success": true, "action": "clear"})
|
|
529
|
+
return
|
|
530
|
+
|
|
531
|
+
# Ensure we have a debug draw parent
|
|
532
|
+
if _debug_draw_node == null or not is_instance_valid(_debug_draw_node):
|
|
533
|
+
_debug_draw_node = Node3D.new()
|
|
534
|
+
_debug_draw_node.name = "_McpDebugDraw"
|
|
535
|
+
get_tree().root.add_child(_debug_draw_node)
|
|
536
|
+
|
|
537
|
+
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
|
538
|
+
mat.albedo_color = color
|
|
539
|
+
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
540
|
+
mat.no_depth_test = true
|
|
541
|
+
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA if color.a < 1.0 else BaseMaterial3D.TRANSPARENCY_DISABLED
|
|
542
|
+
|
|
543
|
+
match action:
|
|
544
|
+
"line":
|
|
545
|
+
var from_dict: Dictionary = params.get("from", {})
|
|
546
|
+
var to_dict: Dictionary = params.get("to", {})
|
|
547
|
+
var from_pos: Vector3 = Vector3(CommandParams.json_float(from_dict, "x", 0), CommandParams.json_float(from_dict, "y", 0), CommandParams.json_float(from_dict, "z", 0))
|
|
548
|
+
var to_pos: Vector3 = Vector3(CommandParams.json_float(to_dict, "x", 0), CommandParams.json_float(to_dict, "y", 0), CommandParams.json_float(to_dict, "z", 0))
|
|
549
|
+
var im: ImmediateMesh = ImmediateMesh.new()
|
|
550
|
+
im.surface_begin(Mesh.PRIMITIVE_LINES, mat)
|
|
551
|
+
im.surface_add_vertex(from_pos)
|
|
552
|
+
im.surface_add_vertex(to_pos)
|
|
553
|
+
im.surface_end()
|
|
554
|
+
var mi: MeshInstance3D = MeshInstance3D.new()
|
|
555
|
+
mi.mesh = im
|
|
556
|
+
_debug_draw_node.add_child(mi)
|
|
557
|
+
_debug_meshes.append({"node": mi, "frames_left": duration})
|
|
558
|
+
respond({"success": true, "action": "line"})
|
|
559
|
+
"sphere":
|
|
560
|
+
var center_dict: Dictionary = params.get("center", {})
|
|
561
|
+
var center: Vector3 = Vector3(CommandParams.json_float(center_dict, "x", 0), CommandParams.json_float(center_dict, "y", 0), CommandParams.json_float(center_dict, "z", 0))
|
|
562
|
+
var radius: float = reader.optional_number("radius", 0.5, 0.0)
|
|
563
|
+
if params_invalid(reader):
|
|
564
|
+
return
|
|
565
|
+
if radius <= 0.0:
|
|
566
|
+
reader.fail("radius must be greater than zero", {"param": "radius", "reason": "out_of_range", "min_exclusive": 0})
|
|
567
|
+
send_params_error(reader)
|
|
568
|
+
return
|
|
569
|
+
var sphere_mesh: SphereMesh = SphereMesh.new()
|
|
570
|
+
sphere_mesh.radius = radius
|
|
571
|
+
sphere_mesh.height = radius * 2.0
|
|
572
|
+
sphere_mesh.material = mat
|
|
573
|
+
var mi: MeshInstance3D = MeshInstance3D.new()
|
|
574
|
+
mi.mesh = sphere_mesh
|
|
575
|
+
mi.global_position = center
|
|
576
|
+
_debug_draw_node.add_child(mi)
|
|
577
|
+
_debug_meshes.append({"node": mi, "frames_left": duration})
|
|
578
|
+
respond({"success": true, "action": "sphere"})
|
|
579
|
+
"box":
|
|
580
|
+
var center_dict: Dictionary = params.get("center", {})
|
|
581
|
+
var center: Vector3 = Vector3(CommandParams.json_float(center_dict, "x", 0), CommandParams.json_float(center_dict, "y", 0), CommandParams.json_float(center_dict, "z", 0))
|
|
582
|
+
var size_dict: Dictionary = params.get("size", {"x": 1, "y": 1, "z": 1})
|
|
583
|
+
var box_size: Vector3 = Vector3(CommandParams.json_float(size_dict, "x", 1), CommandParams.json_float(size_dict, "y", 1), CommandParams.json_float(size_dict, "z", 1))
|
|
584
|
+
var box_mesh: BoxMesh = BoxMesh.new()
|
|
585
|
+
box_mesh.size = box_size
|
|
586
|
+
box_mesh.material = mat
|
|
587
|
+
var mi: MeshInstance3D = MeshInstance3D.new()
|
|
588
|
+
mi.mesh = box_mesh
|
|
589
|
+
mi.global_position = center
|
|
590
|
+
_debug_draw_node.add_child(mi)
|
|
591
|
+
_debug_meshes.append({"node": mi, "frames_left": duration})
|
|
592
|
+
respond({"success": true, "action": "box"})
|
|
593
|
+
_:
|
|
594
|
+
respond({"error": "Unknown debug draw action: %s. Use line, sphere, box, or clear" % action})
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
func _cmd_gi(params: Dictionary) -> void:
|
|
598
|
+
var reader := CommandParams.new(params)
|
|
599
|
+
var parent: Node = require_node(reader, "parent_path", "/root")
|
|
600
|
+
var gi_type: String = reader.optional_enum("gi_type", "voxel_gi", ["voxel_gi", "lightmap_gi", "reflection_probe"])
|
|
601
|
+
if params_invalid(reader):
|
|
602
|
+
return
|
|
603
|
+
var node: VisualInstance3D
|
|
604
|
+
match gi_type:
|
|
605
|
+
"voxel_gi": node = VoxelGI.new()
|
|
606
|
+
"lightmap_gi": node = LightmapGI.new()
|
|
607
|
+
"reflection_probe": node = ReflectionProbe.new()
|
|
608
|
+
_:
|
|
609
|
+
respond({"error": "Unknown GI type: %s" % gi_type})
|
|
610
|
+
return
|
|
611
|
+
if params.has("size"):
|
|
612
|
+
var s: Dictionary = params["size"]
|
|
613
|
+
var extents := Vector3(CommandParams.json_float(s, "x", 10), CommandParams.json_float(s, "y", 10), CommandParams.json_float(s, "z", 10))
|
|
614
|
+
if node is VoxelGI:
|
|
615
|
+
(node as VoxelGI).size = extents
|
|
616
|
+
elif node is ReflectionProbe:
|
|
617
|
+
(node as ReflectionProbe).size = extents
|
|
618
|
+
var custom_name: String = CommandParams.json_string(params, "name")
|
|
619
|
+
if not custom_name.is_empty():
|
|
620
|
+
node.name = custom_name
|
|
621
|
+
parent.add_child(node)
|
|
622
|
+
respond({"success": true, "path": str(node.get_path()), "gi_type": gi_type})
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
func _cmd_sky(params: Dictionary) -> void:
|
|
626
|
+
var action: String = params.get("action", "create")
|
|
627
|
+
var env: Environment = _get_or_create_environment()
|
|
628
|
+
if env == null:
|
|
629
|
+
respond({"error": "Could not get or create environment"})
|
|
630
|
+
return
|
|
631
|
+
var sky_type: String = params.get("sky_type", "procedural")
|
|
632
|
+
if action == "create" or env.sky == null:
|
|
633
|
+
env.sky = Sky.new()
|
|
634
|
+
env.background_mode = Environment.BG_SKY
|
|
635
|
+
var sky_mat: ProceduralSkyMaterial = env.sky.sky_material as ProceduralSkyMaterial
|
|
636
|
+
if sky_mat == null:
|
|
637
|
+
sky_mat = ProceduralSkyMaterial.new()
|
|
638
|
+
if params.has("top_color"):
|
|
639
|
+
var c: Dictionary = params["top_color"]
|
|
640
|
+
sky_mat.sky_top_color = Color(CommandParams.json_float(c, "r", 0.4), CommandParams.json_float(c, "g", 0.6), CommandParams.json_float(c, "b", 1.0))
|
|
641
|
+
if params.has("bottom_color"):
|
|
642
|
+
var c: Dictionary = params["bottom_color"]
|
|
643
|
+
sky_mat.sky_horizon_color = Color(CommandParams.json_float(c, "r", 0.7), CommandParams.json_float(c, "g", 0.8), CommandParams.json_float(c, "b", 0.9))
|
|
644
|
+
if params.has("ground_color"):
|
|
645
|
+
var c: Dictionary = params["ground_color"]
|
|
646
|
+
sky_mat.ground_bottom_color = Color(CommandParams.json_float(c, "r", 0.1), CommandParams.json_float(c, "g", 0.1), CommandParams.json_float(c, "b", 0.1))
|
|
647
|
+
if params.has("sun_energy"):
|
|
648
|
+
sky_mat.sun_curve = CommandParams.to_float(params["sun_energy"])
|
|
649
|
+
env.sky.sky_material = sky_mat
|
|
650
|
+
respond({"success": true, "action": action, "sky_type": sky_type})
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
func _get_or_create_environment() -> Environment:
|
|
654
|
+
var cam: Camera3D = get_viewport().get_camera_3d()
|
|
655
|
+
if cam != null and cam.get_environment() != null:
|
|
656
|
+
return cam.get_environment()
|
|
657
|
+
var we: WorldEnvironment = null
|
|
658
|
+
for child in get_tree().root.get_children():
|
|
659
|
+
if child is WorldEnvironment:
|
|
660
|
+
we = child as WorldEnvironment
|
|
661
|
+
break
|
|
662
|
+
if we != null and we.environment != null:
|
|
663
|
+
return we.environment
|
|
664
|
+
# Create one
|
|
665
|
+
we = WorldEnvironment.new()
|
|
666
|
+
we.environment = Environment.new()
|
|
667
|
+
get_tree().root.add_child(we)
|
|
668
|
+
return we.environment
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
func _cmd_camera_attributes(params: Dictionary) -> void:
|
|
672
|
+
var reader := CommandParams.new(params)
|
|
673
|
+
var action: String = reader.optional_enum("action", "get", ["get", "set"])
|
|
674
|
+
if params_invalid(reader):
|
|
675
|
+
return
|
|
676
|
+
var cam: Camera3D = get_viewport().get_camera_3d()
|
|
677
|
+
if cam == null:
|
|
678
|
+
respond({"error": "No Camera3D found in viewport"})
|
|
679
|
+
return
|
|
680
|
+
if action == "get":
|
|
681
|
+
var current: CameraAttributesPractical = cam.attributes as CameraAttributesPractical
|
|
682
|
+
var info: Dictionary = {"success": true, "action": "get", "has_attributes": cam.attributes != null}
|
|
683
|
+
# Report the values, not just their presence: without them a client cannot
|
|
684
|
+
# confirm that a preceding `set` actually landed.
|
|
685
|
+
if current != null:
|
|
686
|
+
info["dof_blur_far"] = current.dof_blur_far_distance
|
|
687
|
+
info["dof_blur_near"] = current.dof_blur_near_distance
|
|
688
|
+
info["dof_blur_amount"] = current.dof_blur_amount
|
|
689
|
+
info["exposure_multiplier"] = current.exposure_multiplier
|
|
690
|
+
info["auto_exposure"] = current.auto_exposure_enabled
|
|
691
|
+
info["auto_exposure_scale"] = current.auto_exposure_scale
|
|
692
|
+
respond(info)
|
|
693
|
+
return
|
|
694
|
+
# set
|
|
695
|
+
if cam.attributes == null:
|
|
696
|
+
cam.attributes = CameraAttributesPractical.new()
|
|
697
|
+
var attr: CameraAttributesPractical = cam.attributes as CameraAttributesPractical
|
|
698
|
+
if attr == null:
|
|
699
|
+
respond({"error": "Camera attributes is not CameraAttributesPractical"})
|
|
700
|
+
return
|
|
701
|
+
if params.has("dof_blur_far"):
|
|
702
|
+
attr.dof_blur_far_enabled = true
|
|
703
|
+
attr.dof_blur_far_distance = CommandParams.to_float(params["dof_blur_far"])
|
|
704
|
+
if params.has("dof_blur_near"):
|
|
705
|
+
attr.dof_blur_near_enabled = true
|
|
706
|
+
attr.dof_blur_near_distance = CommandParams.to_float(params["dof_blur_near"])
|
|
707
|
+
if params.has("dof_blur_amount"):
|
|
708
|
+
attr.dof_blur_amount = CommandParams.to_float(params["dof_blur_amount"])
|
|
709
|
+
if params.has("exposure_multiplier"):
|
|
710
|
+
attr.exposure_multiplier = CommandParams.to_float(params["exposure_multiplier"])
|
|
711
|
+
if params.has("auto_exposure"):
|
|
712
|
+
attr.auto_exposure_enabled = CommandParams.to_bool(params["auto_exposure"])
|
|
713
|
+
if params.has("auto_exposure_scale"):
|
|
714
|
+
attr.auto_exposure_scale = CommandParams.to_float(params["auto_exposure_scale"])
|
|
715
|
+
respond({"success": true, "action": "set"})
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
# ==========================================================================
|
|
719
|
+
# Batch 3: Animation Advanced + Audio Effects
|
|
720
|
+
# ==========================================================================
|
|
721
|
+
|
|
722
|
+
func _cmd_render_settings(params: Dictionary) -> void:
|
|
723
|
+
var vp: Viewport = get_viewport()
|
|
724
|
+
var reader := CommandParams.new(params)
|
|
725
|
+
var action: String = reader.optional_enum("action", "get", ["get", "set"])
|
|
726
|
+
if params_invalid(reader):
|
|
727
|
+
return
|
|
728
|
+
if action == "get":
|
|
729
|
+
respond({"success": true, "msaa_2d": vp.msaa_2d, "msaa_3d": vp.msaa_3d, "screen_space_aa": vp.screen_space_aa, "use_taa": vp.use_taa, "scaling_3d_mode": vp.scaling_3d_mode, "scaling_3d_scale": vp.scaling_3d_scale})
|
|
730
|
+
return
|
|
731
|
+
if params.has("msaa_2d"):
|
|
732
|
+
vp.msaa_2d = CommandParams.to_int(params["msaa_2d"]) as Viewport.MSAA
|
|
733
|
+
if params.has("msaa_3d"):
|
|
734
|
+
vp.msaa_3d = CommandParams.to_int(params["msaa_3d"]) as Viewport.MSAA
|
|
735
|
+
if params.has("fxaa"):
|
|
736
|
+
vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA if CommandParams.to_bool(params["fxaa"]) else Viewport.SCREEN_SPACE_AA_DISABLED
|
|
737
|
+
if params.has("taa"):
|
|
738
|
+
vp.use_taa = CommandParams.to_bool(params["taa"])
|
|
739
|
+
if params.has("scaling_mode"):
|
|
740
|
+
vp.scaling_3d_mode = CommandParams.to_int(params["scaling_mode"]) as Viewport.Scaling3DMode
|
|
741
|
+
if params.has("scaling_scale"):
|
|
742
|
+
vp.scaling_3d_scale = CommandParams.to_float(params["scaling_scale"])
|
|
743
|
+
respond({"success": true, "action": "set"})
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
func _cmd_video(params: Dictionary) -> void:
|
|
747
|
+
var reader := CommandParams.new(params)
|
|
748
|
+
var action: String = reader.optional_enum("action", "play", ["create", "play", "pause", "resume", "stop", "seek", "get_status"])
|
|
749
|
+
if params_invalid(reader):
|
|
750
|
+
return
|
|
751
|
+
if action == "create":
|
|
752
|
+
var parent_path: String = params.get("parent_path", "/root")
|
|
753
|
+
var parent: Node = get_tree().root.get_node_or_null(parent_path)
|
|
754
|
+
if parent == null:
|
|
755
|
+
respond({"error": "Parent not found: %s" % parent_path})
|
|
756
|
+
return
|
|
757
|
+
var vp: VideoStreamPlayer = VideoStreamPlayer.new()
|
|
758
|
+
var video_path: String = params.get("video_path", "")
|
|
759
|
+
if not video_path.is_empty():
|
|
760
|
+
if not ResourceLoader.exists(video_path):
|
|
761
|
+
respond({"error": "Video resource not found: %s" % video_path})
|
|
762
|
+
return
|
|
763
|
+
var stream: Resource = ResourceLoader.load(video_path)
|
|
764
|
+
if not stream is VideoStream:
|
|
765
|
+
respond({"error": "Resource is not a VideoStream: %s" % video_path})
|
|
766
|
+
return
|
|
767
|
+
vp.stream = stream
|
|
768
|
+
if params.has("volume"):
|
|
769
|
+
vp.volume = CommandParams.to_float(params["volume"])
|
|
770
|
+
if params.has("autoplay"):
|
|
771
|
+
vp.autoplay = CommandParams.to_bool(params["autoplay"])
|
|
772
|
+
if params.has("loop") and "loop" in vp:
|
|
773
|
+
vp.set("loop", CommandParams.to_bool(params["loop"]))
|
|
774
|
+
var custom_name: String = CommandParams.json_string(params, "name")
|
|
775
|
+
if not custom_name.is_empty():
|
|
776
|
+
vp.name = custom_name
|
|
777
|
+
parent.add_child(vp)
|
|
778
|
+
if vp.autoplay:
|
|
779
|
+
vp.play()
|
|
780
|
+
respond({"success": true, "action": "create", "path": str(vp.get_path())})
|
|
781
|
+
return
|
|
782
|
+
var node_path: String = params.get("node_path", "")
|
|
783
|
+
var node: Node = get_tree().root.get_node_or_null(node_path)
|
|
784
|
+
if node == null or not node is VideoStreamPlayer:
|
|
785
|
+
respond({"error": "VideoStreamPlayer not found: %s" % node_path})
|
|
786
|
+
return
|
|
787
|
+
var player: VideoStreamPlayer = node as VideoStreamPlayer
|
|
788
|
+
match action:
|
|
789
|
+
"play":
|
|
790
|
+
player.play()
|
|
791
|
+
respond({"success": true, "action": "play"})
|
|
792
|
+
"pause":
|
|
793
|
+
player.paused = true
|
|
794
|
+
respond({"success": true, "action": "pause"})
|
|
795
|
+
"resume":
|
|
796
|
+
player.paused = false
|
|
797
|
+
respond({"success": true, "action": "resume"})
|
|
798
|
+
"stop":
|
|
799
|
+
player.stop()
|
|
800
|
+
respond({"success": true, "action": "stop"})
|
|
801
|
+
"seek":
|
|
802
|
+
player.stream_position = CommandParams.json_float(params, "position", 0.0)
|
|
803
|
+
respond({"success": true, "action": "seek", "position": player.stream_position})
|
|
804
|
+
"get_status":
|
|
805
|
+
respond({"success": true, "action": "get_status", "is_playing": player.is_playing(), "paused": player.paused, "position": player.stream_position, "length": player.get_stream_length()})
|
|
806
|
+
_:
|
|
807
|
+
respond({"error": "Unknown video action: %s" % action})
|