@satelliteoflove/godot-mcp 3.16.0 → 3.18.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.
@@ -115,15 +115,7 @@ func _get_editor_input_map() -> Dictionary:
115
115
 
116
116
  func _event_to_string(event: InputEvent) -> String:
117
117
  if event is InputEventKey:
118
- var key_event := event as InputEventKey
119
- var key_name := OS.get_keycode_string(key_event.keycode)
120
- if key_event.ctrl_pressed:
121
- key_name = "Ctrl+" + key_name
122
- if key_event.alt_pressed:
123
- key_name = "Alt+" + key_name
124
- if key_event.shift_pressed:
125
- key_name = "Shift+" + key_name
126
- return key_name
118
+ return MCPKeyNames.event_string(event as InputEventKey)
127
119
  elif event is InputEventMouseButton:
128
120
  var mouse_event := event as InputEventMouseButton
129
121
  match mouse_event.button_index:
@@ -137,10 +129,12 @@ func _event_to_string(event: InputEvent) -> String:
137
129
  return "Mouse Button %d" % mouse_event.button_index
138
130
  elif event is InputEventJoypadButton:
139
131
  var joy_event := event as InputEventJoypadButton
140
- return "Joypad Button %d" % joy_event.button_index
132
+ return "Joypad Button %d (%s)" % [joy_event.button_index, MCPJoyNames.button_name(joy_event.button_index)]
141
133
  elif event is InputEventJoypadMotion:
134
+ # The signed axis_value is the direction bit an agent needs to lift the
135
+ # binding straight into an injection (e.g. move_left = left_x, value -1.0).
142
136
  var joy_motion := event as InputEventJoypadMotion
143
- return "Joypad Axis %d" % joy_motion.axis
137
+ return "Joypad Axis %d (%s, value %+.1f)" % [joy_motion.axis, MCPJoyNames.axis_name(joy_motion.axis), joy_motion.axis_value]
144
138
  return event.as_text()
145
139
 
146
140
 
@@ -0,0 +1,68 @@
1
+ @tool
2
+ class_name MCPJoyNames
3
+ extends RefCounted
4
+ ## Canonical joypad name<->index tables, shared by the game bridge (event
5
+ ## injection) and the editor input commands (get_input_map display) so the
6
+ ## wire vocabulary and the displayed names can never drift apart.
7
+
8
+ const BUTTONS := {
9
+ "a": JOY_BUTTON_A,
10
+ "b": JOY_BUTTON_B,
11
+ "x": JOY_BUTTON_X,
12
+ "y": JOY_BUTTON_Y,
13
+ "back": JOY_BUTTON_BACK,
14
+ "guide": JOY_BUTTON_GUIDE,
15
+ "start": JOY_BUTTON_START,
16
+ "left_stick": JOY_BUTTON_LEFT_STICK,
17
+ "right_stick": JOY_BUTTON_RIGHT_STICK,
18
+ "left_shoulder": JOY_BUTTON_LEFT_SHOULDER,
19
+ "right_shoulder": JOY_BUTTON_RIGHT_SHOULDER,
20
+ "dpad_up": JOY_BUTTON_DPAD_UP,
21
+ "dpad_down": JOY_BUTTON_DPAD_DOWN,
22
+ "dpad_left": JOY_BUTTON_DPAD_LEFT,
23
+ "dpad_right": JOY_BUTTON_DPAD_RIGHT,
24
+ "misc1": JOY_BUTTON_MISC1,
25
+ "paddle1": JOY_BUTTON_PADDLE1,
26
+ "paddle2": JOY_BUTTON_PADDLE2,
27
+ "paddle3": JOY_BUTTON_PADDLE3,
28
+ "paddle4": JOY_BUTTON_PADDLE4,
29
+ "touchpad": JOY_BUTTON_TOUCHPAD,
30
+ }
31
+
32
+ const AXES := {
33
+ "left_x": JOY_AXIS_LEFT_X,
34
+ "left_y": JOY_AXIS_LEFT_Y,
35
+ "right_x": JOY_AXIS_RIGHT_X,
36
+ "right_y": JOY_AXIS_RIGHT_Y,
37
+ "trigger_left": JOY_AXIS_TRIGGER_LEFT,
38
+ "trigger_right": JOY_AXIS_TRIGGER_RIGHT,
39
+ }
40
+
41
+
42
+ ## Resolve a wire value (name string, or raw index as an escape hatch) to a
43
+ ## JoyButton index. Returns -1 for anything unknown.
44
+ static func button_index(value: Variant) -> int:
45
+ if value is int or value is float:
46
+ var i := int(value)
47
+ return i if i >= 0 and i < JOY_BUTTON_SDL_MAX else -1
48
+ return int(BUTTONS.get(str(value), -1))
49
+
50
+
51
+ ## Resolve a wire axis name to a JoyAxis index. Names only — the name itself
52
+ ## is what tells an agent trigger (0..1) from stick (-1..1). Returns -1 if unknown.
53
+ static func axis_index(value: Variant) -> int:
54
+ return int(AXES.get(str(value), -1))
55
+
56
+
57
+ static func button_name(idx: int) -> String:
58
+ for n in BUTTONS:
59
+ if int(BUTTONS[n]) == idx:
60
+ return n
61
+ return "button_%d" % idx
62
+
63
+
64
+ static func axis_name(idx: int) -> String:
65
+ for n in AXES:
66
+ if int(AXES[n]) == idx:
67
+ return n
68
+ return "axis_%d" % idx
@@ -0,0 +1 @@
1
+ uid://row4hmo5gdem
@@ -0,0 +1,133 @@
1
+ @tool
2
+ class_name MCPKeyNames
3
+ extends RefCounted
4
+ ## Canonical keyboard name<->keycode parsing/formatting (#290), shared by the
5
+ ## game bridge (raw InputEventKey injection) and the editor input commands
6
+ ## (get_input_map display) so the wire vocabulary and the displayed names can
7
+ ## never drift apart.
8
+ ##
9
+ ## Why this exists rather than calling OS.find_keycode_from_string directly:
10
+ ## that engine helper parses Ctrl/Shift/Alt prefixes but SILENTLY DROPS Meta/Cmd
11
+ ## (verified Godot 4.6 — "Meta+Q" returns a bare KEY_Q). We split modifiers here
12
+ ## ourselves so meta round-trips, and only hand the base token to the engine.
13
+
14
+ ## Modifier token -> key mask. Case-insensitive (callers lowercase first).
15
+ ## Aliases cover the common cross-platform spellings agents reach for.
16
+ const MODIFIERS := {
17
+ "ctrl": KEY_MASK_CTRL,
18
+ "control": KEY_MASK_CTRL,
19
+ "shift": KEY_MASK_SHIFT,
20
+ "alt": KEY_MASK_ALT,
21
+ "option": KEY_MASK_ALT,
22
+ "opt": KEY_MASK_ALT,
23
+ "meta": KEY_MASK_META,
24
+ "cmd": KEY_MASK_META,
25
+ "command": KEY_MASK_META,
26
+ "super": KEY_MASK_META,
27
+ "win": KEY_MASK_META,
28
+ }
29
+
30
+ ## Key mask -> the modifier's own keycode, so a combo can press each modifier as
31
+ ## a real key (the only way is_key_pressed(KEY_CTRL) ever reads true — the
32
+ ## modifier FLAG on another event does not update the polled singleton).
33
+ const MODIFIER_KEYCODES := {
34
+ KEY_MASK_CTRL: KEY_CTRL,
35
+ KEY_MASK_SHIFT: KEY_SHIFT,
36
+ KEY_MASK_ALT: KEY_ALT,
37
+ KEY_MASK_META: KEY_META,
38
+ }
39
+
40
+ # The four modifier masks, in canonical display/iteration order.
41
+ const _MOD_ORDER: Array = [KEY_MASK_CTRL, KEY_MASK_SHIFT, KEY_MASK_ALT, KEY_MASK_META]
42
+ const _MOD_TOKENS := {
43
+ KEY_MASK_CTRL: "Ctrl",
44
+ KEY_MASK_SHIFT: "Shift",
45
+ KEY_MASK_ALT: "Alt",
46
+ KEY_MASK_META: "Meta",
47
+ }
48
+
49
+
50
+ ## Parse a wire key value into {code:int, mask:int}. Accepts a string with
51
+ ## optional modifier prefixes ("a", "Escape", "ctrl+s", "shift+alt+f1") or a raw
52
+ ## Godot Key enum int (which may carry modifier mask bits). Returns code = KEY_NONE
53
+ ## (0) for anything that does not resolve to a base key, which the caller turns
54
+ ## into an "Unknown key" error.
55
+ static func parse(value: Variant) -> Dictionary:
56
+ if value is int or value is float:
57
+ var raw := int(value)
58
+ return {"code": raw & ~int(KEY_MODIFIER_MASK), "mask": raw & int(KEY_MODIFIER_MASK)}
59
+
60
+ var s := str(value).strip_edges()
61
+ if s.is_empty():
62
+ return {"code": KEY_NONE, "mask": 0}
63
+
64
+ var mask := 0
65
+ var base := ""
66
+ for part in s.split("+", false):
67
+ var token := String(part).strip_edges()
68
+ if token.is_empty():
69
+ continue
70
+ var lower := token.to_lower()
71
+ if MODIFIERS.has(lower):
72
+ mask |= int(MODIFIERS[lower])
73
+ else:
74
+ # The last non-modifier token wins as the base (a stray extra base
75
+ # would just overwrite, which is fine — a malformed combo resolves to
76
+ # whatever its final base token is, or KEY_NONE if none resolves).
77
+ base = token
78
+
79
+ if base.is_empty():
80
+ # A lone modifier name ("shift", "ctrl", "cmd") means the modifier KEY
81
+ # itself — e.g. a game binding bare Shift, or polling is_key_pressed(KEY_CTRL).
82
+ # Exactly one modifier resolves to its keycode; a baseless multi-modifier
83
+ # combo ("ctrl+shift") is ambiguous and stays unknown.
84
+ for m in _MOD_ORDER:
85
+ if mask == int(m):
86
+ return {"code": int(MODIFIER_KEYCODES[m]), "mask": 0}
87
+ return {"code": KEY_NONE, "mask": mask}
88
+
89
+ # find_keycode_from_string handles Ctrl/Shift/Alt itself; strip any mask bits
90
+ # it folded in so `code` is always a bare base keycode.
91
+ var resolved := int(OS.find_keycode_from_string(base))
92
+ mask |= resolved & int(KEY_MODIFIER_MASK)
93
+ var code := resolved & ~int(KEY_MODIFIER_MASK)
94
+ return {"code": code, "mask": mask}
95
+
96
+
97
+ ## The modifier keycodes (KEY_CTRL, ...) present in a mask, in canonical order,
98
+ ## so a combo can press each one as its own key event.
99
+ static func modifier_key_indices(mask: int) -> Array:
100
+ var out: Array = []
101
+ for m in _MOD_ORDER:
102
+ if mask & int(m):
103
+ out.append(int(MODIFIER_KEYCODES[m]))
104
+ return out
105
+
106
+
107
+ ## Canonical display for a key binding (get_input_map). Modifier prefixes come
108
+ ## from the event's own flags with fixed tokens (platform-stable, and re-parseable
109
+ ## by parse() — unlike OS.get_keycode_string's mask spelling, which
110
+ ## find_keycode_from_string cannot read back for Meta). A logical key + modifier
111
+ ## combo round-trips through parse(); a physical-only binding (keycode unset)
112
+ ## renders from physical_keycode with a "(physical)" marker that tells an agent to
113
+ ## inject with physical:true — that marker is display-only, not re-parseable.
114
+ static func event_string(event: InputEventKey) -> String:
115
+ var prefix := ""
116
+ if event.ctrl_pressed:
117
+ prefix += _MOD_TOKENS[KEY_MASK_CTRL] + "+"
118
+ if event.shift_pressed:
119
+ prefix += _MOD_TOKENS[KEY_MASK_SHIFT] + "+"
120
+ if event.alt_pressed:
121
+ prefix += _MOD_TOKENS[KEY_MASK_ALT] + "+"
122
+ if event.meta_pressed:
123
+ prefix += _MOD_TOKENS[KEY_MASK_META] + "+"
124
+
125
+ if event.keycode != KEY_NONE:
126
+ var base := OS.get_keycode_string(event.keycode)
127
+ return prefix + (base if not base.is_empty() else "Key %d" % int(event.keycode))
128
+ if event.physical_keycode != KEY_NONE:
129
+ var pbase := OS.get_keycode_string(event.physical_keycode)
130
+ return prefix + (pbase if not pbase.is_empty() else "Key %d" % int(event.physical_keycode)) + " (physical)"
131
+ # Last resort: an event with neither keycode set (e.g. unicode-only).
132
+ var label := prefix.trim_suffix("+")
133
+ return label if not label.is_empty() else "(unset)"
@@ -0,0 +1 @@
1
+ uid://blya2h0lff4ar