gdharness 0.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.
- package/LICENSE +22 -0
- package/README.md +125 -0
- package/build/cli.js +30353 -0
- package/build/godot/addons/auto_reload/auto_reload.gd +89 -0
- package/build/godot/addons/auto_reload/plugin.cfg +6 -0
- package/build/godot/addons/gdharness_editor/bridge_client.gd +197 -0
- package/build/godot/addons/gdharness_editor/plugin.cfg +6 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +88 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +104 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +397 -0
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +85 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +427 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +885 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +292 -0
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +77 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +254 -0
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +283 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +169 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +119 -0
- package/build/godot/operations/audio_buses.gd +125 -0
- package/build/godot/operations/class_cache.gd +180 -0
- package/build/godot/operations/classdb_queries.gd +252 -0
- package/build/godot/operations/dependencies.gd +293 -0
- package/build/godot/operations/file_walk.gd +55 -0
- package/build/godot/operations/gdscript_analysis.gd +288 -0
- package/build/godot/operations/gdscript_authoring.gd +419 -0
- package/build/godot/operations/godot_operations.gd +186 -0
- package/build/godot/operations/import_pipeline.gd +390 -0
- package/build/godot/operations/input_actions.gd +237 -0
- package/build/godot/operations/logger.gd +33 -0
- package/build/godot/operations/plugins.gd +159 -0
- package/build/godot/operations/project_config.gd +153 -0
- package/build/godot/operations/project_diagnostics.gd +159 -0
- package/build/godot/operations/resource_files.gd +132 -0
- package/build/godot/operations/serialisation.gd +154 -0
- package/build/index.js +29252 -0
- package/package.json +57 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends EditorPlugin
|
|
3
|
+
|
|
4
|
+
## Reloads the open scene and the scripts it carries when they change on disk, without the
|
|
5
|
+
## editor's confirmation popup. Every edit made through gdharness lands on disk, and an editor
|
|
6
|
+
## that keeps showing the old file is how a change reads as one that never happened. The
|
|
7
|
+
## files are polled once a second; the editor's own watcher only fires on window focus.
|
|
8
|
+
|
|
9
|
+
const CHECK_INTERVAL_SECONDS: float = 1.0
|
|
10
|
+
|
|
11
|
+
var _timer: Timer
|
|
12
|
+
## The last modification time seen, by path.
|
|
13
|
+
var _watched_files: Dictionary = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
func _enter_tree() -> void:
|
|
17
|
+
_timer = Timer.new()
|
|
18
|
+
_timer.wait_time = CHECK_INTERVAL_SECONDS
|
|
19
|
+
_timer.timeout.connect(_check_for_changes)
|
|
20
|
+
add_child(_timer)
|
|
21
|
+
_timer.start()
|
|
22
|
+
_update_watched_files()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
func _exit_tree() -> void:
|
|
26
|
+
if _timer:
|
|
27
|
+
_timer.stop()
|
|
28
|
+
_timer.queue_free()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
func _update_watched_files() -> void:
|
|
32
|
+
var edited_scene: Node = EditorInterface.get_edited_scene_root()
|
|
33
|
+
if edited_scene and edited_scene.scene_file_path:
|
|
34
|
+
var path: String = edited_scene.scene_file_path
|
|
35
|
+
if not _watched_files.has(path):
|
|
36
|
+
_watched_files[path] = _get_modified_time(path)
|
|
37
|
+
_watch_node_scripts(edited_scene)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
func _watch_node_scripts(node: Node) -> void:
|
|
41
|
+
var script: Variant = node.get_script()
|
|
42
|
+
if script is Script:
|
|
43
|
+
var attached: Script = script
|
|
44
|
+
var path: String = attached.resource_path
|
|
45
|
+
if not path.is_empty() and not _watched_files.has(path):
|
|
46
|
+
_watched_files[path] = _get_modified_time(path)
|
|
47
|
+
for child: Node in node.get_children():
|
|
48
|
+
_watch_node_scripts(child)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
func _get_modified_time(path: String) -> int:
|
|
52
|
+
var global_path: String = ProjectSettings.globalize_path(path)
|
|
53
|
+
if FileAccess.file_exists(global_path):
|
|
54
|
+
return FileAccess.get_modified_time(global_path)
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
func _check_for_changes() -> void:
|
|
59
|
+
_update_watched_files()
|
|
60
|
+
|
|
61
|
+
var scenes_to_reload: Array[String] = []
|
|
62
|
+
var scripts_to_reload: Array[String] = []
|
|
63
|
+
for path: String in _watched_files.keys():
|
|
64
|
+
var current_time: int = _get_modified_time(path)
|
|
65
|
+
var last_time: int = _watched_files[path]
|
|
66
|
+
if current_time > last_time:
|
|
67
|
+
if path.ends_with(".gd"):
|
|
68
|
+
scripts_to_reload.append(path)
|
|
69
|
+
else:
|
|
70
|
+
scenes_to_reload.append(path)
|
|
71
|
+
_watched_files[path] = current_time
|
|
72
|
+
|
|
73
|
+
# Scripts first, so a scene reloaded after them instantiates the new code.
|
|
74
|
+
for path: String in scripts_to_reload:
|
|
75
|
+
_reload_script(path)
|
|
76
|
+
for path: String in scenes_to_reload:
|
|
77
|
+
_reload_scene(path)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
func _reload_script(path: String) -> void:
|
|
81
|
+
print("[gdharness] Script changed on disk, reloading: ", path)
|
|
82
|
+
ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
func _reload_scene(path: String) -> void:
|
|
86
|
+
var edited_scene: Node = EditorInterface.get_edited_scene_root()
|
|
87
|
+
if edited_scene and edited_scene.scene_file_path == path:
|
|
88
|
+
print("[gdharness] Scene changed on disk, reloading: ", path)
|
|
89
|
+
EditorInterface.reload_scene_from_path(path)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Node
|
|
3
|
+
|
|
4
|
+
## The editor's end of the bridge: one WebSocket to the gdharness server, reconnected on its
|
|
5
|
+
## own when it drops, carrying tool requests in and their results out.
|
|
6
|
+
|
|
7
|
+
signal connected
|
|
8
|
+
signal disconnected
|
|
9
|
+
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
|
|
10
|
+
|
|
11
|
+
const DEFAULT_URL: String = "ws://127.0.0.1:6505/godot"
|
|
12
|
+
## Written beside the addon by the install, so it names the version this copy came from.
|
|
13
|
+
const VERSION_MARKER: String = "res://addons/gdharness_editor/.gdharness-version"
|
|
14
|
+
const RECONNECT_DELAY: float = 3.0
|
|
15
|
+
const MAX_RECONNECT_DELAY: float = 30.0
|
|
16
|
+
|
|
17
|
+
var socket: WebSocketPeer = WebSocketPeer.new()
|
|
18
|
+
var server_url: String = DEFAULT_URL
|
|
19
|
+
var _is_connected: bool = false
|
|
20
|
+
var _reconnect_timer: Timer
|
|
21
|
+
var _current_reconnect_delay: float = RECONNECT_DELAY
|
|
22
|
+
var _should_reconnect: bool = true
|
|
23
|
+
var _project_path: String
|
|
24
|
+
var _initialized: bool = false
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func _ready() -> void:
|
|
28
|
+
_project_path = ProjectSettings.globalize_path("res://")
|
|
29
|
+
|
|
30
|
+
_reconnect_timer = Timer.new()
|
|
31
|
+
_reconnect_timer.one_shot = true
|
|
32
|
+
_reconnect_timer.timeout.connect(_on_reconnect_timer)
|
|
33
|
+
add_child(_reconnect_timer)
|
|
34
|
+
|
|
35
|
+
set_process(true)
|
|
36
|
+
_initialized = true
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
func _process(_delta: float) -> void:
|
|
40
|
+
if not _initialized:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
|
|
44
|
+
if _is_connected:
|
|
45
|
+
_handle_disconnect()
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
socket.poll()
|
|
49
|
+
|
|
50
|
+
match socket.get_ready_state():
|
|
51
|
+
WebSocketPeer.STATE_OPEN:
|
|
52
|
+
if not _is_connected:
|
|
53
|
+
_handle_connect()
|
|
54
|
+
|
|
55
|
+
while socket.get_available_packet_count() > 0:
|
|
56
|
+
var packet: PackedByteArray = socket.get_packet()
|
|
57
|
+
_handle_message(packet.get_string_from_utf8())
|
|
58
|
+
|
|
59
|
+
WebSocketPeer.STATE_CLOSING:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
WebSocketPeer.STATE_CLOSED:
|
|
63
|
+
if _is_connected:
|
|
64
|
+
_handle_disconnect()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
func connect_to_server(url: String = "") -> void:
|
|
68
|
+
server_url = _resolve_server_url(url)
|
|
69
|
+
_should_reconnect = true
|
|
70
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
71
|
+
_attempt_connection()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
func _resolve_server_url(explicit_url: String) -> String:
|
|
75
|
+
if explicit_url != "":
|
|
76
|
+
return explicit_url
|
|
77
|
+
|
|
78
|
+
# The same variable the server reads, so the two agree on the port by construction.
|
|
79
|
+
var raw: String = OS.get_environment("GDHARNESS_BRIDGE_PORT")
|
|
80
|
+
if raw != "":
|
|
81
|
+
if raw.is_valid_int() and int(raw) >= 1 and int(raw) <= 65535:
|
|
82
|
+
return "ws://127.0.0.1:%d/godot" % int(raw)
|
|
83
|
+
push_error("GDHARNESS_BRIDGE_PORT is %s, not a port; using %s" % [raw, DEFAULT_URL])
|
|
84
|
+
|
|
85
|
+
return DEFAULT_URL
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
func disconnect_from_server() -> void:
|
|
89
|
+
_should_reconnect = false
|
|
90
|
+
if _reconnect_timer:
|
|
91
|
+
_reconnect_timer.stop()
|
|
92
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
93
|
+
socket.close()
|
|
94
|
+
_is_connected = false
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
func _attempt_connection() -> void:
|
|
98
|
+
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
|
99
|
+
socket.close()
|
|
100
|
+
|
|
101
|
+
var err: Error = socket.connect_to_url(server_url)
|
|
102
|
+
if err != OK:
|
|
103
|
+
push_error("[gdharness] Failed to connect to %s: %s" % [server_url, error_string(err)])
|
|
104
|
+
_schedule_reconnect()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
func _handle_connect() -> void:
|
|
108
|
+
_is_connected = true
|
|
109
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
110
|
+
|
|
111
|
+
# The version reported is the one this editor loaded at startup, not the one on disk: an
|
|
112
|
+
# upgrade replaces the files under a running editor, which goes on serving the old code until
|
|
113
|
+
# somebody restarts it, and nothing else can tell the two apart.
|
|
114
|
+
# The process id with it, because a restarted editor is a different process from the one
|
|
115
|
+
# whoever started it is holding, and nothing else says which one is now on the other end.
|
|
116
|
+
_send_message(
|
|
117
|
+
{
|
|
118
|
+
"type": "godot_ready",
|
|
119
|
+
"project_path": _project_path,
|
|
120
|
+
"addon_version": _loaded_version(),
|
|
121
|
+
"editor_pid": OS.get_process_id()
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
connected.emit()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## The version marker beside this addon, or "" when the copy was not installed by gdharness.
|
|
129
|
+
func _loaded_version() -> String:
|
|
130
|
+
if not FileAccess.file_exists(VERSION_MARKER):
|
|
131
|
+
return ""
|
|
132
|
+
var file: FileAccess = FileAccess.open(VERSION_MARKER, FileAccess.READ)
|
|
133
|
+
if file == null:
|
|
134
|
+
return ""
|
|
135
|
+
var text: String = file.get_as_text().strip_edges()
|
|
136
|
+
file.close()
|
|
137
|
+
return text
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
func _handle_disconnect() -> void:
|
|
141
|
+
_is_connected = false
|
|
142
|
+
disconnected.emit()
|
|
143
|
+
|
|
144
|
+
if _should_reconnect:
|
|
145
|
+
_schedule_reconnect()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
func _schedule_reconnect() -> void:
|
|
149
|
+
if _reconnect_timer == null:
|
|
150
|
+
return
|
|
151
|
+
_reconnect_timer.start(_current_reconnect_delay)
|
|
152
|
+
_current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
func _on_reconnect_timer() -> void:
|
|
156
|
+
_attempt_connection()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
func _handle_message(json_string: String) -> void:
|
|
160
|
+
var parsed: Variant = JSON.parse_string(json_string)
|
|
161
|
+
if not parsed is Dictionary:
|
|
162
|
+
push_error("[gdharness] The server sent something that is not a message: %s" % json_string)
|
|
163
|
+
return
|
|
164
|
+
var message: Dictionary = parsed
|
|
165
|
+
|
|
166
|
+
match message.get("type", ""):
|
|
167
|
+
"ping":
|
|
168
|
+
_send_message({"type": "pong"})
|
|
169
|
+
|
|
170
|
+
"tool_invoke":
|
|
171
|
+
var request_id: String = message.get("id", "")
|
|
172
|
+
var tool_name: String = message.get("tool", "")
|
|
173
|
+
var args: Dictionary = message.get("args", {})
|
|
174
|
+
tool_requested.emit(request_id, tool_name, args)
|
|
175
|
+
|
|
176
|
+
_:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
func send_tool_result(request_id: String, success: bool, result: Variant = null, error: String = "") -> void:
|
|
181
|
+
var response: Dictionary = {"type": "tool_result", "id": request_id, "success": success}
|
|
182
|
+
|
|
183
|
+
if success:
|
|
184
|
+
response["result"] = result
|
|
185
|
+
else:
|
|
186
|
+
response["error"] = error
|
|
187
|
+
|
|
188
|
+
_send_message(response)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
func _send_message(message: Dictionary) -> void:
|
|
192
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
193
|
+
socket.send_text(JSON.stringify(message))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
func is_connected_to_server() -> bool:
|
|
197
|
+
return _is_connected
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends EditorPlugin
|
|
3
|
+
|
|
4
|
+
## The editor half of gdharness: a client that holds a socket to the server and an executor
|
|
5
|
+
## that does what the server asks, with a label in the toolbar saying whether the two are
|
|
6
|
+
## connected.
|
|
7
|
+
|
|
8
|
+
const BridgeClient = preload("bridge_client.gd")
|
|
9
|
+
const ToolExecutor = preload("tool_executor.gd")
|
|
10
|
+
|
|
11
|
+
var _client: BridgeClient
|
|
12
|
+
var _tool_executor: ToolExecutor
|
|
13
|
+
var _status_label: Label
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
func _enter_tree() -> void:
|
|
17
|
+
_client = BridgeClient.new()
|
|
18
|
+
_client.name = "GdharnessBridgeClient"
|
|
19
|
+
add_child(_client)
|
|
20
|
+
|
|
21
|
+
_tool_executor = ToolExecutor.new()
|
|
22
|
+
_tool_executor.name = "GdharnessToolExecutor"
|
|
23
|
+
add_child(_tool_executor)
|
|
24
|
+
_tool_executor.set_editor_plugin(self)
|
|
25
|
+
|
|
26
|
+
_client.connected.connect(_on_connected)
|
|
27
|
+
_client.disconnected.connect(_on_disconnected)
|
|
28
|
+
_client.tool_requested.connect(_on_tool_requested)
|
|
29
|
+
|
|
30
|
+
_setup_status_indicator()
|
|
31
|
+
_client.connect_to_server()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
func _exit_tree() -> void:
|
|
35
|
+
if _client:
|
|
36
|
+
if _client.connected.is_connected(_on_connected):
|
|
37
|
+
_client.connected.disconnect(_on_connected)
|
|
38
|
+
if _client.disconnected.is_connected(_on_disconnected):
|
|
39
|
+
_client.disconnected.disconnect(_on_disconnected)
|
|
40
|
+
if _client.tool_requested.is_connected(_on_tool_requested):
|
|
41
|
+
_client.tool_requested.disconnect(_on_tool_requested)
|
|
42
|
+
_client.disconnect_from_server()
|
|
43
|
+
_client.queue_free()
|
|
44
|
+
_client = null
|
|
45
|
+
|
|
46
|
+
if _tool_executor:
|
|
47
|
+
_tool_executor.queue_free()
|
|
48
|
+
_tool_executor = null
|
|
49
|
+
|
|
50
|
+
if _status_label:
|
|
51
|
+
remove_control_from_container(CONTAINER_TOOLBAR, _status_label)
|
|
52
|
+
_status_label.queue_free()
|
|
53
|
+
_status_label = null
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
func _setup_status_indicator() -> void:
|
|
57
|
+
_status_label = Label.new()
|
|
58
|
+
_status_label.text = "gdharness: connecting"
|
|
59
|
+
_status_label.add_theme_color_override("font_color", Color.YELLOW)
|
|
60
|
+
_status_label.add_theme_font_size_override("font_size", 12)
|
|
61
|
+
add_control_to_container(CONTAINER_TOOLBAR, _status_label)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
func _on_connected() -> void:
|
|
65
|
+
if _status_label:
|
|
66
|
+
_status_label.text = "gdharness: connected"
|
|
67
|
+
_status_label.add_theme_color_override("font_color", Color.GREEN)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
func _on_disconnected() -> void:
|
|
71
|
+
if _status_label:
|
|
72
|
+
_status_label.text = "gdharness: disconnected"
|
|
73
|
+
_status_label.add_theme_color_override("font_color", Color.RED)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
func _on_tool_requested(request_id: String, tool_name: String, args: Dictionary) -> void:
|
|
77
|
+
if _tool_executor == null or _client == null:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
var result: Dictionary = _tool_executor.execute_tool(tool_name, args)
|
|
81
|
+
var success: bool = result.get("ok", false)
|
|
82
|
+
|
|
83
|
+
if success:
|
|
84
|
+
var payload: Dictionary = result.duplicate(true)
|
|
85
|
+
payload.erase("ok")
|
|
86
|
+
_client.send_tool_result(request_id, true, payload, "")
|
|
87
|
+
else:
|
|
88
|
+
_client.send_tool_result(request_id, false, null, str(result.get("error", "Unknown error")))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Node
|
|
3
|
+
|
|
4
|
+
## Routes each command the server sends to the tool module that answers it. The modules are
|
|
5
|
+
## preloaded rather than looked up on disk at runtime, so a missing one fails to parse here
|
|
6
|
+
## instead of leaving a command silently unanswered.
|
|
7
|
+
|
|
8
|
+
const SceneTools = preload("tools/scene_tools.gd")
|
|
9
|
+
const ResourceTools = preload("tools/resource_tools.gd")
|
|
10
|
+
const AnimationTools = preload("tools/animation_tools.gd")
|
|
11
|
+
const PlayTools = preload("tools/play_tools.gd")
|
|
12
|
+
|
|
13
|
+
var _editor_plugin: EditorPlugin = null
|
|
14
|
+
|
|
15
|
+
var _scene_tools: SceneTools = null
|
|
16
|
+
var _resource_tools: ResourceTools = null
|
|
17
|
+
var _animation_tools: AnimationTools = null
|
|
18
|
+
var _play_tools: PlayTools = null
|
|
19
|
+
|
|
20
|
+
var _tool_map: Dictionary = {}
|
|
21
|
+
var _initialized: bool = false
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
25
|
+
_editor_plugin = plugin
|
|
26
|
+
_init_tools()
|
|
27
|
+
_scene_tools.set_editor_plugin(plugin)
|
|
28
|
+
_resource_tools.set_editor_plugin(plugin)
|
|
29
|
+
_animation_tools.set_editor_plugin(plugin)
|
|
30
|
+
_play_tools.set_editor_plugin(plugin)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
func _init_tools() -> void:
|
|
34
|
+
if _initialized:
|
|
35
|
+
return
|
|
36
|
+
_initialized = true
|
|
37
|
+
|
|
38
|
+
_scene_tools = SceneTools.new()
|
|
39
|
+
_scene_tools.name = "SceneTools"
|
|
40
|
+
add_child(_scene_tools)
|
|
41
|
+
|
|
42
|
+
_resource_tools = ResourceTools.new()
|
|
43
|
+
_resource_tools.name = "ResourceTools"
|
|
44
|
+
add_child(_resource_tools)
|
|
45
|
+
|
|
46
|
+
_animation_tools = AnimationTools.new()
|
|
47
|
+
_animation_tools.name = "AnimationTools"
|
|
48
|
+
add_child(_animation_tools)
|
|
49
|
+
|
|
50
|
+
_play_tools = PlayTools.new()
|
|
51
|
+
_play_tools.name = "PlayTools"
|
|
52
|
+
add_child(_play_tools)
|
|
53
|
+
|
|
54
|
+
_tool_map = {
|
|
55
|
+
# Scene tools
|
|
56
|
+
"create_scene": [_scene_tools, "create_scene"],
|
|
57
|
+
"list_scene_nodes": [_scene_tools, "list_scene_nodes"],
|
|
58
|
+
"add_node": [_scene_tools, "add_node"],
|
|
59
|
+
"delete_node": [_scene_tools, "delete_node"],
|
|
60
|
+
"duplicate_node": [_scene_tools, "duplicate_node"],
|
|
61
|
+
"reparent_node": [_scene_tools, "reparent_node"],
|
|
62
|
+
"set_node_properties": [_scene_tools, "set_node_properties"],
|
|
63
|
+
"get_node_properties": [_scene_tools, "get_node_properties"],
|
|
64
|
+
"save_scene": [_scene_tools, "save_scene"],
|
|
65
|
+
"connect_signal": [_scene_tools, "connect_signal"],
|
|
66
|
+
"disconnect_signal": [_scene_tools, "disconnect_signal"],
|
|
67
|
+
"list_connections": [_scene_tools, "list_connections"],
|
|
68
|
+
"rescan_filesystem": [_scene_tools, "rescan_filesystem"],
|
|
69
|
+
# Resource tools
|
|
70
|
+
"create_resource": [_resource_tools, "create_resource"],
|
|
71
|
+
"modify_resource": [_resource_tools, "modify_resource"],
|
|
72
|
+
"create_shader": [_resource_tools, "create_shader"],
|
|
73
|
+
"create_tileset": [_resource_tools, "create_tileset"],
|
|
74
|
+
"set_tilemap_cells": [_resource_tools, "set_tilemap_cells"],
|
|
75
|
+
"set_theme_color": [_resource_tools, "set_theme_color"],
|
|
76
|
+
"set_theme_font_size": [_resource_tools, "set_theme_font_size"],
|
|
77
|
+
# Animation tools
|
|
78
|
+
"play_scene": [_play_tools, "play_scene"],
|
|
79
|
+
"restart_editor": [_play_tools, "restart_editor"],
|
|
80
|
+
"stop_playing": [_play_tools, "stop_playing"],
|
|
81
|
+
"playing_status": [_play_tools, "playing_status"],
|
|
82
|
+
"create_animation": [_animation_tools, "create_animation"],
|
|
83
|
+
"add_animation_track": [_animation_tools, "add_animation_track"],
|
|
84
|
+
"add_animation_state": [_animation_tools, "add_animation_state"],
|
|
85
|
+
"connect_animation_states": [_animation_tools, "connect_animation_states"],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
func execute_tool(tool_name: String, args: Dictionary) -> Dictionary:
|
|
90
|
+
if not _tool_map.has(tool_name):
|
|
91
|
+
return {"ok": false, "error": "Unknown tool: " + tool_name}
|
|
92
|
+
|
|
93
|
+
var handler: Array = _tool_map[tool_name]
|
|
94
|
+
var node: Node = handler[0]
|
|
95
|
+
var method: String = handler[1]
|
|
96
|
+
|
|
97
|
+
if not node.has_method(method):
|
|
98
|
+
return {"ok": false, "error": "Tool method not found: %s.%s" % [node.name, method]}
|
|
99
|
+
|
|
100
|
+
var result: Variant = node.call(method, args)
|
|
101
|
+
if result is Dictionary:
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
return {"ok": false, "error": "Invalid tool result from: " + tool_name}
|