Files
server-deploy/claude-dev-stack/godot-mcp-pro-v1.14.1/addons/godot_mcp/commands/input_commands.gd

163 lines
5.2 KiB
GDScript

@tool
extends "res://addons/godot_mcp/commands/base_command.gd"
const COMMANDS_PATH := "user://mcp_input_commands"
func get_commands() -> Dictionary:
return {
"simulate_key": _simulate_key,
"simulate_mouse_click": _simulate_mouse_click,
"simulate_mouse_move": _simulate_mouse_move,
"simulate_action": _simulate_action,
"simulate_sequence": _simulate_sequence,
}
func _simulate_key(params: Dictionary) -> Dictionary:
var result := require_string(params, "keycode")
if result[1] != null:
return result[1]
var keycode: String = result[0]
var pressed: bool = optional_bool(params, "pressed", true)
var shift: bool = optional_bool(params, "shift", false)
var ctrl: bool = optional_bool(params, "ctrl", false)
var alt: bool = optional_bool(params, "alt", false)
var event := {
"type": "key",
"keycode": keycode,
"pressed": pressed,
"shift": shift,
"ctrl": ctrl,
"alt": alt,
}
_write_commands([event])
return success({"sent": true, "event": event})
func _simulate_mouse_click(params: Dictionary) -> Dictionary:
var button: int = optional_int(params, "button", 1) # MOUSE_BUTTON_LEFT
var pressed: bool = optional_bool(params, "pressed", true)
var double_click: bool = optional_bool(params, "double_click", false)
var auto_release: bool = optional_bool(params, "auto_release", true)
var x: float = float(params.get("x", 0))
var y: float = float(params.get("y", 0))
var press_event := {
"type": "mouse_button",
"button": button,
"pressed": pressed,
"double_click": double_click,
"position": {"x": x, "y": y},
}
# Auto-release: send press + release in sequence so UI buttons actually fire
if pressed and auto_release:
var release_event := press_event.duplicate()
release_event["pressed"] = false
var sequence_data := {
"sequence_events": [press_event, release_event],
"frame_delay": 1,
}
var json := JSON.stringify(sequence_data)
var file := FileAccess.open(COMMANDS_PATH, FileAccess.WRITE)
if file == null:
return error_internal("Failed to write commands: %s" % error_string(FileAccess.get_open_error()))
file.store_string(json)
file.close()
return success({"sent": true, "event": press_event, "auto_release": true})
_write_commands([press_event])
return success({"sent": true, "event": press_event})
func _simulate_mouse_move(params: Dictionary) -> Dictionary:
var x: float = float(params.get("x", 0))
var y: float = float(params.get("y", 0))
var rel_x: float = float(params.get("relative_x", 0))
var rel_y: float = float(params.get("relative_y", 0))
var button_mask: int = optional_int(params, "button_mask", 0)
var unhandled_explicit: bool = params.has("unhandled")
var unhandled: bool = optional_bool(params, "unhandled", false)
var event := {
"type": "mouse_motion",
"position": {"x": x, "y": y},
"relative": {"x": rel_x, "y": rel_y},
"button_mask": button_mask,
}
# Auto-enable unhandled for drag motions (camera-pan use case) ONLY when
# the caller did NOT explicitly pass an "unhandled" key. If they passed
# one — true or false — honor it. This lets UI drag-and-drop tests opt
# back into normal GUI dispatch by passing unhandled: false explicitly.
if unhandled_explicit:
event["unhandled"] = unhandled
elif button_mask > 0:
event["unhandled"] = true
_write_commands([event])
return success({"sent": true, "event": event})
func _simulate_action(params: Dictionary) -> Dictionary:
var result := require_string(params, "action")
if result[1] != null:
return result[1]
var action_name: String = result[0]
var pressed: bool = optional_bool(params, "pressed", true)
var strength: float = float(params.get("strength", 1.0))
var event := {
"type": "action",
"action": action_name,
"pressed": pressed,
"strength": strength,
}
_write_commands([event])
return success({"sent": true, "event": event})
func _simulate_sequence(params: Dictionary) -> Dictionary:
if not params.has("events") or not params["events"] is Array:
return error_invalid_params("Missing required parameter: events (Array)")
var events: Array = params["events"]
if events.is_empty():
return error_invalid_params("Events array is empty")
var frame_delay: int = optional_int(params, "frame_delay", 1)
for event_data: Dictionary in events:
if not event_data.has("type") or (event_data["type"] as String).is_empty():
return error_invalid_params("Invalid event in sequence: %s" % str(event_data))
if frame_delay <= 0:
# All events in one frame - write as plain array
_write_commands(events)
else:
# Sequence with frame delay - game side handles timing
var sequence_data := {
"sequence_events": events,
"frame_delay": frame_delay,
}
var json := JSON.stringify(sequence_data)
var file := FileAccess.open(COMMANDS_PATH, FileAccess.WRITE)
if file == null:
return error_internal("Failed to write commands: %s" % error_string(FileAccess.get_open_error()))
file.store_string(json)
file.close()
return success({"sent": true, "event_count": events.size(), "frame_delay": frame_delay})
func _write_commands(events: Array) -> void:
var json := JSON.stringify(events)
var file := FileAccess.open(COMMANDS_PATH, FileAccess.WRITE)
if file == null:
push_error("[MCP Input] Failed to write commands: %s" % error_string(FileAccess.get_open_error()))
return
file.store_string(json)
file.close()