gopeak 2.1.0 → 2.2.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.
- package/README.md +224 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -0
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -0
- package/build/addon/godot_mcp_editor/plugin.gd +84 -0
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -0
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -0
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -0
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +710 -0
- package/build/cli/check.js +77 -0
- package/build/cli/notify.js +88 -0
- package/build/cli/setup.js +115 -0
- package/build/cli/star.js +51 -0
- package/build/cli/uninstall.js +26 -0
- package/build/cli/utils.js +149 -0
- package/build/cli.js +91 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +556 -0
- package/build/index.js +2761 -2064
- package/build/prompts.js +163 -0
- package/build/visualizer/canvas.js +832 -0
- package/build/visualizer/events.js +814 -0
- package/build/visualizer/layout.js +304 -0
- package/build/visualizer/main.js +245 -0
- package/build/visualizer/modals.js +239 -0
- package/build/visualizer/panel.js +1091 -0
- package/build/visualizer/state.js +210 -0
- package/build/visualizer/syntax.js +106 -0
- package/build/visualizer/usages.js +352 -0
- package/build/visualizer/websocket.js +85 -0
- package/build/visualizer-server.js +375 -0
- package/build/visualizer.html +6395 -0
- package/package.json +15 -6
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Node
|
|
3
|
+
class_name MCPToolExecutor
|
|
4
|
+
|
|
5
|
+
var _editor_plugin: EditorPlugin = null
|
|
6
|
+
|
|
7
|
+
var _scene_tools: Node = null
|
|
8
|
+
var _resource_tools: Node = null
|
|
9
|
+
var _animation_tools: Node = null
|
|
10
|
+
|
|
11
|
+
var _tool_map: Dictionary = {}
|
|
12
|
+
var _initialized := false
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
16
|
+
_editor_plugin = plugin
|
|
17
|
+
_init_tools()
|
|
18
|
+
|
|
19
|
+
if _scene_tools and _scene_tools.has_method("set_editor_plugin"):
|
|
20
|
+
_scene_tools.set_editor_plugin(plugin)
|
|
21
|
+
if _resource_tools and _resource_tools.has_method("set_editor_plugin"):
|
|
22
|
+
_resource_tools.set_editor_plugin(plugin)
|
|
23
|
+
if _animation_tools and _animation_tools.has_method("set_editor_plugin"):
|
|
24
|
+
_animation_tools.set_editor_plugin(plugin)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func _init_tools() -> void:
|
|
28
|
+
if _initialized:
|
|
29
|
+
return
|
|
30
|
+
_initialized = true
|
|
31
|
+
|
|
32
|
+
var base_path: String = get_script().resource_path.get_base_dir()
|
|
33
|
+
var scene_tools_path := "%s/tools/scene_tools.gd" % base_path
|
|
34
|
+
var resource_tools_path := "%s/tools/resource_tools.gd" % base_path
|
|
35
|
+
var animation_tools_path := "%s/tools/animation_tools.gd" % base_path
|
|
36
|
+
|
|
37
|
+
if ResourceLoader.exists(scene_tools_path):
|
|
38
|
+
var scene_script: Script = load(scene_tools_path)
|
|
39
|
+
if scene_script:
|
|
40
|
+
_scene_tools = scene_script.new()
|
|
41
|
+
_scene_tools.name = "SceneTools"
|
|
42
|
+
add_child(_scene_tools)
|
|
43
|
+
|
|
44
|
+
if ResourceLoader.exists(resource_tools_path):
|
|
45
|
+
var resource_script: Script = load(resource_tools_path)
|
|
46
|
+
if resource_script:
|
|
47
|
+
_resource_tools = resource_script.new()
|
|
48
|
+
_resource_tools.name = "ResourceTools"
|
|
49
|
+
add_child(_resource_tools)
|
|
50
|
+
|
|
51
|
+
if ResourceLoader.exists(animation_tools_path):
|
|
52
|
+
var animation_script: Script = load(animation_tools_path)
|
|
53
|
+
if animation_script:
|
|
54
|
+
_animation_tools = animation_script.new()
|
|
55
|
+
_animation_tools.name = "AnimationTools"
|
|
56
|
+
add_child(_animation_tools)
|
|
57
|
+
|
|
58
|
+
_tool_map = {
|
|
59
|
+
# Scene tools
|
|
60
|
+
"create_scene": [_scene_tools, "create_scene"],
|
|
61
|
+
"list_scene_nodes": [_scene_tools, "list_scene_nodes"],
|
|
62
|
+
"add_node": [_scene_tools, "add_node"],
|
|
63
|
+
"delete_node": [_scene_tools, "delete_node"],
|
|
64
|
+
"duplicate_node": [_scene_tools, "duplicate_node"],
|
|
65
|
+
"reparent_node": [_scene_tools, "reparent_node"],
|
|
66
|
+
"set_node_properties": [_scene_tools, "set_node_properties"],
|
|
67
|
+
"get_node_properties": [_scene_tools, "get_node_properties"],
|
|
68
|
+
"load_sprite": [_scene_tools, "load_sprite"],
|
|
69
|
+
"save_scene": [_scene_tools, "save_scene"],
|
|
70
|
+
"connect_signal": [_scene_tools, "connect_signal"],
|
|
71
|
+
"disconnect_signal": [_scene_tools, "disconnect_signal"],
|
|
72
|
+
"list_connections": [_scene_tools, "list_connections"],
|
|
73
|
+
|
|
74
|
+
# Resource tools
|
|
75
|
+
"create_resource": [_resource_tools, "create_resource"],
|
|
76
|
+
"modify_resource": [_resource_tools, "modify_resource"],
|
|
77
|
+
"create_material": [_resource_tools, "create_material"],
|
|
78
|
+
"create_shader": [_resource_tools, "create_shader"],
|
|
79
|
+
"create_tileset": [_resource_tools, "create_tileset"],
|
|
80
|
+
"set_tilemap_cells": [_resource_tools, "set_tilemap_cells"],
|
|
81
|
+
"set_theme_color": [_resource_tools, "set_theme_color"],
|
|
82
|
+
"set_theme_font_size": [_resource_tools, "set_theme_font_size"],
|
|
83
|
+
"apply_theme_shader": [_resource_tools, "apply_theme_shader"],
|
|
84
|
+
|
|
85
|
+
# Animation tools
|
|
86
|
+
"create_animation": [_animation_tools, "create_animation"],
|
|
87
|
+
"add_animation_track": [_animation_tools, "add_animation_track"],
|
|
88
|
+
"create_animation_tree": [_animation_tools, "create_animation_tree"],
|
|
89
|
+
"add_animation_state": [_animation_tools, "add_animation_state"],
|
|
90
|
+
"connect_animation_states": [_animation_tools, "connect_animation_states"],
|
|
91
|
+
"create_navigation_region": [_animation_tools, "create_navigation_region"],
|
|
92
|
+
"create_navigation_agent": [_animation_tools, "create_navigation_agent"]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
func execute_tool(tool_name: String, args: Dictionary) -> Dictionary:
|
|
97
|
+
if not _tool_map.has(tool_name):
|
|
98
|
+
return {"ok": false, "error": "Unknown tool: " + tool_name}
|
|
99
|
+
|
|
100
|
+
var handler: Array = _tool_map[tool_name]
|
|
101
|
+
var node: Node = handler[0]
|
|
102
|
+
var method: String = handler[1]
|
|
103
|
+
|
|
104
|
+
if node == null:
|
|
105
|
+
return {"ok": false, "error": "Tool handler unavailable: " + tool_name}
|
|
106
|
+
|
|
107
|
+
if not node.has_method(method):
|
|
108
|
+
return {"ok": false, "error": "Tool method not found: %s.%s" % [node.name, method]}
|
|
109
|
+
|
|
110
|
+
var result = node.call(method, args)
|
|
111
|
+
if result is Dictionary:
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
return {"ok": false, "error": "Invalid tool result from: " + tool_name}
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Node
|
|
3
|
+
class_name MCPAnimationTools
|
|
4
|
+
|
|
5
|
+
var _editor_plugin: EditorPlugin = null
|
|
6
|
+
|
|
7
|
+
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
8
|
+
_editor_plugin = plugin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Shared helpers
|
|
13
|
+
# =============================================================================
|
|
14
|
+
func _ensure_res_path(path: String) -> String:
|
|
15
|
+
if not path.begins_with("res://"): return "res://" + path
|
|
16
|
+
return path
|
|
17
|
+
|
|
18
|
+
func _refresh_and_reload(scene_path: String) -> void:
|
|
19
|
+
_refresh_filesystem()
|
|
20
|
+
_reload_scene_in_editor(scene_path)
|
|
21
|
+
|
|
22
|
+
func _refresh_filesystem() -> void:
|
|
23
|
+
if _editor_plugin:
|
|
24
|
+
EditorInterface.get_resource_filesystem().scan()
|
|
25
|
+
|
|
26
|
+
func _reload_scene_in_editor(scene_path: String) -> void:
|
|
27
|
+
if not _editor_plugin: return
|
|
28
|
+
var edited = EditorInterface.get_edited_scene_root()
|
|
29
|
+
if edited and edited.scene_file_path == scene_path:
|
|
30
|
+
EditorInterface.reload_scene_from_path(scene_path)
|
|
31
|
+
|
|
32
|
+
func _load_scene(scene_path: String) -> Array:
|
|
33
|
+
if not FileAccess.file_exists(scene_path):
|
|
34
|
+
return [null, {"ok": false, "error": "Scene not found: " + scene_path}]
|
|
35
|
+
var packed = load(scene_path) as PackedScene
|
|
36
|
+
if not packed: return [null, {"ok": false, "error": "Failed to load: " + scene_path}]
|
|
37
|
+
var root = packed.instantiate()
|
|
38
|
+
if not root: return [null, {"ok": false, "error": "Failed to instantiate: " + scene_path}]
|
|
39
|
+
return [root, {}]
|
|
40
|
+
|
|
41
|
+
func _save_scene(scene_root: Node, scene_path: String) -> Dictionary:
|
|
42
|
+
var packed = PackedScene.new()
|
|
43
|
+
if packed.pack(scene_root) != OK:
|
|
44
|
+
scene_root.queue_free()
|
|
45
|
+
return {"ok": false, "error": "Failed to pack scene"}
|
|
46
|
+
if ResourceSaver.save(packed, scene_path) != OK:
|
|
47
|
+
scene_root.queue_free()
|
|
48
|
+
return {"ok": false, "error": "Failed to save scene"}
|
|
49
|
+
scene_root.queue_free()
|
|
50
|
+
_refresh_and_reload(scene_path)
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
func _find_node(root: Node, path: String) -> Node:
|
|
54
|
+
if path == "." or path.is_empty(): return root
|
|
55
|
+
return root.get_node_or_null(path)
|
|
56
|
+
|
|
57
|
+
func _parse_value(value):
|
|
58
|
+
if typeof(value) == TYPE_DICTIONARY:
|
|
59
|
+
if value.has("type") or value.has("_type"):
|
|
60
|
+
var t = value.get("type", value.get("_type", ""))
|
|
61
|
+
match t:
|
|
62
|
+
"Vector2": return Vector2(value.get("x",0), value.get("y",0))
|
|
63
|
+
"Vector3": return Vector3(value.get("x",0), value.get("y",0), value.get("z",0))
|
|
64
|
+
"Color": return Color(value.get("r",1), value.get("g",1), value.get("b",1), value.get("a",1))
|
|
65
|
+
if typeof(value) == TYPE_ARRAY:
|
|
66
|
+
var result = []
|
|
67
|
+
for item in value:
|
|
68
|
+
result.append(_parse_value(item))
|
|
69
|
+
return result
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
func _parse_json_maybe(value):
|
|
73
|
+
if typeof(value) != TYPE_STRING:
|
|
74
|
+
return value
|
|
75
|
+
var parsed = JSON.parse_string(value)
|
|
76
|
+
if parsed == null and value != "null":
|
|
77
|
+
return value
|
|
78
|
+
return parsed
|
|
79
|
+
|
|
80
|
+
func _parse_method_args(raw_args: Array) -> Array:
|
|
81
|
+
var parsed_args: Array = []
|
|
82
|
+
for raw_arg in raw_args:
|
|
83
|
+
var parsed = _parse_json_maybe(raw_arg)
|
|
84
|
+
parsed_args.append(_parse_value(parsed))
|
|
85
|
+
return parsed_args
|
|
86
|
+
|
|
87
|
+
func _get_default_animation_library(player: AnimationPlayer) -> AnimationLibrary:
|
|
88
|
+
var anim_lib: AnimationLibrary = player.get_animation_library("")
|
|
89
|
+
if anim_lib:
|
|
90
|
+
return anim_lib
|
|
91
|
+
anim_lib = AnimationLibrary.new()
|
|
92
|
+
var add_lib_err := player.add_animation_library("", anim_lib)
|
|
93
|
+
if add_lib_err != OK:
|
|
94
|
+
return null
|
|
95
|
+
return anim_lib
|
|
96
|
+
|
|
97
|
+
func _get_state_machine(anim_tree: AnimationTree, state_machine_path: String = "") -> AnimationNodeStateMachine:
|
|
98
|
+
if not anim_tree:
|
|
99
|
+
return null
|
|
100
|
+
if state_machine_path.is_empty() or state_machine_path == "root":
|
|
101
|
+
return anim_tree.tree_root as AnimationNodeStateMachine
|
|
102
|
+
|
|
103
|
+
var current = anim_tree.tree_root
|
|
104
|
+
for segment in state_machine_path.split("/", false):
|
|
105
|
+
if String(segment).is_empty():
|
|
106
|
+
continue
|
|
107
|
+
if current == null or not current.has_method("get_node"):
|
|
108
|
+
return null
|
|
109
|
+
current = current.call("get_node", StringName(segment))
|
|
110
|
+
return current as AnimationNodeStateMachine
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# =============================================================================
|
|
114
|
+
# create_animation
|
|
115
|
+
# =============================================================================
|
|
116
|
+
func create_animation(args: Dictionary) -> Dictionary:
|
|
117
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
118
|
+
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
119
|
+
var animation_name: String = str(args.get("animationName", ""))
|
|
120
|
+
var loop_mode_name: String = str(args.get("loopMode", "none"))
|
|
121
|
+
|
|
122
|
+
if scene_path.strip_edges() == "res://":
|
|
123
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
124
|
+
if animation_name.strip_edges().is_empty():
|
|
125
|
+
return {"ok": false, "error": "Missing animationName"}
|
|
126
|
+
|
|
127
|
+
var loaded := _load_scene(scene_path)
|
|
128
|
+
if not loaded[1].is_empty():
|
|
129
|
+
return loaded[1]
|
|
130
|
+
|
|
131
|
+
var scene_root: Node = loaded[0]
|
|
132
|
+
var player = _find_node(scene_root, player_node_path) as AnimationPlayer
|
|
133
|
+
if not player:
|
|
134
|
+
scene_root.queue_free()
|
|
135
|
+
return {"ok": false, "error": "AnimationPlayer not found at: " + player_node_path}
|
|
136
|
+
|
|
137
|
+
var anim_lib := _get_default_animation_library(player)
|
|
138
|
+
if not anim_lib:
|
|
139
|
+
scene_root.queue_free()
|
|
140
|
+
return {"ok": false, "error": "Failed to create default AnimationLibrary"}
|
|
141
|
+
if anim_lib.has_animation(StringName(animation_name)):
|
|
142
|
+
scene_root.queue_free()
|
|
143
|
+
return {"ok": false, "error": "Animation already exists: " + animation_name}
|
|
144
|
+
|
|
145
|
+
var loop_mode := Animation.LOOP_NONE
|
|
146
|
+
match loop_mode_name:
|
|
147
|
+
"linear": loop_mode = Animation.LOOP_LINEAR
|
|
148
|
+
"pingpong": loop_mode = Animation.LOOP_PINGPONG
|
|
149
|
+
_:
|
|
150
|
+
loop_mode_name = "none"
|
|
151
|
+
loop_mode = Animation.LOOP_NONE
|
|
152
|
+
|
|
153
|
+
var anim = Animation.new()
|
|
154
|
+
anim.length = float(args.get("length", 1.0))
|
|
155
|
+
anim.loop_mode = loop_mode
|
|
156
|
+
anim.step = float(args.get("step", 0.1))
|
|
157
|
+
|
|
158
|
+
var add_err := anim_lib.add_animation(StringName(animation_name), anim)
|
|
159
|
+
if add_err != OK:
|
|
160
|
+
scene_root.queue_free()
|
|
161
|
+
return {"ok": false, "error": "Failed to add animation: " + str(add_err)}
|
|
162
|
+
|
|
163
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
164
|
+
if not save_err.is_empty():
|
|
165
|
+
return save_err
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
"ok": true,
|
|
169
|
+
"animationName": animation_name,
|
|
170
|
+
"length": anim.length,
|
|
171
|
+
"loopMode": loop_mode_name,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# =============================================================================
|
|
176
|
+
# add_animation_track
|
|
177
|
+
# =============================================================================
|
|
178
|
+
func add_animation_track(args: Dictionary) -> Dictionary:
|
|
179
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
180
|
+
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
181
|
+
var animation_name: String = str(args.get("animationName", ""))
|
|
182
|
+
var track: Dictionary = args.get("track", {})
|
|
183
|
+
|
|
184
|
+
if scene_path.strip_edges() == "res://":
|
|
185
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
186
|
+
if animation_name.strip_edges().is_empty():
|
|
187
|
+
return {"ok": false, "error": "Missing animationName"}
|
|
188
|
+
if track.is_empty():
|
|
189
|
+
return {"ok": false, "error": "Missing track"}
|
|
190
|
+
|
|
191
|
+
var loaded := _load_scene(scene_path)
|
|
192
|
+
if not loaded[1].is_empty():
|
|
193
|
+
return loaded[1]
|
|
194
|
+
|
|
195
|
+
var scene_root: Node = loaded[0]
|
|
196
|
+
var player = _find_node(scene_root, player_node_path) as AnimationPlayer
|
|
197
|
+
if not player:
|
|
198
|
+
scene_root.queue_free()
|
|
199
|
+
return {"ok": false, "error": "AnimationPlayer not found at: " + player_node_path}
|
|
200
|
+
|
|
201
|
+
var anim_lib: AnimationLibrary = player.get_animation_library("")
|
|
202
|
+
if not anim_lib:
|
|
203
|
+
scene_root.queue_free()
|
|
204
|
+
return {"ok": false, "error": "Default AnimationLibrary not found"}
|
|
205
|
+
|
|
206
|
+
var anim: Animation = anim_lib.get_animation(StringName(animation_name))
|
|
207
|
+
if not anim:
|
|
208
|
+
scene_root.queue_free()
|
|
209
|
+
return {"ok": false, "error": "Animation not found: " + animation_name}
|
|
210
|
+
|
|
211
|
+
var track_type: String = str(track.get("type", ""))
|
|
212
|
+
var track_idx := -1
|
|
213
|
+
var keyframes: Array = track.get("keyframes", [])
|
|
214
|
+
|
|
215
|
+
match track_type:
|
|
216
|
+
"property":
|
|
217
|
+
var node_path_str: String = str(track.get("nodePath", ""))
|
|
218
|
+
var prop_name: String = str(track.get("property", ""))
|
|
219
|
+
if prop_name.is_empty():
|
|
220
|
+
scene_root.queue_free()
|
|
221
|
+
return {"ok": false, "error": "track.property is required for property track"}
|
|
222
|
+
track_idx = anim.add_track(Animation.TYPE_VALUE)
|
|
223
|
+
anim.track_set_path(track_idx, NodePath(node_path_str + ":" + prop_name))
|
|
224
|
+
for keyframe in keyframes:
|
|
225
|
+
if typeof(keyframe) != TYPE_DICTIONARY:
|
|
226
|
+
continue
|
|
227
|
+
var raw_value = keyframe.get("value")
|
|
228
|
+
var parsed_value = _parse_json_maybe(raw_value) if typeof(raw_value) == TYPE_STRING else raw_value
|
|
229
|
+
anim.track_insert_key(track_idx, float(keyframe.get("time", 0.0)), _parse_value(parsed_value))
|
|
230
|
+
|
|
231
|
+
"method":
|
|
232
|
+
var method_node_path: String = str(track.get("nodePath", ""))
|
|
233
|
+
var method_name: String = str(track.get("method", ""))
|
|
234
|
+
if method_name.is_empty():
|
|
235
|
+
scene_root.queue_free()
|
|
236
|
+
return {"ok": false, "error": "track.method is required for method track"}
|
|
237
|
+
track_idx = anim.add_track(Animation.TYPE_METHOD)
|
|
238
|
+
anim.track_set_path(track_idx, NodePath(method_node_path))
|
|
239
|
+
for keyframe in keyframes:
|
|
240
|
+
if typeof(keyframe) != TYPE_DICTIONARY:
|
|
241
|
+
continue
|
|
242
|
+
anim.track_insert_key(
|
|
243
|
+
track_idx,
|
|
244
|
+
float(keyframe.get("time", 0.0)),
|
|
245
|
+
{
|
|
246
|
+
"method": method_name,
|
|
247
|
+
"args": _parse_method_args(keyframe.get("args", [])),
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
_:
|
|
252
|
+
scene_root.queue_free()
|
|
253
|
+
return {"ok": false, "error": "Unsupported track.type: " + track_type}
|
|
254
|
+
|
|
255
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
256
|
+
if not save_err.is_empty():
|
|
257
|
+
return save_err
|
|
258
|
+
|
|
259
|
+
return {"ok": true, "trackType": track_type, "trackIndex": track_idx}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# =============================================================================
|
|
263
|
+
# create_animation_tree
|
|
264
|
+
# =============================================================================
|
|
265
|
+
func create_animation_tree(args: Dictionary) -> Dictionary:
|
|
266
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
267
|
+
var parent_path: String = str(args.get("parentPath", "."))
|
|
268
|
+
var node_name: String = str(args.get("nodeName", "AnimationTree"))
|
|
269
|
+
var anim_player_path: String = str(args.get("animPlayerPath", ""))
|
|
270
|
+
var root_type: String = str(args.get("rootType", "StateMachine"))
|
|
271
|
+
|
|
272
|
+
if scene_path.strip_edges() == "res://":
|
|
273
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
274
|
+
if anim_player_path.is_empty():
|
|
275
|
+
return {"ok": false, "error": "Missing animPlayerPath"}
|
|
276
|
+
|
|
277
|
+
var loaded := _load_scene(scene_path)
|
|
278
|
+
if not loaded[1].is_empty():
|
|
279
|
+
return loaded[1]
|
|
280
|
+
|
|
281
|
+
var scene_root: Node = loaded[0]
|
|
282
|
+
var parent = _find_node(scene_root, parent_path)
|
|
283
|
+
if not parent:
|
|
284
|
+
scene_root.queue_free()
|
|
285
|
+
return {"ok": false, "error": "Parent node not found: " + parent_path}
|
|
286
|
+
|
|
287
|
+
var anim_tree := AnimationTree.new()
|
|
288
|
+
anim_tree.name = node_name
|
|
289
|
+
anim_tree.anim_player = NodePath(anim_player_path)
|
|
290
|
+
|
|
291
|
+
var root = null
|
|
292
|
+
match root_type:
|
|
293
|
+
"StateMachine": root = AnimationNodeStateMachine.new()
|
|
294
|
+
"BlendTree": root = AnimationNodeBlendTree.new()
|
|
295
|
+
"BlendSpace1D": root = AnimationNodeBlendSpace1D.new()
|
|
296
|
+
"BlendSpace2D": root = AnimationNodeBlendSpace2D.new()
|
|
297
|
+
_:
|
|
298
|
+
scene_root.queue_free()
|
|
299
|
+
return {"ok": false, "error": "Unsupported rootType: " + root_type}
|
|
300
|
+
|
|
301
|
+
anim_tree.tree_root = root
|
|
302
|
+
parent.add_child(anim_tree)
|
|
303
|
+
anim_tree.owner = scene_root
|
|
304
|
+
|
|
305
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
306
|
+
if not save_err.is_empty():
|
|
307
|
+
return save_err
|
|
308
|
+
|
|
309
|
+
return {"ok": true, "nodeName": node_name, "rootType": root_type}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# =============================================================================
|
|
313
|
+
# add_animation_state
|
|
314
|
+
# =============================================================================
|
|
315
|
+
func add_animation_state(args: Dictionary) -> Dictionary:
|
|
316
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
317
|
+
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
318
|
+
var state_name: String = str(args.get("stateName", ""))
|
|
319
|
+
var animation_name: String = str(args.get("animationName", ""))
|
|
320
|
+
var state_machine_path: String = str(args.get("stateMachinePath", ""))
|
|
321
|
+
|
|
322
|
+
if scene_path.strip_edges() == "res://":
|
|
323
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
324
|
+
if anim_tree_path.is_empty():
|
|
325
|
+
return {"ok": false, "error": "Missing animTreePath"}
|
|
326
|
+
if state_name.is_empty():
|
|
327
|
+
return {"ok": false, "error": "Missing stateName"}
|
|
328
|
+
if animation_name.is_empty():
|
|
329
|
+
return {"ok": false, "error": "Missing animationName"}
|
|
330
|
+
|
|
331
|
+
var loaded := _load_scene(scene_path)
|
|
332
|
+
if not loaded[1].is_empty():
|
|
333
|
+
return loaded[1]
|
|
334
|
+
|
|
335
|
+
var scene_root: Node = loaded[0]
|
|
336
|
+
var anim_tree = _find_node(scene_root, anim_tree_path) as AnimationTree
|
|
337
|
+
if not anim_tree:
|
|
338
|
+
scene_root.queue_free()
|
|
339
|
+
return {"ok": false, "error": "AnimationTree not found at: " + anim_tree_path}
|
|
340
|
+
|
|
341
|
+
var sm := _get_state_machine(anim_tree, state_machine_path)
|
|
342
|
+
if not sm:
|
|
343
|
+
scene_root.queue_free()
|
|
344
|
+
return {"ok": false, "error": "AnimationNodeStateMachine not found"}
|
|
345
|
+
|
|
346
|
+
var anim_node := AnimationNodeAnimation.new()
|
|
347
|
+
anim_node.animation = StringName(animation_name)
|
|
348
|
+
sm.add_node(StringName(state_name), anim_node)
|
|
349
|
+
|
|
350
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
351
|
+
if not save_err.is_empty():
|
|
352
|
+
return save_err
|
|
353
|
+
|
|
354
|
+
return {"ok": true, "stateName": state_name, "animationName": animation_name}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# =============================================================================
|
|
358
|
+
# connect_animation_states
|
|
359
|
+
# =============================================================================
|
|
360
|
+
func connect_animation_states(args: Dictionary) -> Dictionary:
|
|
361
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
362
|
+
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
363
|
+
var from_state: String = str(args.get("fromState", ""))
|
|
364
|
+
var to_state: String = str(args.get("toState", ""))
|
|
365
|
+
var transition_type: String = str(args.get("transitionType", "immediate"))
|
|
366
|
+
var state_machine_path: String = str(args.get("stateMachinePath", ""))
|
|
367
|
+
var advance_condition: String = str(args.get("advanceCondition", ""))
|
|
368
|
+
|
|
369
|
+
if scene_path.strip_edges() == "res://":
|
|
370
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
371
|
+
if anim_tree_path.is_empty():
|
|
372
|
+
return {"ok": false, "error": "Missing animTreePath"}
|
|
373
|
+
if from_state.is_empty() or to_state.is_empty():
|
|
374
|
+
return {"ok": false, "error": "Missing fromState or toState"}
|
|
375
|
+
|
|
376
|
+
var loaded := _load_scene(scene_path)
|
|
377
|
+
if not loaded[1].is_empty():
|
|
378
|
+
return loaded[1]
|
|
379
|
+
|
|
380
|
+
var scene_root: Node = loaded[0]
|
|
381
|
+
var anim_tree = _find_node(scene_root, anim_tree_path) as AnimationTree
|
|
382
|
+
if not anim_tree:
|
|
383
|
+
scene_root.queue_free()
|
|
384
|
+
return {"ok": false, "error": "AnimationTree not found at: " + anim_tree_path}
|
|
385
|
+
|
|
386
|
+
var sm := _get_state_machine(anim_tree, state_machine_path)
|
|
387
|
+
if not sm:
|
|
388
|
+
scene_root.queue_free()
|
|
389
|
+
return {"ok": false, "error": "AnimationNodeStateMachine not found"}
|
|
390
|
+
|
|
391
|
+
var transition := AnimationNodeStateMachineTransition.new()
|
|
392
|
+
match transition_type:
|
|
393
|
+
"sync": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_SYNC
|
|
394
|
+
"at_end": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
|
|
395
|
+
"immediate": transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
|
|
396
|
+
_:
|
|
397
|
+
scene_root.queue_free()
|
|
398
|
+
return {"ok": false, "error": "Unsupported transitionType: " + transition_type}
|
|
399
|
+
|
|
400
|
+
if not advance_condition.is_empty():
|
|
401
|
+
transition.advance_condition = StringName(advance_condition)
|
|
402
|
+
|
|
403
|
+
sm.add_transition(StringName(from_state), StringName(to_state), transition)
|
|
404
|
+
|
|
405
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
406
|
+
if not save_err.is_empty():
|
|
407
|
+
return save_err
|
|
408
|
+
|
|
409
|
+
return {"ok": true, "from": from_state, "to": to_state}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# =============================================================================
|
|
413
|
+
# create_navigation_region
|
|
414
|
+
# =============================================================================
|
|
415
|
+
func create_navigation_region(args: Dictionary) -> Dictionary:
|
|
416
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
417
|
+
var parent_path: String = str(args.get("parentPath", "."))
|
|
418
|
+
var node_name: String = str(args.get("nodeName", "NavigationRegion"))
|
|
419
|
+
var is_3d: bool = bool(args.get("is3D", false))
|
|
420
|
+
|
|
421
|
+
if scene_path.strip_edges() == "res://":
|
|
422
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
423
|
+
|
|
424
|
+
var loaded := _load_scene(scene_path)
|
|
425
|
+
if not loaded[1].is_empty():
|
|
426
|
+
return loaded[1]
|
|
427
|
+
|
|
428
|
+
var scene_root: Node = loaded[0]
|
|
429
|
+
var parent = _find_node(scene_root, parent_path)
|
|
430
|
+
if not parent:
|
|
431
|
+
scene_root.queue_free()
|
|
432
|
+
return {"ok": false, "error": "Parent node not found: " + parent_path}
|
|
433
|
+
|
|
434
|
+
var nav: Node = null
|
|
435
|
+
if is_3d:
|
|
436
|
+
var nav3d := NavigationRegion3D.new()
|
|
437
|
+
nav3d.navigation_mesh = NavigationMesh.new()
|
|
438
|
+
nav = nav3d
|
|
439
|
+
else:
|
|
440
|
+
var nav2d := NavigationRegion2D.new()
|
|
441
|
+
nav2d.navigation_polygon = NavigationPolygon.new()
|
|
442
|
+
nav = nav2d
|
|
443
|
+
|
|
444
|
+
nav.name = node_name
|
|
445
|
+
parent.add_child(nav)
|
|
446
|
+
nav.owner = scene_root
|
|
447
|
+
|
|
448
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
449
|
+
if not save_err.is_empty():
|
|
450
|
+
return save_err
|
|
451
|
+
|
|
452
|
+
return {"ok": true, "nodeName": node_name, "is3D": is_3d}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# =============================================================================
|
|
456
|
+
# create_navigation_agent
|
|
457
|
+
# =============================================================================
|
|
458
|
+
func create_navigation_agent(args: Dictionary) -> Dictionary:
|
|
459
|
+
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
460
|
+
var parent_path: String = str(args.get("parentPath", "."))
|
|
461
|
+
var node_name: String = str(args.get("nodeName", "NavigationAgent"))
|
|
462
|
+
var is_3d: bool = bool(args.get("is3D", false))
|
|
463
|
+
|
|
464
|
+
if scene_path.strip_edges() == "res://":
|
|
465
|
+
return {"ok": false, "error": "Missing scenePath"}
|
|
466
|
+
|
|
467
|
+
var loaded := _load_scene(scene_path)
|
|
468
|
+
if not loaded[1].is_empty():
|
|
469
|
+
return loaded[1]
|
|
470
|
+
|
|
471
|
+
var scene_root: Node = loaded[0]
|
|
472
|
+
var parent = _find_node(scene_root, parent_path)
|
|
473
|
+
if not parent:
|
|
474
|
+
scene_root.queue_free()
|
|
475
|
+
return {"ok": false, "error": "Parent node not found: " + parent_path}
|
|
476
|
+
|
|
477
|
+
var agent: Node = null
|
|
478
|
+
if is_3d:
|
|
479
|
+
var agent3d := NavigationAgent3D.new()
|
|
480
|
+
agent3d.name = node_name
|
|
481
|
+
if args.has("pathDesiredDistance") and args.get("pathDesiredDistance") != null:
|
|
482
|
+
agent3d.path_desired_distance = float(args.get("pathDesiredDistance"))
|
|
483
|
+
if args.has("targetDesiredDistance") and args.get("targetDesiredDistance") != null:
|
|
484
|
+
agent3d.target_desired_distance = float(args.get("targetDesiredDistance"))
|
|
485
|
+
agent = agent3d
|
|
486
|
+
else:
|
|
487
|
+
var agent2d := NavigationAgent2D.new()
|
|
488
|
+
agent2d.name = node_name
|
|
489
|
+
if args.has("pathDesiredDistance") and args.get("pathDesiredDistance") != null:
|
|
490
|
+
agent2d.path_desired_distance = float(args.get("pathDesiredDistance"))
|
|
491
|
+
if args.has("targetDesiredDistance") and args.get("targetDesiredDistance") != null:
|
|
492
|
+
agent2d.target_desired_distance = float(args.get("targetDesiredDistance"))
|
|
493
|
+
agent = agent2d
|
|
494
|
+
|
|
495
|
+
parent.add_child(agent)
|
|
496
|
+
agent.owner = scene_root
|
|
497
|
+
|
|
498
|
+
var save_err := _save_scene(scene_root, scene_path)
|
|
499
|
+
if not save_err.is_empty():
|
|
500
|
+
return save_err
|
|
501
|
+
|
|
502
|
+
return {"ok": true, "nodeName": node_name, "is3D": is_3d}
|