@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.
Files changed (70) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +925 -0
  3. package/addons/godot_agent_loop/LICENSE +23 -0
  4. package/addons/godot_agent_loop/README.md +31 -0
  5. package/addons/godot_agent_loop/plugin.cfg +8 -0
  6. package/addons/godot_agent_loop/plugin.gd +417 -0
  7. package/agent-plugin/.claude-plugin/plugin.json +19 -0
  8. package/agent-plugin/.codex-plugin/plugin.json +37 -0
  9. package/agent-plugin/.mcp.json +14 -0
  10. package/agent-plugin/adapter-manifest.json +45 -0
  11. package/agent-plugin/pi/extension.ts +136 -0
  12. package/agent-plugin/skills/build-godot-game/SKILL.md +41 -0
  13. package/agent-plugin/skills/debug-godot-game/SKILL.md +37 -0
  14. package/agent-plugin/skills/ship-godot-game/SKILL.md +46 -0
  15. package/agent-plugin/skills/ship-godot-game/agents/openai.yaml +4 -0
  16. package/agent-plugin/skills/verify-godot-change/SKILL.md +35 -0
  17. package/build/authoring-session-manager.js +237 -0
  18. package/build/domain-tool-registries.js +207 -0
  19. package/build/editor-bridge-protocol.js +1 -0
  20. package/build/editor-connection.js +112 -0
  21. package/build/editor-mutation-guard.js +30 -0
  22. package/build/editor-plugin-installer.js +220 -0
  23. package/build/engine-api/extension_api-4.7.stable.official.5b4e0cb0f.json +356830 -0
  24. package/build/game-command-service.js +49 -0
  25. package/build/game-connection.js +337 -0
  26. package/build/godot-executable.js +156 -0
  27. package/build/godot-process-manager.js +112 -0
  28. package/build/godot-subprocess.js +23 -0
  29. package/build/headless-operation-runner.js +68 -0
  30. package/build/headless-operation-service.js +65 -0
  31. package/build/index.js +478 -0
  32. package/build/interaction-server-installer.js +234 -0
  33. package/build/opencode-setup.js +199 -0
  34. package/build/project-support.js +219 -0
  35. package/build/runtime-protocol.js +202 -0
  36. package/build/scripts/godot_operations.gd +1534 -0
  37. package/build/scripts/mcp_editor_plugin.gd +417 -0
  38. package/build/scripts/mcp_interaction_server.gd +1255 -0
  39. package/build/scripts/mcp_runtime/audio_animation_domain.gd +568 -0
  40. package/build/scripts/mcp_runtime/command_params.gd +339 -0
  41. package/build/scripts/mcp_runtime/core_domain.gd +591 -0
  42. package/build/scripts/mcp_runtime/input_domain.gd +695 -0
  43. package/build/scripts/mcp_runtime/networking_domain.gd +356 -0
  44. package/build/scripts/mcp_runtime/physics_domain.gd +653 -0
  45. package/build/scripts/mcp_runtime/privileged_command_policy.gd +81 -0
  46. package/build/scripts/mcp_runtime/rendering_domain.gd +807 -0
  47. package/build/scripts/mcp_runtime/runtime_domain.gd +108 -0
  48. package/build/scripts/mcp_runtime/scene_2d_domain.gd +527 -0
  49. package/build/scripts/mcp_runtime/scene_3d_domain.gd +573 -0
  50. package/build/scripts/mcp_runtime/system_domain.gd +277 -0
  51. package/build/scripts/mcp_runtime/ui_domain.gd +619 -0
  52. package/build/scripts/mcp_runtime/variant_codec.gd +335 -0
  53. package/build/scripts/validate_script.gd +28 -0
  54. package/build/server-instructions.js +11 -0
  55. package/build/session-timing.js +20 -0
  56. package/build/tool-argument-validation.js +120 -0
  57. package/build/tool-definitions.js +2727 -0
  58. package/build/tool-handlers/game-tool-handlers.js +1345 -0
  59. package/build/tool-handlers/lifecycle-tool-handlers.js +346 -0
  60. package/build/tool-handlers/project-handler-services.js +1458 -0
  61. package/build/tool-handlers/project-tool-handlers.js +1506 -0
  62. package/build/tool-handlers/visual-regression-service.js +139 -0
  63. package/build/tool-manifest.js +1193 -0
  64. package/build/tool-mutation-policy.js +122 -0
  65. package/build/tool-registry.js +34 -0
  66. package/build/tool-surface.js +70 -0
  67. package/build/utils.js +273 -0
  68. package/package.json +110 -0
  69. package/product.json +52 -0
  70. package/scripts/prepare-package.js +42 -0
@@ -0,0 +1,695 @@
1
+ extends "res://mcp_runtime/runtime_domain.gd"
2
+
3
+ # Input domain: synthesized mouse, keyboard, gamepad, and touch input, plus the
4
+ # input-map and mouse-state commands.
5
+ #
6
+ # This domain owns the two pieces of input state that used to sit beside
7
+ # unrelated handlers on the server: the key-name map and the set of keys and
8
+ # actions currently held down by key_hold.
9
+
10
+ var _key_map: Dictionary = {}
11
+ var _held_keys: Dictionary[String, int] = {}
12
+ var _initial_mouse_mode: Input.MouseMode
13
+ var _mouse_mode_owned: bool = false
14
+ var _held_gamepad_buttons: Dictionary = {}
15
+ var _held_gamepad_axes: Dictionary = {}
16
+ var _active_touches: Dictionary = {}
17
+
18
+
19
+ func _init() -> void:
20
+ _init_key_map()
21
+ _initial_mouse_mode = Input.mouse_mode
22
+
23
+
24
+ # key_hold deliberately leaves a key or action pressed after responding, so the
25
+ # domain must undo that when it goes away: Input state is global and outlives
26
+ # the server, and a stuck key would keep driving the game.
27
+ func _exit_tree() -> void:
28
+ _release_all_held()
29
+ _release_all_gamepad_inputs()
30
+ _release_all_touches()
31
+ if _mouse_mode_owned:
32
+ Input.mouse_mode = _initial_mouse_mode
33
+
34
+
35
+ func _release_all_held() -> void:
36
+ for entry: String in _held_keys:
37
+ if entry.begins_with("action:"):
38
+ Input.action_release(entry.substr(7))
39
+ continue
40
+ var held_keycode: Key = _held_keys[entry] as Key
41
+ var event: InputEventKey = InputEventKey.new()
42
+ event.keycode = held_keycode
43
+ event.physical_keycode = held_keycode
44
+ event.pressed = false
45
+ Input.parse_input_event(event)
46
+ _held_keys.clear()
47
+
48
+
49
+ func register_commands() -> void:
50
+ register_command("click", _cmd_click)
51
+ register_command("key_press", _cmd_key_press)
52
+ register_command("key_hold", _cmd_key_hold)
53
+ register_command("key_release", _cmd_key_release)
54
+ register_command("scroll", _cmd_scroll)
55
+ register_command("mouse_move", _cmd_mouse_move)
56
+ register_command("mouse_drag", _cmd_mouse_drag)
57
+ register_command("gamepad", _cmd_gamepad)
58
+ register_command("touch", _cmd_touch)
59
+ register_command("input_state", _cmd_input_state)
60
+ register_command("input_action", _cmd_input_action)
61
+
62
+
63
+ # --- Click ---
64
+ func _cmd_click(params: Dictionary) -> void:
65
+ var reader: CommandParams = CommandParams.new(params)
66
+ var x: float = reader.required_number("x")
67
+ var y: float = reader.required_number("y")
68
+ var button: int = reader.optional_int("button", MOUSE_BUTTON_LEFT, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_XBUTTON2)
69
+ if params_invalid(reader):
70
+ return
71
+
72
+ var pos: Vector2 = Vector2(x, y)
73
+
74
+ # Mouse button press
75
+ var press_event: InputEventMouseButton = InputEventMouseButton.new()
76
+ press_event.position = pos
77
+ press_event.global_position = pos
78
+ press_event.button_index = button as MouseButton
79
+ press_event.pressed = true
80
+ Input.parse_input_event(press_event)
81
+
82
+ # Wait a frame then release
83
+ await get_tree().process_frame
84
+
85
+ var release_event: InputEventMouseButton = InputEventMouseButton.new()
86
+ release_event.position = pos
87
+ release_event.global_position = pos
88
+ release_event.button_index = button as MouseButton
89
+ release_event.pressed = false
90
+ Input.parse_input_event(release_event)
91
+
92
+ respond({"success": true, "clicked": {"x": x, "y": y, "button": button}})
93
+
94
+
95
+ # --- Key Press ---
96
+ # key_press/key_hold/key_release accept exactly one of `action` (a Godot input
97
+ # action) or `key` (a key name resolvable through the key map).
98
+ func _read_key_or_action(reader: CommandParams) -> Dictionary:
99
+ var action: String = reader.optional_string("action", "")
100
+ var key: String = reader.optional_string("key", "")
101
+ if reader.failed():
102
+ return {}
103
+ if action.is_empty() and key.is_empty():
104
+ reader.fail("Must provide 'key' or 'action' parameter", {"param": "key", "reason": "missing"})
105
+ return {}
106
+ if not action.is_empty() and not key.is_empty():
107
+ reader.fail("Provide exactly one of 'key' or 'action'", {"param": "key", "reason": "mutually_exclusive"})
108
+ return {}
109
+ if not action.is_empty():
110
+ if not InputMap.has_action(action):
111
+ reader.fail("Input action not found: %s" % action, {"param": "action", "reason": "action_not_found", "value": action})
112
+ return {}
113
+ return {"mode": "action", "action": action}
114
+ var keycode: int = _string_to_keycode(key)
115
+ if keycode == KEY_NONE:
116
+ reader.fail("Unknown key: %s" % key, {"param": "key", "reason": "invalid_value", "value": key})
117
+ return {}
118
+ return {"mode": "key", "key": key, "keycode": keycode}
119
+
120
+
121
+ func _cmd_key_press(params: Dictionary) -> void:
122
+ var reader: CommandParams = CommandParams.new(params)
123
+ var pressed: bool = reader.optional_bool("pressed", true)
124
+ var text: String = reader.optional_string("text")
125
+ var physical: bool = reader.optional_bool("physical", false)
126
+ var shift: bool = reader.optional_bool("shift", false)
127
+ var ctrl: bool = reader.optional_bool("ctrl", false)
128
+ var alt: bool = reader.optional_bool("alt", false)
129
+ var meta: bool = reader.optional_bool("meta", false)
130
+ if not text.is_empty():
131
+ if reader.has_param("key") or reader.has_param("action"):
132
+ reader.fail("Provide exactly one of 'key', 'action', or 'text'", {"param": "text", "reason": "mutually_exclusive"})
133
+ if not pressed:
134
+ reader.fail("text injection only supports pressed=true", {"param": "pressed", "reason": "invalid_value"})
135
+ if params_invalid(reader):
136
+ return
137
+ for character_index: int in text.length():
138
+ var codepoint: int = text.unicode_at(character_index)
139
+ var text_event := InputEventKey.new()
140
+ text_event.unicode = codepoint
141
+ _apply_key_modifiers(text_event, shift, ctrl, alt, meta)
142
+ text_event.pressed = true
143
+ Input.parse_input_event(text_event)
144
+ text_event = text_event.duplicate()
145
+ text_event.pressed = false
146
+ Input.parse_input_event(text_event)
147
+ respond({"success": true, "text": text, "codepoints": text.length()})
148
+ return
149
+ var input: Dictionary = _read_key_or_action(reader)
150
+ if params_invalid(reader):
151
+ return
152
+
153
+ if input["mode"] == "action":
154
+ var action: String = input["action"]
155
+ if pressed:
156
+ Input.action_press(action)
157
+ await get_tree().process_frame
158
+ Input.action_release(action)
159
+ else:
160
+ Input.action_release(action)
161
+ respond({"success": true, "action": action, "pressed": pressed})
162
+ return
163
+
164
+ var key: String = input["key"]
165
+ var keycode: int = input["keycode"]
166
+ var event: InputEventKey = InputEventKey.new()
167
+ if physical:
168
+ event.physical_keycode = keycode as Key
169
+ else:
170
+ event.keycode = keycode as Key
171
+ _apply_key_modifiers(event, shift, ctrl, alt, meta)
172
+ event.pressed = pressed
173
+ Input.parse_input_event(event)
174
+
175
+ if pressed:
176
+ # Auto-release after a frame
177
+ await get_tree().process_frame
178
+ var release_event: InputEventKey = InputEventKey.new()
179
+ release_event.keycode = event.keycode
180
+ release_event.physical_keycode = event.physical_keycode
181
+ _apply_key_modifiers(release_event, shift, ctrl, alt, meta)
182
+ release_event.pressed = false
183
+ Input.parse_input_event(release_event)
184
+
185
+ respond({"success": true, "key": key, "pressed": pressed})
186
+
187
+
188
+ func _apply_key_modifiers(event: InputEventKey, shift: bool, ctrl: bool, alt: bool, meta: bool) -> void:
189
+ event.shift_pressed = shift
190
+ event.ctrl_pressed = ctrl
191
+ event.alt_pressed = alt
192
+ event.meta_pressed = meta
193
+
194
+
195
+ # --- Key Hold (no auto-release) ---
196
+ func _cmd_key_hold(params: Dictionary) -> void:
197
+ var reader: CommandParams = CommandParams.new(params)
198
+ var input: Dictionary = _read_key_or_action(reader)
199
+ if params_invalid(reader):
200
+ return
201
+
202
+ if input["mode"] == "action":
203
+ var action: String = input["action"]
204
+ Input.action_press(action)
205
+ _held_keys["action:" + action] = true
206
+ respond({"success": true, "held": action, "type": "action"})
207
+ return
208
+
209
+ var key: String = input["key"]
210
+ var keycode: int = input["keycode"]
211
+ var event: InputEventKey = InputEventKey.new()
212
+ event.keycode = keycode as Key
213
+ event.physical_keycode = keycode as Key
214
+ event.pressed = true
215
+ Input.parse_input_event(event)
216
+ _held_keys["key:" + key.to_upper()] = keycode
217
+ respond({"success": true, "held": key, "type": "key"})
218
+
219
+
220
+ # --- Key Release ---
221
+ func _cmd_key_release(params: Dictionary) -> void:
222
+ var reader: CommandParams = CommandParams.new(params)
223
+ var input: Dictionary = _read_key_or_action(reader)
224
+ if params_invalid(reader):
225
+ return
226
+
227
+ if input["mode"] == "action":
228
+ var action: String = input["action"]
229
+ Input.action_release(action)
230
+ @warning_ignore("return_value_discarded")
231
+ _held_keys.erase("action:" + action)
232
+ respond({"success": true, "released": action, "type": "action"})
233
+ return
234
+
235
+ var key: String = input["key"]
236
+ var keycode: int = input["keycode"]
237
+ var event: InputEventKey = InputEventKey.new()
238
+ event.keycode = keycode as Key
239
+ event.physical_keycode = keycode as Key
240
+ event.pressed = false
241
+ Input.parse_input_event(event)
242
+ @warning_ignore("return_value_discarded")
243
+ _held_keys.erase("key:" + key.to_upper())
244
+ respond({"success": true, "released": key, "type": "key"})
245
+
246
+
247
+ # --- Scroll ---
248
+ func _cmd_scroll(params: Dictionary) -> void:
249
+ var reader: CommandParams = CommandParams.new(params)
250
+ var x: float = reader.optional_number("x", 0.0)
251
+ var y: float = reader.optional_number("y", 0.0)
252
+ var direction: String = reader.optional_enum("direction", "up", ["up", "down", "left", "right"])
253
+ var amount: int = reader.optional_int("amount", 1, 1, 1000)
254
+ if params_invalid(reader):
255
+ return
256
+
257
+ var button_index: int = MOUSE_BUTTON_WHEEL_UP
258
+ match direction:
259
+ "down":
260
+ button_index = MOUSE_BUTTON_WHEEL_DOWN
261
+ "left":
262
+ button_index = MOUSE_BUTTON_WHEEL_LEFT
263
+ "right":
264
+ button_index = MOUSE_BUTTON_WHEEL_RIGHT
265
+
266
+ for i in amount:
267
+ var press_event: InputEventMouseButton = InputEventMouseButton.new()
268
+ press_event.position = Vector2(x, y)
269
+ press_event.global_position = Vector2(x, y)
270
+ press_event.button_index = button_index as MouseButton
271
+ press_event.pressed = true
272
+ press_event.factor = 1.0
273
+ Input.parse_input_event(press_event)
274
+
275
+ var release_event: InputEventMouseButton = InputEventMouseButton.new()
276
+ release_event.position = Vector2(x, y)
277
+ release_event.global_position = Vector2(x, y)
278
+ release_event.button_index = button_index as MouseButton
279
+ release_event.pressed = false
280
+ Input.parse_input_event(release_event)
281
+
282
+ respond({"success": true, "direction": direction, "amount": amount, "position": {"x": x, "y": y}})
283
+
284
+
285
+ # --- Mouse Move ---
286
+ func _cmd_mouse_move(params: Dictionary) -> void:
287
+ var reader: CommandParams = CommandParams.new(params)
288
+ var x: float = reader.required_number("x")
289
+ var y: float = reader.required_number("y")
290
+ var relative_x: float = reader.optional_number("relative_x", 0.0)
291
+ var relative_y: float = reader.optional_number("relative_y", 0.0)
292
+ if params_invalid(reader):
293
+ return
294
+
295
+ var event: InputEventMouseMotion = InputEventMouseMotion.new()
296
+ event.position = Vector2(x, y)
297
+ event.global_position = Vector2(x, y)
298
+ event.relative = Vector2(relative_x, relative_y)
299
+ Input.parse_input_event(event)
300
+
301
+ respond({"success": true, "position": {"x": x, "y": y}})
302
+
303
+
304
+ # --- Mouse Drag ---
305
+ func _cmd_mouse_drag(params: Dictionary) -> void:
306
+ var reader: CommandParams = CommandParams.new(params)
307
+ var from_x: float = reader.required_number("from_x")
308
+ var from_y: float = reader.required_number("from_y")
309
+ var to_x: float = reader.required_number("to_x")
310
+ var to_y: float = reader.required_number("to_y")
311
+ var button: int = reader.optional_int("button", MOUSE_BUTTON_LEFT, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_XBUTTON2)
312
+ var steps: int = reader.optional_int("steps", 10, 1, 1000)
313
+ if not [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_XBUTTON2].has(button):
314
+ reader.fail("button must be a pressable mouse button", {"param": "button", "reason": "invalid_value", "value": button})
315
+ if params_invalid(reader):
316
+ return
317
+
318
+ var from_pos: Vector2 = Vector2(from_x, from_y)
319
+ var to_pos: Vector2 = Vector2(to_x, to_y)
320
+
321
+ # Press at start position
322
+ var press_event: InputEventMouseButton = InputEventMouseButton.new()
323
+ press_event.position = from_pos
324
+ press_event.global_position = from_pos
325
+ press_event.button_index = button as MouseButton
326
+ press_event.button_mask = _mouse_button_mask(button) as MouseButtonMask
327
+ press_event.pressed = true
328
+ Input.parse_input_event(press_event)
329
+
330
+ # Lerp position over steps frames
331
+ for i in steps:
332
+ await get_tree().process_frame
333
+ var t: float = float(i + 1) / float(steps)
334
+ var current_pos: Vector2 = from_pos.lerp(to_pos, t)
335
+ var move_event: InputEventMouseMotion = InputEventMouseMotion.new()
336
+ move_event.position = current_pos
337
+ move_event.global_position = current_pos
338
+ move_event.relative = (to_pos - from_pos) / float(steps)
339
+ move_event.button_mask = _mouse_button_mask(button) as MouseButtonMask
340
+ Input.parse_input_event(move_event)
341
+
342
+ # Release at end position
343
+ var release_event: InputEventMouseButton = InputEventMouseButton.new()
344
+ release_event.position = to_pos
345
+ release_event.global_position = to_pos
346
+ release_event.button_index = button as MouseButton
347
+ release_event.button_mask = 0
348
+ release_event.pressed = false
349
+ Input.parse_input_event(release_event)
350
+
351
+ respond({"success": true, "from": {"x": from_x, "y": from_y}, "to": {"x": to_x, "y": to_y}, "steps": steps})
352
+
353
+
354
+ # --- Gamepad ---
355
+ func _cmd_gamepad(params: Dictionary) -> void:
356
+ var reader: CommandParams = CommandParams.new(params)
357
+ var input_type: String = reader.optional_enum("type", "button", ["button", "axis"])
358
+ var index: int = reader.optional_int("index", 0, 0)
359
+ var value: float = reader.optional_number("value", 0.0, -1.0, 1.0)
360
+ var device: int = reader.optional_int("device", 0, 0)
361
+ var deadzone: float = reader.optional_number("deadzone", 0.0, 0.0, 1.0)
362
+ if input_type == "button" and index > JOY_BUTTON_MAX - 1:
363
+ reader.fail("Gamepad button index is out of range", {"param": "index", "reason": "out_of_range", "value": index})
364
+ if input_type == "axis" and index > JOY_AXIS_MAX - 1:
365
+ reader.fail("Gamepad axis index is out of range", {"param": "index", "reason": "out_of_range", "value": index})
366
+ if params_invalid(reader):
367
+ return
368
+
369
+ if input_type == "button":
370
+ var event: InputEventJoypadButton = InputEventJoypadButton.new()
371
+ event.device = device
372
+ event.button_index = index as JoyButton
373
+ event.pressed = value > 0.5
374
+ event.pressure = value
375
+ Input.parse_input_event(event)
376
+ var button_key: String = "%s:%s" % [device, index]
377
+ if event.pressed:
378
+ _held_gamepad_buttons[button_key] = [device, index]
379
+ else:
380
+ @warning_ignore("return_value_discarded")
381
+ _held_gamepad_buttons.erase(button_key)
382
+ respond({"success": true, "type": "button", "index": index, "pressed": event.pressed, "device": device})
383
+ else:
384
+ var event: InputEventJoypadMotion = InputEventJoypadMotion.new()
385
+ event.device = device
386
+ event.axis = index as JoyAxis
387
+ event.axis_value = 0.0 if absf(value) < deadzone else value
388
+ Input.parse_input_event(event)
389
+ var axis_key: String = "%s:%s" % [device, index]
390
+ if is_zero_approx(event.axis_value):
391
+ @warning_ignore("return_value_discarded")
392
+ _held_gamepad_axes.erase(axis_key)
393
+ else:
394
+ _held_gamepad_axes[axis_key] = [device, index]
395
+ respond({"success": true, "type": "axis", "index": index, "value": event.axis_value, "raw_value": value, "deadzone": deadzone, "device": device})
396
+
397
+
398
+ func _release_all_gamepad_inputs() -> void:
399
+ for entry: Array in _held_gamepad_buttons.values():
400
+ var button_event := InputEventJoypadButton.new()
401
+ button_event.device = CommandParams.to_int(entry[0])
402
+ button_event.button_index = CommandParams.to_int(entry[1]) as JoyButton
403
+ button_event.pressed = false
404
+ Input.parse_input_event(button_event)
405
+ for entry: Array in _held_gamepad_axes.values():
406
+ var axis_event := InputEventJoypadMotion.new()
407
+ axis_event.device = CommandParams.to_int(entry[0])
408
+ axis_event.axis = CommandParams.to_int(entry[1]) as JoyAxis
409
+ axis_event.axis_value = 0.0
410
+ Input.parse_input_event(axis_event)
411
+ _held_gamepad_buttons.clear()
412
+ _held_gamepad_axes.clear()
413
+
414
+
415
+ # --- Touch ---
416
+ func _cmd_touch(params: Dictionary) -> void:
417
+ var reader: CommandParams = CommandParams.new(params)
418
+ var action: String = reader.optional_enum("action", "press", ["press", "release", "drag"])
419
+ var x: float = reader.optional_number("x", 0.0)
420
+ var y: float = reader.optional_number("y", 0.0)
421
+ var idx: int = reader.optional_int("index", 0, 0)
422
+ if params_invalid(reader):
423
+ return
424
+ match action:
425
+ "press":
426
+ var ev: InputEventScreenTouch = InputEventScreenTouch.new()
427
+ ev.index = idx
428
+ ev.position = Vector2(x, y)
429
+ ev.pressed = true
430
+ Input.parse_input_event(ev)
431
+ _active_touches[idx] = ev.position
432
+ await get_tree().process_frame
433
+ respond({"success": true, "action": "press", "x": x, "y": y})
434
+ "release":
435
+ var ev: InputEventScreenTouch = InputEventScreenTouch.new()
436
+ ev.index = idx
437
+ ev.position = Vector2(x, y)
438
+ ev.pressed = false
439
+ Input.parse_input_event(ev)
440
+ @warning_ignore("return_value_discarded")
441
+ _active_touches.erase(idx)
442
+ await get_tree().process_frame
443
+ respond({"success": true, "action": "release", "x": x, "y": y})
444
+ "drag":
445
+ var to_x: float = reader.optional_number("to_x", x)
446
+ var to_y: float = reader.optional_number("to_y", y)
447
+ var steps: int = reader.optional_int("steps", 10, 1, 1000)
448
+ if params_invalid(reader):
449
+ return
450
+ var press_ev: InputEventScreenTouch = InputEventScreenTouch.new()
451
+ press_ev.index = idx
452
+ press_ev.position = Vector2(x, y)
453
+ press_ev.pressed = true
454
+ Input.parse_input_event(press_ev)
455
+ _active_touches[idx] = press_ev.position
456
+ for i in range(steps):
457
+ var t: float = float(i + 1) / float(steps)
458
+ var drag_ev: InputEventScreenDrag = InputEventScreenDrag.new()
459
+ drag_ev.index = idx
460
+ drag_ev.position = Vector2(lerpf(x, to_x, t), lerpf(y, to_y, t))
461
+ Input.parse_input_event(drag_ev)
462
+ _active_touches[idx] = drag_ev.position
463
+ await get_tree().process_frame
464
+ var rel_ev: InputEventScreenTouch = InputEventScreenTouch.new()
465
+ rel_ev.index = idx
466
+ rel_ev.position = Vector2(to_x, to_y)
467
+ rel_ev.pressed = false
468
+ Input.parse_input_event(rel_ev)
469
+ @warning_ignore("return_value_discarded")
470
+ _active_touches.erase(idx)
471
+ await get_tree().process_frame
472
+ respond({"success": true, "action": "drag", "from": {"x": x, "y": y}, "to": {"x": to_x, "y": to_y}})
473
+
474
+
475
+ func _release_all_touches() -> void:
476
+ for index: Variant in _active_touches:
477
+ var event := InputEventScreenTouch.new()
478
+ event.index = CommandParams.to_int(index)
479
+ event.position = _active_touches[index]
480
+ event.pressed = false
481
+ Input.parse_input_event(event)
482
+ _active_touches.clear()
483
+
484
+
485
+ # --- Input State ---
486
+ func _cmd_input_state(params: Dictionary) -> void:
487
+ var reader: CommandParams = CommandParams.new(params)
488
+ var action: String = reader.optional_enum("action", "query", ["query", "warp_mouse", "set_mouse_mode"])
489
+ if params_invalid(reader):
490
+ return
491
+ match action:
492
+ "query":
493
+ var requested_keys: Array = reader.optional_array("keys")
494
+ var requested_actions: Array = reader.optional_array("actions")
495
+ var requested_buttons: Array = reader.optional_array("mouse_buttons")
496
+ if requested_keys.size() > 128:
497
+ reader.fail("keys exceeds the selector limit", {"param": "keys", "reason": "limit_exceeded", "max_items": 128})
498
+ if requested_actions.size() > 128:
499
+ reader.fail("actions exceeds the selector limit", {"param": "actions", "reason": "limit_exceeded", "max_items": 128})
500
+ if requested_buttons.size() > 9:
501
+ reader.fail("mouse_buttons exceeds the selector limit", {"param": "mouse_buttons", "reason": "limit_exceeded", "max_items": 9})
502
+ if params_invalid(reader):
503
+ return
504
+ var key_states: Dictionary = {}
505
+ for key_value: Variant in requested_keys:
506
+ if not key_value is String:
507
+ reader.fail("keys must contain only strings", {"param": "keys", "reason": "invalid_type"})
508
+ break
509
+ var key_name: String = key_value
510
+ var keycode: int = _string_to_keycode(key_name)
511
+ if keycode == KEY_NONE:
512
+ reader.fail("Unknown key: %s" % key_name, {"param": "keys", "reason": "invalid_value", "value": key_name})
513
+ break
514
+ key_states[key_name] = {
515
+ "pressed": Input.is_key_pressed(keycode as Key),
516
+ "physical_pressed": Input.is_physical_key_pressed(keycode as Key),
517
+ }
518
+ var action_states: Dictionary = {}
519
+ for action_value: Variant in requested_actions:
520
+ if not action_value is String:
521
+ reader.fail("actions must contain only strings", {"param": "actions", "reason": "invalid_type"})
522
+ break
523
+ var action_name: String = action_value
524
+ if not InputMap.has_action(action_name):
525
+ reader.fail("Input action not found: %s" % action_name, {"param": "actions", "reason": "action_not_found", "value": action_name})
526
+ break
527
+ action_states[action_name] = {
528
+ "pressed": Input.is_action_pressed(action_name),
529
+ "strength": Input.get_action_strength(action_name),
530
+ "raw_strength": Input.get_action_raw_strength(action_name),
531
+ }
532
+ var button_states: Dictionary = {}
533
+ for button_value: Variant in requested_buttons:
534
+ if not (button_value is int or CommandParams._is_integral(button_value)):
535
+ reader.fail("mouse_buttons must contain only integers", {"param": "mouse_buttons", "reason": "invalid_type"})
536
+ break
537
+ var button: int = CommandParams.to_int(button_value)
538
+ if button < MOUSE_BUTTON_LEFT or button > MOUSE_BUTTON_XBUTTON2:
539
+ reader.fail("Mouse button is out of range", {"param": "mouse_buttons", "reason": "out_of_range", "value": button})
540
+ break
541
+ button_states[str(button)] = Input.is_mouse_button_pressed(button as MouseButton)
542
+ if params_invalid(reader):
543
+ return
544
+ var mouse_pos: Vector2 = get_viewport().get_mouse_position()
545
+ var joypads: Array = Input.get_connected_joypads()
546
+ respond({
547
+ "success": true,
548
+ "mouse_position": {"x": mouse_pos.x, "y": mouse_pos.y},
549
+ "mouse_mode": _mouse_mode_name(Input.mouse_mode),
550
+ "mouse_buttons": button_states,
551
+ "keys": key_states,
552
+ "actions": action_states,
553
+ "connected_joypads": joypads.size(),
554
+ "joypad_ids": joypads,
555
+ })
556
+ "warp_mouse":
557
+ var x: float = reader.required_number("x")
558
+ var y: float = reader.required_number("y")
559
+ if params_invalid(reader):
560
+ return
561
+ if DisplayServer.get_name() == "headless":
562
+ respond({"error": "warp_mouse is unavailable with the headless display driver", "error_data": {"reason": "unsupported_display", "display_server": DisplayServer.get_name()}})
563
+ return
564
+ var pos: Vector2 = Vector2(x, y)
565
+ Input.warp_mouse(pos)
566
+ respond({"success": true, "action": "warp_mouse", "position": {"x": pos.x, "y": pos.y}})
567
+ "set_mouse_mode":
568
+ var mode_str: String = reader.optional_enum("mouse_mode", "visible", ["visible", "hidden", "captured", "confined"])
569
+ if params_invalid(reader):
570
+ return
571
+ if DisplayServer.get_name() == "headless" and mode_str != "visible":
572
+ respond({"error": "mouse mode '%s' is unavailable with the headless display driver" % mode_str, "error_data": {"reason": "unsupported_display", "display_server": DisplayServer.get_name(), "mode": mode_str}})
573
+ return
574
+ var mode_val: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
575
+ match mode_str:
576
+ "hidden": mode_val = Input.MOUSE_MODE_HIDDEN
577
+ "captured": mode_val = Input.MOUSE_MODE_CAPTURED
578
+ "confined": mode_val = Input.MOUSE_MODE_CONFINED
579
+ Input.mouse_mode = mode_val
580
+ _mouse_mode_owned = true
581
+ respond({"success": true, "action": "set_mouse_mode", "mode": mode_str})
582
+
583
+
584
+ func _mouse_mode_name(mode: Input.MouseMode) -> String:
585
+ match mode:
586
+ Input.MOUSE_MODE_HIDDEN:
587
+ return "hidden"
588
+ Input.MOUSE_MODE_CAPTURED:
589
+ return "captured"
590
+ Input.MOUSE_MODE_CONFINED:
591
+ return "confined"
592
+ Input.MOUSE_MODE_CONFINED_HIDDEN:
593
+ return "confined_hidden"
594
+ _:
595
+ return "visible"
596
+
597
+
598
+ # --- Input Action ---
599
+ func _cmd_input_action(params: Dictionary) -> void:
600
+ var reader: CommandParams = CommandParams.new(params)
601
+ var action: String = reader.required_enum("action", ["set_strength", "add_action", "remove_action", "list"])
602
+ if params_invalid(reader):
603
+ return
604
+ match action:
605
+ "set_strength":
606
+ var action_name: String = reader.required_string("action_name")
607
+ var strength: float = reader.optional_number("strength", 1.0, 0.0, 1.0)
608
+ if params_invalid(reader):
609
+ return
610
+ if not InputMap.has_action(action_name):
611
+ reader.fail("Input action not found: %s" % action_name, {"param": "action_name", "reason": "action_not_found", "value": action_name})
612
+ send_params_error(reader)
613
+ return
614
+ if is_zero_approx(strength):
615
+ Input.action_release(action_name)
616
+ @warning_ignore("return_value_discarded")
617
+ _held_keys.erase("action:" + action_name)
618
+ else:
619
+ Input.action_press(action_name, strength)
620
+ _held_keys["action:" + action_name] = 1
621
+ respond({"success": true, "action": "set_strength", "action_name": action_name, "strength": strength})
622
+ "add_action":
623
+ var action_name: String = reader.required_string("action_name")
624
+ var key: String = reader.optional_string("key", "")
625
+ if params_invalid(reader):
626
+ return
627
+ if not InputMap.has_action(action_name):
628
+ InputMap.add_action(action_name)
629
+ if not key.is_empty():
630
+ var keycode: int = _string_to_keycode(key)
631
+ if keycode == KEY_NONE:
632
+ reader.fail("Unknown key: %s" % key, {"param": "key", "reason": "invalid_value", "value": key})
633
+ send_params_error(reader)
634
+ return
635
+ var ev: InputEventKey = InputEventKey.new()
636
+ ev.keycode = keycode as Key
637
+ ev.physical_keycode = keycode as Key
638
+ if not InputMap.action_has_event(action_name, ev):
639
+ InputMap.action_add_event(action_name, ev)
640
+ respond({"success": true, "action": "add_action", "action_name": action_name})
641
+ "remove_action":
642
+ var action_name: String = reader.required_string("action_name")
643
+ if params_invalid(reader):
644
+ return
645
+ if InputMap.has_action(action_name):
646
+ Input.action_release(action_name)
647
+ @warning_ignore("return_value_discarded")
648
+ _held_keys.erase("action:" + action_name)
649
+ InputMap.erase_action(action_name)
650
+ respond({"success": true, "action": "remove_action", "action_name": action_name})
651
+ "list":
652
+ var actions: Array = InputMap.get_actions()
653
+ respond({"success": true, "actions": actions})
654
+
655
+
656
+ func _mouse_button_mask(button: int) -> int:
657
+ return 1 << (button - 1)
658
+
659
+
660
+ # --- Key String to Keycode ---
661
+ func _init_key_map() -> void:
662
+ _key_map = {
663
+ "A": KEY_A, "B": KEY_B, "C": KEY_C, "D": KEY_D,
664
+ "E": KEY_E, "F": KEY_F, "G": KEY_G, "H": KEY_H,
665
+ "I": KEY_I, "J": KEY_J, "K": KEY_K, "L": KEY_L,
666
+ "M": KEY_M, "N": KEY_N, "O": KEY_O, "P": KEY_P,
667
+ "Q": KEY_Q, "R": KEY_R, "S": KEY_S, "T": KEY_T,
668
+ "U": KEY_U, "V": KEY_V, "W": KEY_W, "X": KEY_X,
669
+ "Y": KEY_Y, "Z": KEY_Z,
670
+ "0": KEY_0, "1": KEY_1, "2": KEY_2, "3": KEY_3,
671
+ "4": KEY_4, "5": KEY_5, "6": KEY_6, "7": KEY_7,
672
+ "8": KEY_8, "9": KEY_9,
673
+ "SPACE": KEY_SPACE, "ENTER": KEY_ENTER, "RETURN": KEY_ENTER,
674
+ "ESCAPE": KEY_ESCAPE, "ESC": KEY_ESCAPE,
675
+ "TAB": KEY_TAB, "BACKSPACE": KEY_BACKSPACE,
676
+ "DELETE": KEY_DELETE, "INSERT": KEY_INSERT,
677
+ "HOME": KEY_HOME, "END": KEY_END,
678
+ "PAGEUP": KEY_PAGEUP, "PAGE_UP": KEY_PAGEUP,
679
+ "PAGEDOWN": KEY_PAGEDOWN, "PAGE_DOWN": KEY_PAGEDOWN,
680
+ "UP": KEY_UP, "DOWN": KEY_DOWN, "LEFT": KEY_LEFT, "RIGHT": KEY_RIGHT,
681
+ "SHIFT": KEY_SHIFT, "CTRL": KEY_CTRL, "CONTROL": KEY_CTRL,
682
+ "ALT": KEY_ALT, "CAPSLOCK": KEY_CAPSLOCK, "CAPS_LOCK": KEY_CAPSLOCK,
683
+ "F1": KEY_F1, "F2": KEY_F2, "F3": KEY_F3, "F4": KEY_F4,
684
+ "F5": KEY_F5, "F6": KEY_F6, "F7": KEY_F7, "F8": KEY_F8,
685
+ "F9": KEY_F9, "F10": KEY_F10, "F11": KEY_F11, "F12": KEY_F12,
686
+ }
687
+
688
+
689
+ func _string_to_keycode(key_str: String) -> int:
690
+ var upper: String = key_str.to_upper()
691
+ if _key_map.has(upper):
692
+ return _key_map[upper]
693
+ if key_str.length() == 1:
694
+ return key_str.unicode_at(0)
695
+ return KEY_NONE