@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,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
|