@satelliteoflove/godot-mcp 2.3.0 → 2.4.1

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 (77) hide show
  1. package/README.md +58 -27
  2. package/addon/command_router.gd +39 -0
  3. package/addon/command_router.gd.uid +1 -0
  4. package/addon/commands/animation_commands.gd +633 -0
  5. package/addon/commands/animation_commands.gd.uid +1 -0
  6. package/addon/commands/debug_commands.gd +109 -0
  7. package/addon/commands/debug_commands.gd.uid +1 -0
  8. package/addon/commands/file_commands.gd +95 -0
  9. package/addon/commands/file_commands.gd.uid +1 -0
  10. package/addon/commands/node_commands.gd +255 -0
  11. package/addon/commands/node_commands.gd.uid +1 -0
  12. package/addon/commands/project_commands.gd +114 -0
  13. package/addon/commands/project_commands.gd.uid +1 -0
  14. package/addon/commands/resource_commands.gd +293 -0
  15. package/addon/commands/resource_commands.gd.uid +1 -0
  16. package/addon/commands/scene3d_commands.gd +162 -0
  17. package/addon/commands/scene3d_commands.gd.uid +1 -0
  18. package/addon/commands/scene_commands.gd +131 -0
  19. package/addon/commands/scene_commands.gd.uid +1 -0
  20. package/addon/commands/screenshot_commands.gd +130 -0
  21. package/addon/commands/screenshot_commands.gd.uid +1 -0
  22. package/addon/commands/script_commands.gd +156 -0
  23. package/addon/commands/script_commands.gd.uid +1 -0
  24. package/addon/commands/selection_commands.gd +170 -0
  25. package/addon/commands/selection_commands.gd.uid +1 -0
  26. package/addon/commands/system_commands.gd +29 -0
  27. package/addon/commands/tilemap_commands.gd +657 -0
  28. package/addon/commands/tilemap_commands.gd.uid +1 -0
  29. package/addon/core/base_command.gd +58 -0
  30. package/addon/core/base_command.gd.uid +1 -0
  31. package/addon/core/mcp_debugger_plugin.gd +149 -0
  32. package/addon/core/mcp_debugger_plugin.gd.uid +1 -0
  33. package/addon/core/mcp_logger.gd +40 -0
  34. package/addon/core/mcp_logger.gd.uid +1 -0
  35. package/addon/core/mcp_utils.gd +129 -0
  36. package/addon/core/mcp_utils.gd.uid +1 -0
  37. package/addon/game_bridge/mcp_game_bridge.gd +195 -0
  38. package/addon/game_bridge/mcp_game_bridge.gd.uid +1 -0
  39. package/addon/plugin.cfg +8 -0
  40. package/addon/plugin.gd +89 -0
  41. package/addon/plugin.gd.uid +1 -0
  42. package/addon/ui/status_panel.gd +23 -0
  43. package/addon/ui/status_panel.gd.uid +1 -0
  44. package/addon/ui/status_panel.tscn +41 -0
  45. package/addon/websocket_server.gd +143 -0
  46. package/addon/websocket_server.gd.uid +1 -0
  47. package/dist/cli.d.ts +3 -0
  48. package/dist/cli.d.ts.map +1 -0
  49. package/dist/cli.js +60 -0
  50. package/dist/cli.js.map +1 -0
  51. package/dist/connection/websocket.d.ts +14 -0
  52. package/dist/connection/websocket.d.ts.map +1 -1
  53. package/dist/connection/websocket.js +69 -1
  54. package/dist/connection/websocket.js.map +1 -1
  55. package/dist/core/types.d.ts +5 -0
  56. package/dist/core/types.d.ts.map +1 -1
  57. package/dist/index.d.ts +1 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +1 -6
  60. package/dist/index.js.map +1 -1
  61. package/dist/installer/install.d.ts +13 -0
  62. package/dist/installer/install.d.ts.map +1 -0
  63. package/dist/installer/install.js +77 -0
  64. package/dist/installer/install.js.map +1 -0
  65. package/dist/tools/editor.d.ts.map +1 -1
  66. package/dist/tools/editor.js.map +1 -1
  67. package/dist/tools/project.d.ts +3 -3
  68. package/dist/tools/project.d.ts.map +1 -1
  69. package/dist/tools/project.js +28 -2
  70. package/dist/tools/project.js.map +1 -1
  71. package/dist/tools/scene3d.d.ts.map +1 -1
  72. package/dist/tools/scene3d.js.map +1 -1
  73. package/dist/version.d.ts +2 -0
  74. package/dist/version.d.ts.map +1 -0
  75. package/dist/version.js +20 -0
  76. package/dist/version.js.map +1 -0
  77. package/package.json +6 -4
@@ -0,0 +1,633 @@
1
+ @tool
2
+ extends MCPBaseCommand
3
+ class_name MCPAnimationCommands
4
+
5
+
6
+ const TRACK_TYPE_MAP := {
7
+ "value": Animation.TYPE_VALUE,
8
+ "position_3d": Animation.TYPE_POSITION_3D,
9
+ "rotation_3d": Animation.TYPE_ROTATION_3D,
10
+ "scale_3d": Animation.TYPE_SCALE_3D,
11
+ "blend_shape": Animation.TYPE_BLEND_SHAPE,
12
+ "method": Animation.TYPE_METHOD,
13
+ "bezier": Animation.TYPE_BEZIER,
14
+ "audio": Animation.TYPE_AUDIO,
15
+ "animation": Animation.TYPE_ANIMATION
16
+ }
17
+
18
+ const LOOP_MODE_MAP := {
19
+ "none": Animation.LOOP_NONE,
20
+ "linear": Animation.LOOP_LINEAR,
21
+ "pingpong": Animation.LOOP_PINGPONG
22
+ }
23
+
24
+
25
+ func get_commands() -> Dictionary:
26
+ return {
27
+ "list_animation_players": list_animation_players,
28
+ "get_animation_player_info": get_animation_player_info,
29
+ "get_animation_details": get_animation_details,
30
+ "get_track_keyframes": get_track_keyframes,
31
+ "play_animation": play_animation,
32
+ "stop_animation": stop_animation,
33
+ "seek_animation": seek_animation,
34
+ "create_animation": create_animation,
35
+ "delete_animation": delete_animation,
36
+ "update_animation_properties": update_animation_properties,
37
+ "add_animation_track": add_animation_track,
38
+ "remove_animation_track": remove_animation_track,
39
+ "add_keyframe": add_keyframe,
40
+ "remove_keyframe": remove_keyframe,
41
+ "update_keyframe": update_keyframe
42
+ }
43
+
44
+
45
+ func _get_animation_player(node_path: String) -> AnimationPlayer:
46
+ var node := _get_node(node_path)
47
+ if not node:
48
+ return null
49
+ if not node is AnimationPlayer:
50
+ return null
51
+ return node as AnimationPlayer
52
+
53
+
54
+ func _get_animation(player: AnimationPlayer, anim_name: String) -> Animation:
55
+ if not player.has_animation(anim_name):
56
+ return null
57
+ return player.get_animation(anim_name)
58
+
59
+
60
+ func _track_type_to_string(track_type: int) -> String:
61
+ for key in TRACK_TYPE_MAP:
62
+ if TRACK_TYPE_MAP[key] == track_type:
63
+ return key
64
+ return "unknown"
65
+
66
+
67
+ func _loop_mode_to_string(loop_mode: int) -> String:
68
+ for key in LOOP_MODE_MAP:
69
+ if LOOP_MODE_MAP[key] == loop_mode:
70
+ return key
71
+ return "none"
72
+
73
+
74
+ func _find_animation_players(node: Node, result: Array, root: Node) -> void:
75
+ if node is AnimationPlayer:
76
+ var relative_path := str(root.get_path_to(node))
77
+ result.append({
78
+ "path": relative_path,
79
+ "name": node.name
80
+ })
81
+ for child in node.get_children():
82
+ _find_animation_players(child, result, root)
83
+
84
+
85
+ func list_animation_players(params: Dictionary) -> Dictionary:
86
+ var root_path: String = params.get("root_path", "")
87
+ var root: Node
88
+
89
+ if root_path.is_empty():
90
+ root = EditorInterface.get_edited_scene_root()
91
+ else:
92
+ root = _get_node(root_path)
93
+
94
+ if not root:
95
+ return _error("NODE_NOT_FOUND", "Root node not found")
96
+
97
+ var players := []
98
+ _find_animation_players(root, players, root)
99
+
100
+ return _success({"animation_players": players})
101
+
102
+
103
+ func get_animation_player_info(params: Dictionary) -> Dictionary:
104
+ var node_path: String = params.get("node_path", "")
105
+ if node_path.is_empty():
106
+ return _error("INVALID_PARAMS", "node_path is required")
107
+
108
+ var player := _get_animation_player(node_path)
109
+ if not player:
110
+ var node := _get_node(node_path)
111
+ if not node:
112
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
113
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer: %s" % node_path)
114
+
115
+ var libraries := {}
116
+ for lib_name in player.get_animation_library_list():
117
+ var lib := player.get_animation_library(lib_name)
118
+ libraries[lib_name] = Array(lib.get_animation_list())
119
+
120
+ return _success({
121
+ "current_animation": player.current_animation,
122
+ "is_playing": player.is_playing(),
123
+ "current_position": player.current_animation_position,
124
+ "speed_scale": player.speed_scale,
125
+ "libraries": libraries,
126
+ "animation_count": player.get_animation_list().size()
127
+ })
128
+
129
+
130
+ func get_animation_details(params: Dictionary) -> Dictionary:
131
+ var node_path: String = params.get("node_path", "")
132
+ var anim_name: String = params.get("animation_name", "")
133
+
134
+ if node_path.is_empty():
135
+ return _error("INVALID_PARAMS", "node_path is required")
136
+ if anim_name.is_empty():
137
+ return _error("INVALID_PARAMS", "animation_name is required")
138
+
139
+ var player := _get_animation_player(node_path)
140
+ if not player:
141
+ var node := _get_node(node_path)
142
+ if not node:
143
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
144
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
145
+
146
+ var anim := _get_animation(player, anim_name)
147
+ if not anim:
148
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
149
+
150
+ var tracks := []
151
+ for i in range(anim.get_track_count()):
152
+ tracks.append({
153
+ "index": i,
154
+ "type": _track_type_to_string(anim.track_get_type(i)),
155
+ "path": str(anim.track_get_path(i)),
156
+ "interpolation": anim.track_get_interpolation_type(i),
157
+ "keyframe_count": anim.track_get_key_count(i)
158
+ })
159
+
160
+ var lib_name := ""
161
+ var pure_name := anim_name
162
+ if "/" in anim_name:
163
+ var parts := anim_name.split("/", true, 1)
164
+ lib_name = parts[0]
165
+ pure_name = parts[1]
166
+
167
+ return _success({
168
+ "name": pure_name,
169
+ "library": lib_name,
170
+ "length": anim.length,
171
+ "loop_mode": _loop_mode_to_string(anim.loop_mode),
172
+ "step": anim.step,
173
+ "track_count": anim.get_track_count(),
174
+ "tracks": tracks
175
+ })
176
+
177
+
178
+ func get_track_keyframes(params: Dictionary) -> Dictionary:
179
+ var node_path: String = params.get("node_path", "")
180
+ var anim_name: String = params.get("animation_name", "")
181
+ var track_index: int = params.get("track_index", -1)
182
+
183
+ if node_path.is_empty():
184
+ return _error("INVALID_PARAMS", "node_path is required")
185
+ if anim_name.is_empty():
186
+ return _error("INVALID_PARAMS", "animation_name is required")
187
+ if track_index < 0:
188
+ return _error("INVALID_PARAMS", "track_index is required")
189
+
190
+ var player := _get_animation_player(node_path)
191
+ if not player:
192
+ var node := _get_node(node_path)
193
+ if not node:
194
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
195
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
196
+
197
+ var anim := _get_animation(player, anim_name)
198
+ if not anim:
199
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
200
+
201
+ if track_index >= anim.get_track_count():
202
+ return _error("TRACK_NOT_FOUND", "Track index out of range: %d" % track_index)
203
+
204
+ var keyframes := []
205
+ var track_type := anim.track_get_type(track_index)
206
+
207
+ for i in range(anim.track_get_key_count(track_index)):
208
+ var kf := {
209
+ "time": anim.track_get_key_time(track_index, i),
210
+ "transition": anim.track_get_key_transition(track_index, i)
211
+ }
212
+
213
+ match track_type:
214
+ Animation.TYPE_METHOD:
215
+ kf["method"] = anim.method_track_get_name(track_index, i)
216
+ kf["args"] = anim.method_track_get_params(track_index, i)
217
+ Animation.TYPE_BEZIER:
218
+ kf["value"] = anim.bezier_track_get_key_value(track_index, i)
219
+ kf["in_handle"] = _serialize_value(anim.bezier_track_get_key_in_handle(track_index, i))
220
+ kf["out_handle"] = _serialize_value(anim.bezier_track_get_key_out_handle(track_index, i))
221
+ _:
222
+ kf["value"] = _serialize_value(anim.track_get_key_value(track_index, i))
223
+
224
+ keyframes.append(kf)
225
+
226
+ return _success({
227
+ "track_path": str(anim.track_get_path(track_index)),
228
+ "track_type": _track_type_to_string(track_type),
229
+ "keyframes": keyframes
230
+ })
231
+
232
+
233
+ func play_animation(params: Dictionary) -> Dictionary:
234
+ var node_path: String = params.get("node_path", "")
235
+ var anim_name: String = params.get("animation_name", "")
236
+ var custom_blend: float = params.get("custom_blend", -1.0)
237
+ var custom_speed: float = params.get("custom_speed", 1.0)
238
+ var from_end: bool = params.get("from_end", false)
239
+
240
+ if node_path.is_empty():
241
+ return _error("INVALID_PARAMS", "node_path is required")
242
+ if anim_name.is_empty():
243
+ return _error("INVALID_PARAMS", "animation_name is required")
244
+
245
+ var player := _get_animation_player(node_path)
246
+ if not player:
247
+ var node := _get_node(node_path)
248
+ if not node:
249
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
250
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
251
+
252
+ if not player.has_animation(anim_name):
253
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
254
+
255
+ player.play(anim_name, custom_blend, custom_speed, from_end)
256
+
257
+ return _success({"playing": anim_name, "from_position": player.current_animation_position})
258
+
259
+
260
+ func stop_animation(params: Dictionary) -> Dictionary:
261
+ var node_path: String = params.get("node_path", "")
262
+ var keep_state: bool = params.get("keep_state", false)
263
+
264
+ if node_path.is_empty():
265
+ return _error("INVALID_PARAMS", "node_path is required")
266
+
267
+ var player := _get_animation_player(node_path)
268
+ if not player:
269
+ var node := _get_node(node_path)
270
+ if not node:
271
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
272
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
273
+
274
+ player.stop(keep_state)
275
+
276
+ return _success({"stopped": true})
277
+
278
+
279
+ func seek_animation(params: Dictionary) -> Dictionary:
280
+ var node_path: String = params.get("node_path", "")
281
+ var seconds: float = params.get("seconds", 0.0)
282
+ var update: bool = params.get("update", true)
283
+
284
+ if node_path.is_empty():
285
+ return _error("INVALID_PARAMS", "node_path is required")
286
+ if not params.has("seconds"):
287
+ return _error("INVALID_PARAMS", "seconds is required")
288
+
289
+ var player := _get_animation_player(node_path)
290
+ if not player:
291
+ var node := _get_node(node_path)
292
+ if not node:
293
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
294
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
295
+
296
+ player.seek(seconds, update)
297
+
298
+ return _success({"position": player.current_animation_position})
299
+
300
+
301
+ func create_animation(params: Dictionary) -> Dictionary:
302
+ var node_path: String = params.get("node_path", "")
303
+ var anim_name: String = params.get("animation_name", "")
304
+ var lib_name: String = params.get("library_name", "")
305
+ var length: float = params.get("length", 1.0)
306
+ var loop_mode: String = params.get("loop_mode", "none")
307
+ var step: float = params.get("step", 0.1)
308
+
309
+ if node_path.is_empty():
310
+ return _error("INVALID_PARAMS", "node_path is required")
311
+ if anim_name.is_empty():
312
+ return _error("INVALID_PARAMS", "animation_name is required")
313
+
314
+ var player := _get_animation_player(node_path)
315
+ if not player:
316
+ var node := _get_node(node_path)
317
+ if not node:
318
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
319
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
320
+
321
+ var lib: AnimationLibrary
322
+ if player.has_animation_library(lib_name):
323
+ lib = player.get_animation_library(lib_name)
324
+ else:
325
+ lib = AnimationLibrary.new()
326
+ player.add_animation_library(lib_name, lib)
327
+
328
+ if lib.has_animation(anim_name):
329
+ return _error("ANIMATION_EXISTS", "Animation already exists: %s" % anim_name)
330
+
331
+ var anim := Animation.new()
332
+ anim.length = length
333
+ if LOOP_MODE_MAP.has(loop_mode):
334
+ anim.loop_mode = LOOP_MODE_MAP[loop_mode]
335
+ anim.step = step
336
+
337
+ var err := lib.add_animation(anim_name, anim)
338
+ if err != OK:
339
+ return _error("CREATE_FAILED", "Failed to create animation: %s" % error_string(err))
340
+
341
+ return _success({"created": anim_name, "library": lib_name})
342
+
343
+
344
+ func delete_animation(params: Dictionary) -> Dictionary:
345
+ var node_path: String = params.get("node_path", "")
346
+ var anim_name: String = params.get("animation_name", "")
347
+ var lib_name: String = params.get("library_name", "")
348
+
349
+ if node_path.is_empty():
350
+ return _error("INVALID_PARAMS", "node_path is required")
351
+ if anim_name.is_empty():
352
+ return _error("INVALID_PARAMS", "animation_name is required")
353
+
354
+ var player := _get_animation_player(node_path)
355
+ if not player:
356
+ var node := _get_node(node_path)
357
+ if not node:
358
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
359
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
360
+
361
+ if not player.has_animation_library(lib_name):
362
+ return _error("LIBRARY_NOT_FOUND", "Animation library not found: %s" % lib_name)
363
+
364
+ var lib := player.get_animation_library(lib_name)
365
+ if not lib.has_animation(anim_name):
366
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
367
+
368
+ lib.remove_animation(anim_name)
369
+
370
+ return _success({"deleted": anim_name})
371
+
372
+
373
+ func update_animation_properties(params: Dictionary) -> Dictionary:
374
+ var node_path: String = params.get("node_path", "")
375
+ var anim_name: String = params.get("animation_name", "")
376
+
377
+ if node_path.is_empty():
378
+ return _error("INVALID_PARAMS", "node_path is required")
379
+ if anim_name.is_empty():
380
+ return _error("INVALID_PARAMS", "animation_name is required")
381
+
382
+ var player := _get_animation_player(node_path)
383
+ if not player:
384
+ var node := _get_node(node_path)
385
+ if not node:
386
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
387
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
388
+
389
+ var anim := _get_animation(player, anim_name)
390
+ if not anim:
391
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
392
+
393
+ var updated := {}
394
+
395
+ if params.has("length"):
396
+ anim.length = params["length"]
397
+ updated["length"] = anim.length
398
+
399
+ if params.has("loop_mode"):
400
+ var loop_str: String = params["loop_mode"]
401
+ if LOOP_MODE_MAP.has(loop_str):
402
+ anim.loop_mode = LOOP_MODE_MAP[loop_str]
403
+ updated["loop_mode"] = loop_str
404
+
405
+ if params.has("step"):
406
+ anim.step = params["step"]
407
+ updated["step"] = anim.step
408
+
409
+ return _success({"updated": anim_name, "properties": updated})
410
+
411
+
412
+ func add_animation_track(params: Dictionary) -> Dictionary:
413
+ var node_path: String = params.get("node_path", "")
414
+ var anim_name: String = params.get("animation_name", "")
415
+ var track_type: String = params.get("track_type", "")
416
+ var track_path: String = params.get("track_path", "")
417
+ var insert_at: int = params.get("insert_at", -1)
418
+
419
+ if node_path.is_empty():
420
+ return _error("INVALID_PARAMS", "node_path is required")
421
+ if anim_name.is_empty():
422
+ return _error("INVALID_PARAMS", "animation_name is required")
423
+ if track_type.is_empty():
424
+ return _error("INVALID_PARAMS", "track_type is required")
425
+ if track_path.is_empty():
426
+ return _error("INVALID_PARAMS", "track_path is required")
427
+
428
+ if not TRACK_TYPE_MAP.has(track_type):
429
+ return _error("INVALID_TRACK_TYPE", "Invalid track type: %s. Valid types: %s" % [track_type, ", ".join(TRACK_TYPE_MAP.keys())])
430
+
431
+ var player := _get_animation_player(node_path)
432
+ if not player:
433
+ var node := _get_node(node_path)
434
+ if not node:
435
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
436
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
437
+
438
+ var anim := _get_animation(player, anim_name)
439
+ if not anim:
440
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
441
+
442
+ var godot_track_type: int = TRACK_TYPE_MAP[track_type]
443
+ var track_index: int
444
+
445
+ if insert_at >= 0:
446
+ track_index = anim.add_track(godot_track_type, insert_at)
447
+ else:
448
+ track_index = anim.add_track(godot_track_type)
449
+
450
+ anim.track_set_path(track_index, track_path)
451
+
452
+ return _success({
453
+ "track_index": track_index,
454
+ "track_path": track_path,
455
+ "track_type": track_type
456
+ })
457
+
458
+
459
+ func remove_animation_track(params: Dictionary) -> Dictionary:
460
+ var node_path: String = params.get("node_path", "")
461
+ var anim_name: String = params.get("animation_name", "")
462
+ var track_index: int = params.get("track_index", -1)
463
+
464
+ if node_path.is_empty():
465
+ return _error("INVALID_PARAMS", "node_path is required")
466
+ if anim_name.is_empty():
467
+ return _error("INVALID_PARAMS", "animation_name is required")
468
+ if track_index < 0:
469
+ return _error("INVALID_PARAMS", "track_index is required")
470
+
471
+ var player := _get_animation_player(node_path)
472
+ if not player:
473
+ var node := _get_node(node_path)
474
+ if not node:
475
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
476
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
477
+
478
+ var anim := _get_animation(player, anim_name)
479
+ if not anim:
480
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
481
+
482
+ if track_index >= anim.get_track_count():
483
+ return _error("TRACK_NOT_FOUND", "Track index out of range: %d" % track_index)
484
+
485
+ anim.remove_track(track_index)
486
+
487
+ return _success({"removed_track": track_index})
488
+
489
+
490
+ func add_keyframe(params: Dictionary) -> Dictionary:
491
+ var node_path: String = params.get("node_path", "")
492
+ var anim_name: String = params.get("animation_name", "")
493
+ var track_index: int = params.get("track_index", -1)
494
+ var time: float = params.get("time", 0.0)
495
+ var transition: float = params.get("transition", 1.0)
496
+
497
+ if node_path.is_empty():
498
+ return _error("INVALID_PARAMS", "node_path is required")
499
+ if anim_name.is_empty():
500
+ return _error("INVALID_PARAMS", "animation_name is required")
501
+ if track_index < 0:
502
+ return _error("INVALID_PARAMS", "track_index is required")
503
+ if not params.has("time"):
504
+ return _error("INVALID_PARAMS", "time is required")
505
+ if not params.has("value"):
506
+ return _error("INVALID_PARAMS", "value is required")
507
+
508
+ var player := _get_animation_player(node_path)
509
+ if not player:
510
+ var node := _get_node(node_path)
511
+ if not node:
512
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
513
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
514
+
515
+ var anim := _get_animation(player, anim_name)
516
+ if not anim:
517
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
518
+
519
+ if track_index >= anim.get_track_count():
520
+ return _error("TRACK_NOT_FOUND", "Track index out of range: %d" % track_index)
521
+
522
+ var value = MCPUtils.deserialize_value(params["value"])
523
+ var track_type := anim.track_get_type(track_index)
524
+ var key_index: int
525
+
526
+ match track_type:
527
+ Animation.TYPE_BEZIER:
528
+ key_index = anim.bezier_track_insert_key(track_index, time, value)
529
+ Animation.TYPE_METHOD:
530
+ var method_name: String = params.get("method_name", "")
531
+ var args: Array = params.get("args", [])
532
+ key_index = anim.method_track_add_key(track_index, time, method_name, args)
533
+ _:
534
+ key_index = anim.track_insert_key(track_index, time, value, transition)
535
+
536
+ return _success({
537
+ "keyframe_index": key_index,
538
+ "time": time,
539
+ "value": _serialize_value(value)
540
+ })
541
+
542
+
543
+ func remove_keyframe(params: Dictionary) -> Dictionary:
544
+ var node_path: String = params.get("node_path", "")
545
+ var anim_name: String = params.get("animation_name", "")
546
+ var track_index: int = params.get("track_index", -1)
547
+ var keyframe_index: int = params.get("keyframe_index", -1)
548
+
549
+ if node_path.is_empty():
550
+ return _error("INVALID_PARAMS", "node_path is required")
551
+ if anim_name.is_empty():
552
+ return _error("INVALID_PARAMS", "animation_name is required")
553
+ if track_index < 0:
554
+ return _error("INVALID_PARAMS", "track_index is required")
555
+ if keyframe_index < 0:
556
+ return _error("INVALID_PARAMS", "keyframe_index is required")
557
+
558
+ var player := _get_animation_player(node_path)
559
+ if not player:
560
+ var node := _get_node(node_path)
561
+ if not node:
562
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
563
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
564
+
565
+ var anim := _get_animation(player, anim_name)
566
+ if not anim:
567
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
568
+
569
+ if track_index >= anim.get_track_count():
570
+ return _error("TRACK_NOT_FOUND", "Track index out of range: %d" % track_index)
571
+
572
+ if keyframe_index >= anim.track_get_key_count(track_index):
573
+ return _error("KEYFRAME_NOT_FOUND", "Keyframe index out of range: %d" % keyframe_index)
574
+
575
+ anim.track_remove_key(track_index, keyframe_index)
576
+
577
+ return _success({"removed_keyframe": keyframe_index, "track_index": track_index})
578
+
579
+
580
+ func update_keyframe(params: Dictionary) -> Dictionary:
581
+ var node_path: String = params.get("node_path", "")
582
+ var anim_name: String = params.get("animation_name", "")
583
+ var track_index: int = params.get("track_index", -1)
584
+ var keyframe_index: int = params.get("keyframe_index", -1)
585
+
586
+ if node_path.is_empty():
587
+ return _error("INVALID_PARAMS", "node_path is required")
588
+ if anim_name.is_empty():
589
+ return _error("INVALID_PARAMS", "animation_name is required")
590
+ if track_index < 0:
591
+ return _error("INVALID_PARAMS", "track_index is required")
592
+ if keyframe_index < 0:
593
+ return _error("INVALID_PARAMS", "keyframe_index is required")
594
+
595
+ var player := _get_animation_player(node_path)
596
+ if not player:
597
+ var node := _get_node(node_path)
598
+ if not node:
599
+ return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
600
+ return _error("NOT_ANIMATION_PLAYER", "Node is not an AnimationPlayer")
601
+
602
+ var anim := _get_animation(player, anim_name)
603
+ if not anim:
604
+ return _error("ANIMATION_NOT_FOUND", "Animation not found: %s" % anim_name)
605
+
606
+ if track_index >= anim.get_track_count():
607
+ return _error("TRACK_NOT_FOUND", "Track index out of range: %d" % track_index)
608
+
609
+ if keyframe_index >= anim.track_get_key_count(track_index):
610
+ return _error("KEYFRAME_NOT_FOUND", "Keyframe index out of range: %d" % keyframe_index)
611
+
612
+ var result := {}
613
+
614
+ if params.has("time"):
615
+ var new_time: float = params["time"]
616
+ var old_value = anim.track_get_key_value(track_index, keyframe_index)
617
+ var old_transition := anim.track_get_key_transition(track_index, keyframe_index)
618
+ anim.track_remove_key(track_index, keyframe_index)
619
+ keyframe_index = anim.track_insert_key(track_index, new_time, old_value, old_transition)
620
+ result["time"] = new_time
621
+ result["keyframe_index"] = keyframe_index
622
+
623
+ if params.has("value"):
624
+ var new_value = MCPUtils.deserialize_value(params["value"])
625
+ anim.track_set_key_value(track_index, keyframe_index, new_value)
626
+ result["value"] = _serialize_value(new_value)
627
+
628
+ if params.has("transition"):
629
+ var new_transition: float = params["transition"]
630
+ anim.track_set_key_transition(track_index, keyframe_index, new_transition)
631
+ result["transition"] = new_transition
632
+
633
+ return _success({"updated_keyframe": keyframe_index, "changes": result})
@@ -0,0 +1 @@
1
+ uid://bp2p0xfd1tm1h