gdharness 0.12.17 → 0.13.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.
- package/build/cli.js +174 -56
- package/build/godot/addons/auto_reload/auto_reload.gd +9 -2
- package/build/godot/addons/gdharness_editor/bridge_client.gd +11 -5
- package/build/godot/addons/gdharness_editor/plugin.gd +17 -4
- package/build/godot/addons/gdharness_editor/reading.gd +64 -0
- package/build/godot/addons/gdharness_editor/reading.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +30 -12
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +5 -2
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +3 -1
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +50 -27
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +92 -42
- package/build/godot/addons/gdharness_runtime/reading.gd +64 -0
- package/build/godot/addons/gdharness_runtime/reading.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +24 -10
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +7 -5
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +24 -22
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +16 -13
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +24 -8
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +7 -4
- package/build/godot/operations/audio_buses.gd +7 -6
- package/build/godot/operations/class_cache.gd +14 -9
- package/build/godot/operations/classdb_queries.gd +4 -3
- package/build/godot/operations/dependencies.gd +19 -16
- package/build/godot/operations/file_walk.gd +4 -2
- package/build/godot/operations/gdscript_analysis.gd +2 -3
- package/build/godot/operations/gdscript_authoring.gd +20 -8
- package/build/godot/operations/godot_operations.gd +7 -2
- package/build/godot/operations/import_pipeline.gd +5 -4
- package/build/godot/operations/input_actions.gd +13 -9
- package/build/godot/operations/patterns.gd +22 -0
- package/build/godot/operations/plugins.gd +10 -6
- package/build/godot/operations/project_config.gd +41 -3
- package/build/godot/operations/reading.gd +66 -0
- package/build/godot/operations/serialisation.gd +41 -9
- package/build/index.js +147 -53
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ extends Node
|
|
|
3
3
|
|
|
4
4
|
## Animations and animation trees, edited in the open editor.
|
|
5
5
|
|
|
6
|
+
const Read = preload("../reading.gd")
|
|
7
|
+
|
|
6
8
|
var _editor_plugin: EditorPlugin = null
|
|
7
9
|
|
|
8
10
|
|
|
@@ -75,12 +77,19 @@ func _parse_value(value: Variant) -> Variant:
|
|
|
75
77
|
var t: Variant = fields.get("type", fields.get("_type", ""))
|
|
76
78
|
match t:
|
|
77
79
|
"Vector2":
|
|
78
|
-
return Vector2(fields.get("x", 0), fields.get("y", 0))
|
|
80
|
+
return Vector2(Read.as_float(fields.get("x", 0)), Read.as_float(fields.get("y", 0)))
|
|
79
81
|
"Vector3":
|
|
80
|
-
return Vector3(
|
|
82
|
+
return Vector3(
|
|
83
|
+
Read.as_float(fields.get("x", 0)),
|
|
84
|
+
Read.as_float(fields.get("y", 0)),
|
|
85
|
+
Read.as_float(fields.get("z", 0))
|
|
86
|
+
)
|
|
81
87
|
"Color":
|
|
82
88
|
return Color(
|
|
83
|
-
fields.get("r", 1),
|
|
89
|
+
Read.as_float(fields.get("r", 1), 1.0),
|
|
90
|
+
Read.as_float(fields.get("g", 1), 1.0),
|
|
91
|
+
Read.as_float(fields.get("b", 1), 1.0),
|
|
92
|
+
Read.as_float(fields.get("a", 1), 1.0)
|
|
84
93
|
)
|
|
85
94
|
if typeof(value) == TYPE_ARRAY:
|
|
86
95
|
var result: Array = []
|
|
@@ -93,8 +102,9 @@ func _parse_value(value: Variant) -> Variant:
|
|
|
93
102
|
func _parse_json_maybe(value: Variant) -> Variant:
|
|
94
103
|
if typeof(value) != TYPE_STRING:
|
|
95
104
|
return value
|
|
96
|
-
var
|
|
97
|
-
|
|
105
|
+
var text: String = value
|
|
106
|
+
var parsed: Variant = JSON.parse_string(text)
|
|
107
|
+
if parsed == null and text != "null":
|
|
98
108
|
return value
|
|
99
109
|
return parsed
|
|
100
110
|
|
|
@@ -183,9 +193,9 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
183
193
|
loop_mode = Animation.LOOP_NONE
|
|
184
194
|
|
|
185
195
|
var anim: Animation = Animation.new()
|
|
186
|
-
anim.length =
|
|
196
|
+
anim.length = Read.as_float(args.get("length", 1.0), 1.0)
|
|
187
197
|
anim.loop_mode = loop_mode
|
|
188
|
-
anim.step =
|
|
198
|
+
anim.step = Read.as_float(args.get("step", 0.1), 0.1)
|
|
189
199
|
|
|
190
200
|
var add_err: Error = anim_lib.add_animation(StringName(animation_name), anim)
|
|
191
201
|
if add_err != OK:
|
|
@@ -262,7 +272,11 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
262
272
|
var parsed_value: Variant = (
|
|
263
273
|
_parse_json_maybe(raw_value) if typeof(raw_value) == TYPE_STRING else raw_value
|
|
264
274
|
)
|
|
265
|
-
anim.track_insert_key(
|
|
275
|
+
var placed: int = anim.track_insert_key(
|
|
276
|
+
track_idx, Read.as_float(keyframe.get("time", 0.0)), _parse_value(parsed_value)
|
|
277
|
+
)
|
|
278
|
+
if placed < 0:
|
|
279
|
+
push_error("gdharness: could not key " + prop_name + " on " + node_path_str)
|
|
266
280
|
|
|
267
281
|
"method":
|
|
268
282
|
var method_node_path: String = str(track.get("nodePath", ""))
|
|
@@ -276,10 +290,14 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
276
290
|
if typeof(entry) != TYPE_DICTIONARY:
|
|
277
291
|
continue
|
|
278
292
|
var keyframe: Dictionary = entry
|
|
279
|
-
var
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
anim.track_insert_key(
|
|
293
|
+
var given: Variant = keyframe.get("args", [])
|
|
294
|
+
var arguments: Array = given if given is Array else []
|
|
295
|
+
var invocation: Dictionary = {"method": method_name, "args": _parse_method_args(arguments)}
|
|
296
|
+
var placed: int = anim.track_insert_key(
|
|
297
|
+
track_idx, Read.as_float(keyframe.get("time", 0.0)), invocation
|
|
298
|
+
)
|
|
299
|
+
if placed < 0:
|
|
300
|
+
push_error("gdharness: could not key " + method_name + " on " + method_node_path)
|
|
283
301
|
|
|
284
302
|
_:
|
|
285
303
|
scene_root.queue_free()
|
|
@@ -34,7 +34,7 @@ func global_classes(_args: Dictionary) -> Dictionary:
|
|
|
34
34
|
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
35
35
|
|
|
36
36
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
37
|
-
var names:
|
|
37
|
+
var names: Array[String] = []
|
|
38
38
|
_walk(filesystem.get_filesystem(), names)
|
|
39
39
|
names.sort()
|
|
40
40
|
return {
|
|
@@ -45,7 +45,10 @@ func global_classes(_args: Dictionary) -> Dictionary:
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
# Array[String] rather than PackedStringArray: appending to a packed array answers with whether it
|
|
49
|
+
# worked, and a project holding return_value_discarded at error level refuses a script that drops
|
|
50
|
+
# that answer. Both reach the caller as the same list of strings.
|
|
51
|
+
func _walk(directory: EditorFileSystemDirectory, into: Array[String]) -> void:
|
|
49
52
|
if directory == null:
|
|
50
53
|
return
|
|
51
54
|
for index: int in range(directory.get_file_count()):
|
|
@@ -9,6 +9,8 @@ extends Node
|
|
|
9
9
|
## tools answer at all.
|
|
10
10
|
|
|
11
11
|
## What the display server calls itself when the engine was started with no display at all.
|
|
12
|
+
const Read = preload("../reading.gd")
|
|
13
|
+
|
|
12
14
|
const HEADLESS_DISPLAY: String = "headless"
|
|
13
15
|
## Where Godot keeps the port its own debugger listens on for a game it is playing. One setting
|
|
14
16
|
## for every editor on the machine, and bound only while a game runs.
|
|
@@ -158,7 +160,7 @@ func playing_status(_args: Dictionary) -> Dictionary:
|
|
|
158
160
|
# play, so the one from when this editor greeted the server is a number it has moved off.
|
|
159
161
|
var settings: EditorSettings = EditorInterface.get_editor_settings()
|
|
160
162
|
var debugger: int = (
|
|
161
|
-
|
|
163
|
+
Read.as_int(settings.get_setting(DEBUGGER_SETTING))
|
|
162
164
|
if settings != null and settings.has_setting(DEBUGGER_SETTING)
|
|
163
165
|
else 0
|
|
164
166
|
)
|
|
@@ -3,6 +3,8 @@ extends Node
|
|
|
3
3
|
|
|
4
4
|
## Resource files, shaders, tilesets and themes, written through the open editor.
|
|
5
5
|
|
|
6
|
+
const Read = preload("../reading.gd")
|
|
7
|
+
|
|
6
8
|
# Shader templates. Written as real multi-line source rather than escaped one-liners so that
|
|
7
9
|
# what ends up in the .gdshader can be read here. The %s is the shader type.
|
|
8
10
|
const SHADER_EMPTY: String = """shader_type %s;
|
|
@@ -75,26 +77,37 @@ func _parse_value(value: Variant) -> Variant:
|
|
|
75
77
|
var t: Variant = fields.get("type", fields.get("_type", ""))
|
|
76
78
|
match t:
|
|
77
79
|
"Vector2":
|
|
78
|
-
return Vector2(fields.get("x", 0), fields.get("y", 0))
|
|
80
|
+
return Vector2(Read.as_float(fields.get("x", 0)), Read.as_float(fields.get("y", 0)))
|
|
79
81
|
"Vector3":
|
|
80
|
-
return Vector3(
|
|
82
|
+
return Vector3(
|
|
83
|
+
Read.as_float(fields.get("x", 0)),
|
|
84
|
+
Read.as_float(fields.get("y", 0)),
|
|
85
|
+
Read.as_float(fields.get("z", 0))
|
|
86
|
+
)
|
|
81
87
|
"Color":
|
|
82
88
|
return Color(
|
|
83
|
-
fields.get("r", 1),
|
|
89
|
+
Read.as_float(fields.get("r", 1), 1.0),
|
|
90
|
+
Read.as_float(fields.get("g", 1), 1.0),
|
|
91
|
+
Read.as_float(fields.get("b", 1), 1.0),
|
|
92
|
+
Read.as_float(fields.get("a", 1), 1.0)
|
|
84
93
|
)
|
|
85
94
|
"Vector2i":
|
|
86
|
-
return Vector2i(fields.get("x", 0), fields.get("y", 0))
|
|
95
|
+
return Vector2i(Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)))
|
|
87
96
|
"Vector3i":
|
|
88
|
-
return Vector3i(
|
|
97
|
+
return Vector3i(
|
|
98
|
+
Read.as_int(fields.get("x", 0)),
|
|
99
|
+
Read.as_int(fields.get("y", 0)),
|
|
100
|
+
Read.as_int(fields.get("z", 0))
|
|
101
|
+
)
|
|
89
102
|
"Rect2":
|
|
90
103
|
return Rect2(
|
|
91
|
-
fields.get("x", 0),
|
|
92
|
-
fields.get("y", 0),
|
|
93
|
-
fields.get("width", 0),
|
|
94
|
-
fields.get("height", 0)
|
|
104
|
+
Read.as_float(fields.get("x", 0)),
|
|
105
|
+
Read.as_float(fields.get("y", 0)),
|
|
106
|
+
Read.as_float(fields.get("width", 0)),
|
|
107
|
+
Read.as_float(fields.get("height", 0))
|
|
95
108
|
)
|
|
96
109
|
"NodePath":
|
|
97
|
-
return NodePath(fields.get("path", ""))
|
|
110
|
+
return NodePath(str(fields.get("path", "")))
|
|
98
111
|
if typeof(value) == TYPE_ARRAY:
|
|
99
112
|
var result: Array = []
|
|
100
113
|
for item: Variant in value:
|
|
@@ -106,16 +119,17 @@ func _parse_value(value: Variant) -> Variant:
|
|
|
106
119
|
func _set_resource_properties(resource: Resource, properties: Variant) -> void:
|
|
107
120
|
var props: Dictionary = _parse_properties_dict(properties)
|
|
108
121
|
for key: Variant in props:
|
|
109
|
-
var
|
|
110
|
-
resource.set(
|
|
122
|
+
var named: String = str(key)
|
|
123
|
+
resource.set(named, _parse_value(props[key]))
|
|
111
124
|
|
|
112
125
|
|
|
113
126
|
func _parse_properties_dict(raw: Variant) -> Dictionary:
|
|
114
127
|
if typeof(raw) == TYPE_DICTIONARY:
|
|
115
128
|
return raw
|
|
116
129
|
if typeof(raw) == TYPE_STRING and raw != "":
|
|
130
|
+
var text: String = raw
|
|
117
131
|
var json: JSON = JSON.new()
|
|
118
|
-
if json.parse(
|
|
132
|
+
if json.parse(text) == OK and typeof(json.data) == TYPE_DICTIONARY:
|
|
119
133
|
return json.data
|
|
120
134
|
return {}
|
|
121
135
|
|
|
@@ -212,8 +226,10 @@ func create_shader(args: Dictionary) -> Dictionary:
|
|
|
212
226
|
var file: FileAccess = FileAccess.open(shader_path, FileAccess.WRITE)
|
|
213
227
|
if file == null:
|
|
214
228
|
return {"ok": false, "error": "Failed to open shader file for writing", "shaderPath": shader_path}
|
|
215
|
-
file.store_string(code)
|
|
229
|
+
var written: bool = file.store_string(code)
|
|
216
230
|
file.close()
|
|
231
|
+
if not written:
|
|
232
|
+
return {"ok": false, "error": "Failed to write the shader file", "shaderPath": shader_path}
|
|
217
233
|
|
|
218
234
|
_refresh_filesystem()
|
|
219
235
|
return {"ok": true, "shaderPath": shader_path, "shaderType": shader_type}
|
|
@@ -241,17 +257,21 @@ func create_tileset(args: Dictionary) -> Dictionary:
|
|
|
241
257
|
atlas.texture = tex
|
|
242
258
|
|
|
243
259
|
var tile_size: Dictionary = source.get("tileSize", {})
|
|
244
|
-
atlas.texture_region_size = Vector2i(
|
|
260
|
+
atlas.texture_region_size = Vector2i(
|
|
261
|
+
Read.as_int(tile_size.get("x", 0)), Read.as_int(tile_size.get("y", 0))
|
|
262
|
+
)
|
|
245
263
|
|
|
246
264
|
if source.has("separation"):
|
|
247
265
|
var sep: Dictionary = source.get("separation", {})
|
|
248
|
-
atlas.separation = Vector2i(
|
|
266
|
+
atlas.separation = Vector2i(Read.as_int(sep.get("x", 0)), Read.as_int(sep.get("y", 0)))
|
|
249
267
|
|
|
250
268
|
if source.has("offset"):
|
|
251
269
|
var off: Dictionary = source.get("offset", {})
|
|
252
|
-
atlas.margins = Vector2i(
|
|
270
|
+
atlas.margins = Vector2i(Read.as_int(off.get("x", 0)), Read.as_int(off.get("y", 0)))
|
|
253
271
|
|
|
254
|
-
tileset.add_source(atlas)
|
|
272
|
+
var added: int = tileset.add_source(atlas)
|
|
273
|
+
if added < 0:
|
|
274
|
+
return {"ok": false, "error": "Failed to add a tile source", "texture": tex_path}
|
|
255
275
|
|
|
256
276
|
var save_result: Error = ResourceSaver.save(tileset, tileset_path)
|
|
257
277
|
if save_result != OK:
|
|
@@ -262,8 +282,8 @@ func create_tileset(args: Dictionary) -> Dictionary:
|
|
|
262
282
|
|
|
263
283
|
|
|
264
284
|
## The source ids a tile set holds, for saying what a cell could have named instead.
|
|
265
|
-
func _source_ids(tile_set: TileSet) ->
|
|
266
|
-
var ids:
|
|
285
|
+
func _source_ids(tile_set: TileSet) -> Array[int]:
|
|
286
|
+
var ids: Array[int] = []
|
|
267
287
|
for index: int in tile_set.get_source_count():
|
|
268
288
|
ids.append(tile_set.get_source_id(index))
|
|
269
289
|
return ids
|
|
@@ -293,7 +313,7 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
293
313
|
root.queue_free()
|
|
294
314
|
return {"ok": false, "error": "TileMap node not found", "tilemapNodePath": node_path}
|
|
295
315
|
|
|
296
|
-
var layer: int =
|
|
316
|
+
var layer: int = Read.as_int(args.get("layer", 0))
|
|
297
317
|
var cells: Variant = args.get("cells", [])
|
|
298
318
|
# Refused rather than skipped: placing nothing and answering success is indistinguishable
|
|
299
319
|
# from placing everything, and the caller only finds out by opening the scene.
|
|
@@ -322,7 +342,7 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
322
342
|
"ok": false, "error": "every cell must be an object with coords, sourceId and atlasCoords"
|
|
323
343
|
}
|
|
324
344
|
var cell: Dictionary = entry
|
|
325
|
-
var source_id: int =
|
|
345
|
+
var source_id: int = Read.as_int(cell.get("sourceId", -1), -1)
|
|
326
346
|
if not tile_set.has_source(source_id):
|
|
327
347
|
root.queue_free()
|
|
328
348
|
return {
|
|
@@ -332,13 +352,13 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
332
352
|
|
|
333
353
|
var coords: Dictionary = cell.get("coords", {})
|
|
334
354
|
var atlas_coords: Dictionary = cell.get("atlasCoords", {})
|
|
335
|
-
var at: Vector2i = Vector2i(
|
|
355
|
+
var at: Vector2i = Vector2i(Read.as_int(coords.get("x", 0)), Read.as_int(coords.get("y", 0)))
|
|
336
356
|
tilemap.set_cell(
|
|
337
357
|
layer,
|
|
338
358
|
at,
|
|
339
359
|
source_id,
|
|
340
|
-
Vector2i(
|
|
341
|
-
|
|
360
|
+
Vector2i(Read.as_int(atlas_coords.get("x", 0)), Read.as_int(atlas_coords.get("y", 0))),
|
|
361
|
+
Read.as_int(cell.get("alternativeTile", 0))
|
|
342
362
|
)
|
|
343
363
|
if tilemap.get_cell_source_id(layer, at) != -1:
|
|
344
364
|
written += 1
|
|
@@ -368,7 +388,10 @@ func set_theme_color(args: Dictionary) -> Dictionary:
|
|
|
368
388
|
|
|
369
389
|
var c: Dictionary = args.get("color", {})
|
|
370
390
|
var color: Color = Color(
|
|
371
|
-
|
|
391
|
+
Read.as_float(c.get("r", 1.0), 1.0),
|
|
392
|
+
Read.as_float(c.get("g", 1.0), 1.0),
|
|
393
|
+
Read.as_float(c.get("b", 1.0), 1.0),
|
|
394
|
+
Read.as_float(c.get("a", 1.0), 1.0)
|
|
372
395
|
)
|
|
373
396
|
theme.set_color(color_name, control_type, color)
|
|
374
397
|
|
|
@@ -406,7 +429,7 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
|
|
|
406
429
|
if theme == null:
|
|
407
430
|
return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
|
|
408
431
|
|
|
409
|
-
theme.set_font_size(font_size_name, control_type,
|
|
432
|
+
theme.set_font_size(font_size_name, control_type, Read.as_int(args.get("size", 0)))
|
|
410
433
|
|
|
411
434
|
var save_result: Error = ResourceSaver.save(theme, theme_path)
|
|
412
435
|
if save_result != OK:
|
|
@@ -3,6 +3,8 @@ extends Node
|
|
|
3
3
|
|
|
4
4
|
## Scenes and their nodes, edited in the open editor and read back from what it holds.
|
|
5
5
|
|
|
6
|
+
const Read = preload("../reading.gd")
|
|
7
|
+
|
|
6
8
|
var _editor_plugin: EditorPlugin = null
|
|
7
9
|
|
|
8
10
|
|
|
@@ -89,13 +91,15 @@ func _find_node(root: Node, path: String) -> Node:
|
|
|
89
91
|
## declares, or a bare array positional for a vector. Each is its own function; asking all three
|
|
90
92
|
## in one was twenty-four exits deep and impossible to follow.
|
|
91
93
|
func _parse_value(value: Variant, expected_type: int = TYPE_NIL) -> Variant:
|
|
92
|
-
if
|
|
93
|
-
var
|
|
94
|
+
if value is Dictionary:
|
|
95
|
+
var fields: Dictionary = value
|
|
96
|
+
var tagged: Array = _parse_tagged_dictionary(fields)
|
|
94
97
|
if tagged[0]:
|
|
95
98
|
return tagged[1]
|
|
96
|
-
return _parse_shaped_dictionary(
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
+
return _parse_shaped_dictionary(fields, expected_type)
|
|
100
|
+
if value is Array:
|
|
101
|
+
var items: Array = value
|
|
102
|
+
return _parse_array(items, expected_type)
|
|
99
103
|
return value
|
|
100
104
|
|
|
101
105
|
|
|
@@ -114,26 +118,53 @@ func _parse_tagged_dictionary(value: Dictionary) -> Array:
|
|
|
114
118
|
|
|
115
119
|
match type_tag:
|
|
116
120
|
"Vector2":
|
|
117
|
-
return [true, Vector2(value.get("x", 0), value.get("y", 0))]
|
|
121
|
+
return [true, Vector2(Read.as_float(value.get("x", 0)), Read.as_float(value.get("y", 0)))]
|
|
118
122
|
"Vector3":
|
|
119
|
-
return [
|
|
123
|
+
return [
|
|
124
|
+
true,
|
|
125
|
+
Vector3(
|
|
126
|
+
Read.as_float(value.get("x", 0)),
|
|
127
|
+
Read.as_float(value.get("y", 0)),
|
|
128
|
+
Read.as_float(value.get("z", 0))
|
|
129
|
+
)
|
|
130
|
+
]
|
|
120
131
|
"Color":
|
|
121
|
-
return [
|
|
132
|
+
return [
|
|
133
|
+
true,
|
|
134
|
+
Color(
|
|
135
|
+
Read.as_float(value.get("r", 1), 1.0),
|
|
136
|
+
Read.as_float(value.get("g", 1), 1.0),
|
|
137
|
+
Read.as_float(value.get("b", 1), 1.0),
|
|
138
|
+
Read.as_float(value.get("a", 1), 1.0)
|
|
139
|
+
)
|
|
140
|
+
]
|
|
122
141
|
"Vector2i":
|
|
123
|
-
return [true, Vector2i(value.get("x", 0), value.get("y", 0))]
|
|
142
|
+
return [true, Vector2i(Read.as_int(value.get("x", 0)), Read.as_int(value.get("y", 0)))]
|
|
124
143
|
"Vector3i":
|
|
125
|
-
return [
|
|
144
|
+
return [
|
|
145
|
+
true,
|
|
146
|
+
Vector3i(
|
|
147
|
+
Read.as_int(value.get("x", 0)),
|
|
148
|
+
Read.as_int(value.get("y", 0)),
|
|
149
|
+
Read.as_int(value.get("z", 0))
|
|
150
|
+
)
|
|
151
|
+
]
|
|
126
152
|
"Rect2":
|
|
127
153
|
return [
|
|
128
154
|
true,
|
|
129
|
-
Rect2(
|
|
155
|
+
Rect2(
|
|
156
|
+
Read.as_float(value.get("x", 0)),
|
|
157
|
+
Read.as_float(value.get("y", 0)),
|
|
158
|
+
Read.as_float(value.get("width", 0)),
|
|
159
|
+
Read.as_float(value.get("height", 0))
|
|
160
|
+
)
|
|
130
161
|
]
|
|
131
162
|
"Transform2D":
|
|
132
163
|
return _parse_transform2d(value)
|
|
133
164
|
"Transform3D":
|
|
134
165
|
return _parse_transform3d(value)
|
|
135
166
|
"NodePath":
|
|
136
|
-
return [true, NodePath(value.get("path", ""))]
|
|
167
|
+
return [true, NodePath(str(value.get("path", "")))]
|
|
137
168
|
"Resource":
|
|
138
169
|
var resource_path: String = str(value.get("path", ""))
|
|
139
170
|
return [true, null if resource_path.is_empty() else load(resource_path)]
|
|
@@ -172,9 +203,9 @@ func _parse_transform2d(value: Dictionary) -> Array:
|
|
|
172
203
|
return [
|
|
173
204
|
true,
|
|
174
205
|
Transform2D(
|
|
175
|
-
Vector2(basis_x.get("x", 1), basis_x.get("y", 0)),
|
|
176
|
-
Vector2(basis_y.get("x", 0), basis_y.get("y", 1)),
|
|
177
|
-
Vector2(origin.get("x", 0), origin.get("y", 0))
|
|
206
|
+
Vector2(Read.as_float(basis_x.get("x", 1), 1.0), Read.as_float(basis_x.get("y", 0))),
|
|
207
|
+
Vector2(Read.as_float(basis_y.get("x", 0)), Read.as_float(basis_y.get("y", 1), 1.0)),
|
|
208
|
+
Vector2(Read.as_float(origin.get("x", 0)), Read.as_float(origin.get("y", 0)))
|
|
178
209
|
)
|
|
179
210
|
]
|
|
180
211
|
|
|
@@ -189,11 +220,18 @@ func _parse_transform3d(value: Dictionary) -> Array:
|
|
|
189
220
|
var y: Dictionary = b.get("y", {})
|
|
190
221
|
var z: Dictionary = b.get("z", {})
|
|
191
222
|
var basis: Basis = Basis(
|
|
192
|
-
Vector3(
|
|
193
|
-
|
|
194
|
-
|
|
223
|
+
Vector3(
|
|
224
|
+
Read.as_float(x.get("x", 1), 1.0), Read.as_float(x.get("y", 0)), Read.as_float(x.get("z", 0))
|
|
225
|
+
),
|
|
226
|
+
Vector3(
|
|
227
|
+
Read.as_float(y.get("x", 0)), Read.as_float(y.get("y", 1), 1.0), Read.as_float(y.get("z", 0))
|
|
228
|
+
),
|
|
229
|
+
Vector3(Read.as_float(z.get("x", 0)), Read.as_float(z.get("y", 0)), Read.as_float(z.get("z", 1), 1.0))
|
|
230
|
+
)
|
|
231
|
+
var origin: Vector3 = Vector3(
|
|
232
|
+
Read.as_float(o.get("x", 0)), Read.as_float(o.get("y", 0)), Read.as_float(o.get("z", 0))
|
|
195
233
|
)
|
|
196
|
-
return [true, Transform3D(basis,
|
|
234
|
+
return [true, Transform3D(basis, origin)]
|
|
197
235
|
|
|
198
236
|
|
|
199
237
|
## A dictionary with no tag, read against the type the property declares. Falls back to the
|
|
@@ -202,27 +240,37 @@ func _parse_shaped_dictionary(value: Dictionary, expected_type: int) -> Variant:
|
|
|
202
240
|
match expected_type:
|
|
203
241
|
TYPE_VECTOR2:
|
|
204
242
|
if value.has("x") and value.has("y"):
|
|
205
|
-
return Vector2(
|
|
243
|
+
return Vector2(Read.as_float(value["x"]), Read.as_float(value["y"]))
|
|
206
244
|
TYPE_VECTOR2I:
|
|
207
245
|
if value.has("x") and value.has("y"):
|
|
208
|
-
return Vector2i(
|
|
246
|
+
return Vector2i(Read.as_int(value["x"]), Read.as_int(value["y"]))
|
|
209
247
|
TYPE_VECTOR3:
|
|
210
248
|
if value.has("x") and value.has("y") and value.has("z"):
|
|
211
|
-
return Vector3(
|
|
249
|
+
return Vector3(
|
|
250
|
+
Read.as_float(value["x"]), Read.as_float(value["y"]), Read.as_float(value["z"])
|
|
251
|
+
)
|
|
212
252
|
TYPE_VECTOR3I:
|
|
213
253
|
if value.has("x") and value.has("y") and value.has("z"):
|
|
214
|
-
return Vector3i(
|
|
254
|
+
return Vector3i(Read.as_int(value["x"]), Read.as_int(value["y"]), Read.as_int(value["z"]))
|
|
215
255
|
TYPE_COLOR:
|
|
216
256
|
if value.has("r") and value.has("g") and value.has("b"):
|
|
217
|
-
return Color(
|
|
257
|
+
return Color(
|
|
258
|
+
Read.as_float(value["r"]),
|
|
259
|
+
Read.as_float(value["g"]),
|
|
260
|
+
Read.as_float(value["b"]),
|
|
261
|
+
Read.as_float(value.get("a", 1), 1.0)
|
|
262
|
+
)
|
|
218
263
|
TYPE_RECT2:
|
|
219
264
|
if value.has("x") and value.has("y") and value.has("width") and value.has("height"):
|
|
220
265
|
return Rect2(
|
|
221
|
-
|
|
266
|
+
Read.as_float(value["x"]),
|
|
267
|
+
Read.as_float(value["y"]),
|
|
268
|
+
Read.as_float(value["width"]),
|
|
269
|
+
Read.as_float(value["height"])
|
|
222
270
|
)
|
|
223
271
|
TYPE_NODE_PATH:
|
|
224
272
|
if value.has("path"):
|
|
225
|
-
return NodePath(value
|
|
273
|
+
return NodePath(str(value["path"]))
|
|
226
274
|
return value
|
|
227
275
|
|
|
228
276
|
|
|
@@ -231,23 +279,23 @@ func _parse_array(value: Array, expected_type: int) -> Variant:
|
|
|
231
279
|
match expected_type:
|
|
232
280
|
TYPE_VECTOR2:
|
|
233
281
|
if value.size() >= 2:
|
|
234
|
-
return Vector2(value[0], value[1])
|
|
282
|
+
return Vector2(Read.as_float(value[0]), Read.as_float(value[1]))
|
|
235
283
|
TYPE_VECTOR2I:
|
|
236
284
|
if value.size() >= 2:
|
|
237
|
-
return Vector2i(value[0], value[1])
|
|
285
|
+
return Vector2i(Read.as_int(value[0]), Read.as_int(value[1]))
|
|
238
286
|
TYPE_VECTOR3:
|
|
239
287
|
if value.size() >= 3:
|
|
240
|
-
return Vector3(value[0], value[1], value[2])
|
|
288
|
+
return Vector3(Read.as_float(value[0]), Read.as_float(value[1]), Read.as_float(value[2]))
|
|
241
289
|
TYPE_VECTOR3I:
|
|
242
290
|
if value.size() >= 3:
|
|
243
|
-
return Vector3i(value[0], value[1], value[2])
|
|
291
|
+
return Vector3i(Read.as_int(value[0]), Read.as_int(value[1]), Read.as_int(value[2]))
|
|
244
292
|
return value.map(func(item: Variant) -> Variant: return _parse_value(item))
|
|
245
293
|
|
|
246
294
|
|
|
247
295
|
func _get_property_type(node: Node, prop_name: String) -> int:
|
|
248
296
|
for prop: Dictionary in node.get_property_list():
|
|
249
297
|
if str(prop.get("name", "")) == prop_name:
|
|
250
|
-
return
|
|
298
|
+
return Read.as_int(prop.get("type", TYPE_NIL), TYPE_NIL)
|
|
251
299
|
return TYPE_NIL
|
|
252
300
|
|
|
253
301
|
|
|
@@ -316,7 +364,7 @@ func _set_node_properties(node: Node, properties: Dictionary) -> String:
|
|
|
316
364
|
# A resource-valued property takes the path of one, which is how a caller names a
|
|
317
365
|
# TileSet, a material or a theme: there is no other way to hand a tool a Resource.
|
|
318
366
|
if expected_type == TYPE_OBJECT and typeof(raw) == TYPE_STRING:
|
|
319
|
-
var path: String =
|
|
367
|
+
var path: String = str(raw)
|
|
320
368
|
# The project boundary is enforced here as well as on the server, because only the
|
|
321
369
|
# engine knows that this property is one holding a path: an absolute path and a
|
|
322
370
|
# user:// one both load, and neither names a file this project owns.
|
|
@@ -344,7 +392,7 @@ func _parse_properties_arg(raw_properties: Variant) -> Dictionary:
|
|
|
344
392
|
if typeof(raw_properties) == TYPE_DICTIONARY:
|
|
345
393
|
return raw_properties
|
|
346
394
|
if typeof(raw_properties) == TYPE_STRING:
|
|
347
|
-
var text: String =
|
|
395
|
+
var text: String = str(raw_properties)
|
|
348
396
|
if text.strip_edges().is_empty():
|
|
349
397
|
return {}
|
|
350
398
|
var parsed: Variant = JSON.parse_string(text)
|
|
@@ -356,7 +404,9 @@ func _parse_properties_arg(raw_properties: Variant) -> Dictionary:
|
|
|
356
404
|
func _ensure_parent_dir_for_scene(scene_path: String) -> void:
|
|
357
405
|
var base_dir: String = scene_path.get_base_dir()
|
|
358
406
|
if not DirAccess.dir_exists_absolute(base_dir):
|
|
359
|
-
DirAccess.make_dir_recursive_absolute(base_dir)
|
|
407
|
+
var made: Error = DirAccess.make_dir_recursive_absolute(base_dir)
|
|
408
|
+
if made != OK:
|
|
409
|
+
push_error("gdharness: could not create " + base_dir + ": " + error_string(made))
|
|
360
410
|
|
|
361
411
|
|
|
362
412
|
func _set_owner_recursive(node: Node, scene_owner: Node) -> void:
|
|
@@ -376,7 +426,7 @@ func _build_node_tree(
|
|
|
376
426
|
if include_properties:
|
|
377
427
|
var props: Dictionary = {}
|
|
378
428
|
for p: Dictionary in node.get_property_list():
|
|
379
|
-
if not (
|
|
429
|
+
if not (Read.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
|
|
380
430
|
continue
|
|
381
431
|
var pn: String = str(p.get("name", ""))
|
|
382
432
|
if pn.is_empty():
|
|
@@ -439,8 +489,8 @@ func create_scene(args: Dictionary) -> Dictionary:
|
|
|
439
489
|
func list_scene_nodes(args: Dictionary) -> Dictionary:
|
|
440
490
|
var project_path: String = str(args.get("projectPath", ""))
|
|
441
491
|
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
442
|
-
var depth: int =
|
|
443
|
-
var include_properties: bool =
|
|
492
|
+
var depth: int = Read.as_int(args.get("depth", -1), -1)
|
|
493
|
+
var include_properties: bool = Read.as_bool(args.get("includeProperties", false))
|
|
444
494
|
|
|
445
495
|
var loaded: Array = _load_scene(scene_path)
|
|
446
496
|
var refused: Dictionary = loaded[1]
|
|
@@ -652,7 +702,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
652
702
|
var project_path: String = str(args.get("projectPath", ""))
|
|
653
703
|
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
654
704
|
var node_path: String = str(args.get("nodePath", "."))
|
|
655
|
-
var include_defaults: bool =
|
|
705
|
+
var include_defaults: bool = Read.as_bool(args.get("includeDefaults", false))
|
|
656
706
|
|
|
657
707
|
var loaded: Array = _load_scene(scene_path)
|
|
658
708
|
var refused: Dictionary = loaded[1]
|
|
@@ -671,7 +721,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
671
721
|
|
|
672
722
|
var props: Dictionary = {}
|
|
673
723
|
for p: Dictionary in node.get_property_list():
|
|
674
|
-
var usage: int =
|
|
724
|
+
var usage: int = Read.as_int(p.get("usage", 0))
|
|
675
725
|
if not (usage & PROPERTY_USAGE_STORAGE):
|
|
676
726
|
continue
|
|
677
727
|
var prop_name: String = str(p.get("name", ""))
|
|
@@ -721,7 +771,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
721
771
|
var method_name: String = str(args.get("methodName", ""))
|
|
722
772
|
# A connection without CONNECT_PERSIST is a runtime one, and PackedScene.pack drops those on
|
|
723
773
|
# the way out: without this the scene saves unchanged and this answers success over nothing.
|
|
724
|
-
var flags: int =
|
|
774
|
+
var flags: int = Read.as_int(args.get("flags", 0)) | Object.CONNECT_PERSIST
|
|
725
775
|
|
|
726
776
|
if (
|
|
727
777
|
source_node_path.is_empty()
|
|
@@ -854,7 +904,7 @@ func list_connections(args: Dictionary) -> Dictionary:
|
|
|
854
904
|
"signalName": signal_name,
|
|
855
905
|
"targetNodePath": target_path,
|
|
856
906
|
"methodName": str(callable.get_method()),
|
|
857
|
-
"flags":
|
|
907
|
+
"flags": Read.as_int(conn.get("flags", 0))
|
|
858
908
|
}
|
|
859
909
|
)
|
|
860
910
|
|
|
@@ -877,7 +927,7 @@ func rescan_filesystem(args: Dictionary) -> Dictionary:
|
|
|
877
927
|
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
878
928
|
|
|
879
929
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
880
|
-
if not
|
|
930
|
+
if not Read.as_bool(args.get("statusOnly", false)):
|
|
881
931
|
filesystem.scan()
|
|
882
932
|
|
|
883
933
|
# Importing is reported separately from scanning, and a class is not registered until
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
# A value out of a request that arrived as JSON, as the type the caller wants.
|
|
4
|
+
#
|
|
5
|
+
# `int(value)`, `float(value)` and `bool(value)` take a Variant, and a project holding
|
|
6
|
+
# `unsafe_call_argument` at error level will not compile a script that hands one over. The
|
|
7
|
+
# conversion is the same; what changes is that the type is established first, on a typed local, and
|
|
8
|
+
# the conversion happens from that.
|
|
9
|
+
#
|
|
10
|
+
# A copy rather than a preload of the one beside the operations, because an addon is installed as a
|
|
11
|
+
# directory and has to hold everything it uses. This one ships inside exported games, so it must not
|
|
12
|
+
# reach into the editor addon, which does not.
|
|
13
|
+
#
|
|
14
|
+
# `fallback` answers a value that is there and is null, which is a caller sending JSON null where a
|
|
15
|
+
# number was expected. Absent keys never reach here: the callers pass their own default to
|
|
16
|
+
# `Dictionary.get`, so what arrives is that default.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
static func as_int(value: Variant, fallback: int = 0) -> int:
|
|
20
|
+
if value is int:
|
|
21
|
+
var already: int = value
|
|
22
|
+
return already
|
|
23
|
+
if value is float:
|
|
24
|
+
var real: float = value
|
|
25
|
+
return int(real)
|
|
26
|
+
if value is bool:
|
|
27
|
+
var flag: bool = value
|
|
28
|
+
return 1 if flag else 0
|
|
29
|
+
if value is String:
|
|
30
|
+
var text: String = value
|
|
31
|
+
return text.to_int()
|
|
32
|
+
return fallback
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
static func as_float(value: Variant, fallback: float = 0.0) -> float:
|
|
36
|
+
if value is float:
|
|
37
|
+
var already: float = value
|
|
38
|
+
return already
|
|
39
|
+
if value is int:
|
|
40
|
+
var whole: int = value
|
|
41
|
+
return float(whole)
|
|
42
|
+
if value is bool:
|
|
43
|
+
var flag: bool = value
|
|
44
|
+
return 1.0 if flag else 0.0
|
|
45
|
+
if value is String:
|
|
46
|
+
var text: String = value
|
|
47
|
+
return text.to_float()
|
|
48
|
+
return fallback
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
static func as_bool(value: Variant, fallback: bool = false) -> bool:
|
|
52
|
+
if value is bool:
|
|
53
|
+
var already: bool = value
|
|
54
|
+
return already
|
|
55
|
+
if value is int:
|
|
56
|
+
var whole: int = value
|
|
57
|
+
return whole != 0
|
|
58
|
+
if value is float:
|
|
59
|
+
var real: float = value
|
|
60
|
+
return not is_zero_approx(real)
|
|
61
|
+
if value is String:
|
|
62
|
+
var text: String = value
|
|
63
|
+
return not text.is_empty()
|
|
64
|
+
return fallback
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://ch24s3ttpunch
|