gdharness 1.2.41 → 1.3.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 +643 -378
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -4
- package/build/godot/addons/gdharness_editor/bridge_client.gd +1 -1
- package/build/godot/addons/gdharness_editor/file_reads.gd +134 -10
- package/build/godot/addons/gdharness_editor/headless_check.gd +34 -4
- package/build/godot/addons/gdharness_editor/property_values.gd +4 -9
- package/build/godot/addons/gdharness_editor/reading.gd +25 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +209 -15
- package/build/godot/addons/gdharness_editor/serialisation.gd +5 -1
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +9 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +346 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +438 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +91 -49
- package/build/godot/addons/gdharness_runtime/reading.gd +25 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +29 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +2 -7
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -14
- package/build/godot/addons/gdharness_runtime/serialisation.gd +5 -1
- package/build/godot/operations/animation_reads.gd +7 -4
- package/build/godot/operations/editor_tools.gd +19 -1
- package/build/godot/operations/file_reads.gd +134 -10
- package/build/godot/operations/property_values.gd +4 -9
- package/build/godot/operations/reading.gd +25 -0
- package/build/godot/operations/serialisation.gd +5 -1
- package/build/keeper.js +10 -4
- package/package.json +1 -2
- package/build/index.js +0 -45043
|
@@ -62,10 +62,13 @@ static func _bare(scene_path: String, node_path: String, kind: String) -> Array:
|
|
|
62
62
|
var type: String = str(read.get("type", ""))
|
|
63
63
|
if not ClassDB.class_exists(type) or not ClassDB.is_parent_class(type, kind):
|
|
64
64
|
var said: String = "%s not found at: %s" % [kind, node_path]
|
|
65
|
-
if read.is_empty()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
if read.is_empty():
|
|
66
|
+
var hinted: String = said
|
|
67
|
+
if FileReads.scene_node(scene_path, node_path.get_slice("/", 0)).is_empty():
|
|
68
|
+
hinted = Read.from_the_root(
|
|
69
|
+
said, node_path, str(FileReads.scene_node(scene_path, ".").get("name", ""))
|
|
70
|
+
)
|
|
71
|
+
said = hinted if hinted != said else FileReads.missing_from(said, scene_path, node_path)
|
|
69
72
|
return [null, {"ok": false, "error": said}]
|
|
70
73
|
var bare: Node = ClassDB.instantiate(type)
|
|
71
74
|
bare.name = str(read["name"])
|
|
@@ -518,7 +518,7 @@ func _handle_message(json_string: String) -> void:
|
|
|
518
518
|
|
|
519
519
|
|
|
520
520
|
## Says [param tool_name] was not run because the editor is scanning or importing, for the server to
|
|
521
|
-
## ask again once that is over.
|
|
521
|
+
## ask again once that is over.
|
|
522
522
|
func send_busy(request_id: String, tool_name: String) -> void:
|
|
523
523
|
_send_message(
|
|
524
524
|
{
|
|
@@ -15,9 +15,10 @@ const Read = preload("reading.gd")
|
|
|
15
15
|
const Serialisation = preload("serialisation.gd")
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf
|
|
19
|
-
## instances another scene and every stored property when
|
|
20
|
-
## file's value or the default where it sets none.
|
|
18
|
+
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf and
|
|
19
|
+
## editableChildren on a node that instances another scene and every stored property when
|
|
20
|
+
## [param include_properties] is set, the file's value or the default where it sets none.
|
|
21
|
+
## [param depth] -1 reads every level.
|
|
21
22
|
static func scene_tree(scene_path: String, depth: int, include_properties: bool) -> Dictionary:
|
|
22
23
|
var laid: Dictionary = _laid(scene_path)
|
|
23
24
|
if not laid.get("ok", false):
|
|
@@ -41,6 +42,31 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
|
|
|
41
42
|
return {"ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"]}
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
## [param said], the refusal for [param node_path], which [param scene_path] does not have, with
|
|
46
|
+
## where the path leaves the scene as [method Read.missing_below] says it.
|
|
47
|
+
static func missing_from(said: String, scene_path: String, node_path: String) -> String:
|
|
48
|
+
var laid: Dictionary = _laid(scene_path)
|
|
49
|
+
if not laid.get("ok", false):
|
|
50
|
+
return said
|
|
51
|
+
var nodes: Dictionary = laid["nodes"]
|
|
52
|
+
var parts: PackedStringArray = node_path.trim_prefix("./").split("/", false)
|
|
53
|
+
var at: String = "."
|
|
54
|
+
var reached: PackedStringArray = []
|
|
55
|
+
for part: String in parts:
|
|
56
|
+
var below: String = _joined(at, part)
|
|
57
|
+
if not nodes.has(below):
|
|
58
|
+
break
|
|
59
|
+
at = below
|
|
60
|
+
var _walked: bool = reached.append(part)
|
|
61
|
+
if reached.size() >= parts.size():
|
|
62
|
+
return said
|
|
63
|
+
var names: PackedStringArray = []
|
|
64
|
+
var children: Array[String] = nodes[at]["children"]
|
|
65
|
+
for child: String in children:
|
|
66
|
+
var _named: bool = names.append(str(nodes[child]["name"]))
|
|
67
|
+
return Read.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
|
|
68
|
+
|
|
69
|
+
|
|
44
70
|
## Every node of [param scene_path] as {ok, nodes}, keyed by path, or a refusal.
|
|
45
71
|
static func _laid(scene_path: String) -> Dictionary:
|
|
46
72
|
if not FileAccess.file_exists(scene_path):
|
|
@@ -51,13 +77,11 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
51
77
|
if loaded == null:
|
|
52
78
|
return {"ok": false, "error": "Failed to load %s as a scene" % scene_path}
|
|
53
79
|
if not loaded is PackedScene:
|
|
80
|
+
var kind: String = loaded.get_class()
|
|
81
|
+
var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
|
|
54
82
|
return {
|
|
55
83
|
"ok": false,
|
|
56
|
-
"error":
|
|
57
|
-
(
|
|
58
|
-
"%s is a %s, not a scene: resource_edit read reads its properties."
|
|
59
|
-
% [scene_path, loaded.get_class()]
|
|
60
|
-
)
|
|
84
|
+
"error": "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort]
|
|
61
85
|
}
|
|
62
86
|
var packed: PackedScene = loaded
|
|
63
87
|
var nodes: Dictionary = {}
|
|
@@ -82,8 +106,18 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
82
106
|
var refused: String = _lay(base, prefix, nodes, within)
|
|
83
107
|
if not refused.is_empty():
|
|
84
108
|
return refused
|
|
109
|
+
var ids: Array[int] = _unique_ids(state.get_path(), state.get_node_count())
|
|
85
110
|
for index: int in state.get_node_count():
|
|
86
111
|
var path: String = _joined(prefix, str(state.get_node_path(index)))
|
|
112
|
+
var recovered: bool = false
|
|
113
|
+
if not nodes.has(path) and index > 0 and _overrides(state, index) and ids[index] >= 0:
|
|
114
|
+
# The engine finds an entry whose node was renamed or moved in the scene it comes from by
|
|
115
|
+
# the node's unique_id, and keeps the node's new name.
|
|
116
|
+
var parent: String = _joined(prefix, str(state.get_node_path(index, true)))
|
|
117
|
+
var found: String = _by_id(nodes, _owner_of(nodes, parent, prefix), ids[index])
|
|
118
|
+
recovered = not found.is_empty()
|
|
119
|
+
if recovered:
|
|
120
|
+
path = found
|
|
87
121
|
var fresh: bool = not nodes.has(path)
|
|
88
122
|
if fresh:
|
|
89
123
|
var instance: PackedScene = state.get_node_instance(index)
|
|
@@ -98,6 +132,7 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
98
132
|
if not nodes.has(path):
|
|
99
133
|
return "%s holds no nodes" % instance.resource_path
|
|
100
134
|
nodes[path]["instanceOf"] = instance.resource_path
|
|
135
|
+
nodes[path]["editableChildren"] = false
|
|
101
136
|
elif state.is_node_instance_placeholder(index):
|
|
102
137
|
nodes[path] = _made("InstancePlaceholder")
|
|
103
138
|
nodes[path]["instanceOf"] = state.get_node_instance_placeholder(index)
|
|
@@ -106,9 +141,12 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
106
141
|
continue
|
|
107
142
|
else:
|
|
108
143
|
nodes[path] = _made(str(state.get_node_type(index)))
|
|
144
|
+
if ids[index] >= 0:
|
|
145
|
+
nodes[path]["uniqueId"] = ids[index]
|
|
109
146
|
# The name is the state's even for an entry laid already, since an inherited scene names its
|
|
110
147
|
# root itself; a root instanced inside another scene is renamed by that scene once laid.
|
|
111
|
-
|
|
148
|
+
if not recovered:
|
|
149
|
+
nodes[path]["name"] = str(state.get_node_name(index))
|
|
112
150
|
if fresh:
|
|
113
151
|
# The root of what is laid here is placed by whoever instanced it.
|
|
114
152
|
if path != prefix:
|
|
@@ -123,6 +161,86 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
123
161
|
held[str(state.get_node_property_name(index, property))] = state.get_node_property_value(
|
|
124
162
|
index, property
|
|
125
163
|
)
|
|
164
|
+
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
165
|
+
# instances a write here sets; a file instanced below makes its own owner's editable.
|
|
166
|
+
if prefix == ".":
|
|
167
|
+
for editable: String in _editable_in(state.get_path()):
|
|
168
|
+
var at: String = _joined(prefix, editable)
|
|
169
|
+
var marked: Dictionary = nodes.get(at, {})
|
|
170
|
+
if marked.has("editableChildren"):
|
|
171
|
+
marked["editableChildren"] = true
|
|
172
|
+
return ""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## The paths of the instances [param scene_path] gives editable children, from its root. SceneState
|
|
176
|
+
## does not give them to a script, so they are read off the scene's bundle.
|
|
177
|
+
static func _editable_in(scene_path: String) -> PackedStringArray:
|
|
178
|
+
var found: PackedStringArray = []
|
|
179
|
+
var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
180
|
+
if not loaded is PackedScene:
|
|
181
|
+
return found
|
|
182
|
+
var given: Variant = loaded.get("_bundled")
|
|
183
|
+
if not given is Dictionary:
|
|
184
|
+
return found
|
|
185
|
+
var bundled: Dictionary = given
|
|
186
|
+
var listed: Variant = bundled.get("editable_instances", [])
|
|
187
|
+
if listed is Array:
|
|
188
|
+
for one: Variant in listed:
|
|
189
|
+
var _kept: bool = found.append(str(one))
|
|
190
|
+
return found
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
## The unique_id of each node entry in the text scene at [param scene_path], in the order its state
|
|
194
|
+
## holds them, -1 where an entry has none. All -1 for a binary scene, and for a file whose entries do
|
|
195
|
+
## not number [param count], since SceneState does not give the ids to a script.
|
|
196
|
+
static func _unique_ids(scene_path: String, count: int) -> Array[int]:
|
|
197
|
+
var ids: Array[int] = []
|
|
198
|
+
var _sized: int = ids.resize(count)
|
|
199
|
+
ids.fill(-1)
|
|
200
|
+
if not scene_path.ends_with(".tscn") or not FileAccess.file_exists(scene_path):
|
|
201
|
+
return ids
|
|
202
|
+
var read: Array[int] = []
|
|
203
|
+
var pattern: RegEx = RegEx.create_from_string(" unique_id=(\\d+)")
|
|
204
|
+
for line: String in FileAccess.get_file_as_string(scene_path).split("\n"):
|
|
205
|
+
if line.begins_with("[node "):
|
|
206
|
+
var found: RegExMatch = pattern.search(line)
|
|
207
|
+
read.append(int(found.get_string(1)) if found != null else -1)
|
|
208
|
+
return read if read.size() == count else ids
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## Whether entry [param index] of [param state] only sets values on a node another file makes.
|
|
212
|
+
static func _overrides(state: SceneState, index: int) -> bool:
|
|
213
|
+
return (
|
|
214
|
+
state.get_node_instance(index) == null
|
|
215
|
+
and not state.is_node_instance_placeholder(index)
|
|
216
|
+
and str(state.get_node_type(index)).is_empty()
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
## The node whose file owns what sits under [param parent]: the nearest instance at or above it, or
|
|
221
|
+
## [param prefix], the root of what is being laid, as the engine searches.
|
|
222
|
+
static func _owner_of(nodes: Dictionary, parent: String, prefix: String) -> String:
|
|
223
|
+
var at: String = parent
|
|
224
|
+
while at != prefix and at != "." and nodes.has(at):
|
|
225
|
+
var entry: Dictionary = nodes[at]
|
|
226
|
+
if entry.has("instanceOf"):
|
|
227
|
+
break
|
|
228
|
+
at = "." if not at.contains("/") else at.get_base_dir()
|
|
229
|
+
return at if nodes.has(at) else prefix
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## The path of the node [param owner] owns whose unique_id is [param id], or "": one under an instance
|
|
233
|
+
## nested below [param owner] belongs to that instance's file, whose ids are numbered apart.
|
|
234
|
+
static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
|
|
235
|
+
for path: String in nodes:
|
|
236
|
+
if path == owner or not (owner == "." or path.begins_with(owner + "/")):
|
|
237
|
+
continue
|
|
238
|
+
var entry: Dictionary = nodes[path]
|
|
239
|
+
if Read.as_int(entry.get("uniqueId", -1), -1) != id:
|
|
240
|
+
continue
|
|
241
|
+
var above: String = "." if not path.contains("/") else path.get_base_dir()
|
|
242
|
+
if _owner_of(nodes, above, owner) == owner:
|
|
243
|
+
return path
|
|
126
244
|
return ""
|
|
127
245
|
|
|
128
246
|
|
|
@@ -162,12 +280,18 @@ static func _entry(
|
|
|
162
280
|
# The root's file is the scene being read, so only another node's names a second scene.
|
|
163
281
|
if path != "." and node.has("instanceOf"):
|
|
164
282
|
data["instanceOf"] = node["instanceOf"]
|
|
283
|
+
if node.has("editableChildren"):
|
|
284
|
+
data["editableChildren"] = node["editableChildren"]
|
|
165
285
|
if include_properties:
|
|
166
286
|
var given: Dictionary = node["properties"]
|
|
167
287
|
data["properties"] = _stored(str(node["type"]), given)
|
|
288
|
+
var below: Array[String] = node["children"]
|
|
289
|
+
# Counted rather than left as an empty list, which reads as a node with no children.
|
|
168
290
|
if depth >= 0 and current_depth >= depth:
|
|
291
|
+
if not below.is_empty():
|
|
292
|
+
data["childrenNotListed"] = below.size()
|
|
169
293
|
return data
|
|
170
|
-
for child: String in
|
|
294
|
+
for child: String in below:
|
|
171
295
|
children.append(_entry(nodes, child, include_properties, depth, current_depth + 1))
|
|
172
296
|
return data
|
|
173
297
|
|
|
@@ -248,7 +248,8 @@ static func entries(lines: PackedStringArray) -> Dictionary:
|
|
|
248
248
|
|
|
249
249
|
|
|
250
250
|
## Each property the main resource of [param after] sets differently from [param before], leaving
|
|
251
|
-
## out [param asked], as [name, whether before sets it, the text before gives it
|
|
251
|
+
## out [param asked], as [name, whether before sets it, the text before gives it, the text after
|
|
252
|
+
## gives it or null].
|
|
252
253
|
static func main_changes(before: String, after: String, asked: Dictionary) -> Array[Array]:
|
|
253
254
|
var was: Dictionary = _main_entries(before)
|
|
254
255
|
var now: Dictionary = _main_entries(after)
|
|
@@ -259,10 +260,25 @@ static func main_changes(before: String, after: String, asked: Dictionary) -> Ar
|
|
|
259
260
|
var name: String = str(key)
|
|
260
261
|
if asked.has(name) or was.get(name) == now.get(name):
|
|
261
262
|
continue
|
|
262
|
-
changed.append([name, was.has(name), str(was.get(name, ""))])
|
|
263
|
+
changed.append([name, was.has(name), str(was.get(name, "")), now.get(name)])
|
|
263
264
|
return changed
|
|
264
265
|
|
|
265
266
|
|
|
267
|
+
## Whether [param was] and [param now], the texts two files give a property or null where one sets
|
|
268
|
+
## nothing, read as the same value, nothing standing for [param default]. A file written by hand
|
|
269
|
+
## spells a value its own way, 1 for 1.0, and may set a default, which the saver leaves out.
|
|
270
|
+
static func reads_the_same(was: Variant, now: Variant, default: Variant) -> bool:
|
|
271
|
+
var values: Array = []
|
|
272
|
+
for text: Variant in [was, now]:
|
|
273
|
+
if text == null:
|
|
274
|
+
values.append(default)
|
|
275
|
+
elif "Resource(" in str(text):
|
|
276
|
+
return false
|
|
277
|
+
else:
|
|
278
|
+
values.append(str_to_var(str(text)))
|
|
279
|
+
return PropertyValues.same(values[0], values[1])
|
|
280
|
+
|
|
281
|
+
|
|
266
282
|
static func _main_entries(text: String) -> Dictionary:
|
|
267
283
|
var main: Dictionary = sections(text).get("resource", {})
|
|
268
284
|
return entries_of(main)
|
|
@@ -298,7 +314,8 @@ static func scripted_sections(text: String) -> Dictionary:
|
|
|
298
314
|
|
|
299
315
|
|
|
300
316
|
## Each property an embedded resource of [param after] sets differently from [param before], as
|
|
301
|
-
## [its id, name, whether before sets it, the text before gives it
|
|
317
|
+
## [its id, name, whether before sets it, the text before gives it, the text after gives it or
|
|
318
|
+
## null]. Only those with a script that
|
|
302
319
|
## is not a tool are compared, since only a script runs differently here, and those in
|
|
303
320
|
## [param reached_ids], which the tool changed, are left out.
|
|
304
321
|
static func embedded_changes(before: String, after: String, reached_ids: Dictionary) -> Array[Array]:
|
|
@@ -321,7 +338,7 @@ static func embedded_changes(before: String, after: String, reached_ids: Diction
|
|
|
321
338
|
for entry: Variant in all:
|
|
322
339
|
var name: String = str(entry)
|
|
323
340
|
if was.get(name) != now.get(name):
|
|
324
|
-
changed.append([id, name, was.has(name), str(was.get(name, ""))])
|
|
341
|
+
changed.append([id, name, was.has(name), str(was.get(name, "")), now.get(name)])
|
|
325
342
|
return changed
|
|
326
343
|
|
|
327
344
|
|
|
@@ -345,11 +362,15 @@ static func settle_saved(
|
|
|
345
362
|
var embedded: Array[Array] = []
|
|
346
363
|
for found: Array in embedded_changes(before, after, reached_ids):
|
|
347
364
|
var holder: Resource = by_id.get(found[0])
|
|
365
|
+
if holder != null and _unchanged(holder, found.slice(1)):
|
|
366
|
+
continue
|
|
348
367
|
if found[2] or holder == null or not at_default(holder, str(found[1])):
|
|
349
368
|
embedded.append(found)
|
|
350
369
|
var top: Array[Array] = []
|
|
351
370
|
if main != null:
|
|
352
371
|
for found: Array in main_changes(before, after, asked):
|
|
372
|
+
if _unchanged(main, found):
|
|
373
|
+
continue
|
|
353
374
|
if found[1] or not at_default(main, str(found[0])):
|
|
354
375
|
top.append(found)
|
|
355
376
|
if embedded.is_empty() and top.is_empty():
|
|
@@ -382,6 +403,15 @@ static func settle_saved(
|
|
|
382
403
|
return ""
|
|
383
404
|
|
|
384
405
|
|
|
406
|
+
## Whether the saved text gives a property of [param object] what the file did, spelt another way.
|
|
407
|
+
## [param change] is [name, whether the file set it, the text it gave, the text after or null].
|
|
408
|
+
static func _unchanged(object: Object, change: Array) -> bool:
|
|
409
|
+
var name: String = change[0]
|
|
410
|
+
var was_set: bool = change[1]
|
|
411
|
+
var was: Variant = change[2] if was_set else null
|
|
412
|
+
return reads_the_same(was, change[3], default_of(object, name))
|
|
413
|
+
|
|
414
|
+
|
|
385
415
|
## Every embedded resource [param values] reach, at any depth, by its id in the file.
|
|
386
416
|
static func embedded_in(values: Array, found: Dictionary) -> Dictionary:
|
|
387
417
|
for value: Variant in values:
|
|
@@ -600,18 +600,13 @@ func shown(value: Variant) -> String:
|
|
|
600
600
|
## not mine". A tag that names a type but lacks the keys to build it is left unhandled on purpose,
|
|
601
601
|
## so the caller can still read it against the type the property declares.
|
|
602
602
|
func _parse_tagged_dictionary(value: Dictionary) -> Array:
|
|
603
|
-
var type_tag: String = str(value.get("_type",
|
|
603
|
+
var type_tag: String = str(value.get("_type", ""))
|
|
604
604
|
if type_tag == "Resource":
|
|
605
605
|
return _parse_resource_tag(value)
|
|
606
606
|
|
|
607
607
|
# The shared builder is the same one the answer was written by, so every shape it writes comes
|
|
608
|
-
# back as itself.
|
|
609
|
-
|
|
610
|
-
var tagged: Dictionary = value
|
|
611
|
-
if not value.has("_type") and not type_tag.is_empty():
|
|
612
|
-
tagged = value.duplicate()
|
|
613
|
-
tagged["_type"] = type_tag
|
|
614
|
-
var rebuilt: Variant = _values.deserialize_value(tagged)
|
|
608
|
+
# back as itself.
|
|
609
|
+
var rebuilt: Variant = _values.deserialize_value(value)
|
|
615
610
|
if not rebuilt is Dictionary:
|
|
616
611
|
return [true, rebuilt]
|
|
617
612
|
|
|
@@ -660,7 +655,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
660
655
|
var wanted: Dictionary = {}
|
|
661
656
|
for key: Variant in value:
|
|
662
657
|
var property: String = str(key)
|
|
663
|
-
if property != "_type"
|
|
658
|
+
if property != "_type":
|
|
664
659
|
wanted[property] = value[key]
|
|
665
660
|
var refused: String = write_all(built, wanted)
|
|
666
661
|
if not refused.is_empty():
|
|
@@ -22,6 +22,9 @@ extends RefCounted
|
|
|
22
22
|
# number was expected. Absent keys never reach here: the callers pass their own default to
|
|
23
23
|
# `Dictionary.get`, so what arrives is that default.
|
|
24
24
|
|
|
25
|
+
## How many of a node's children a refusal for a path missing below it names.
|
|
26
|
+
const NAMED_CHILDREN: int = 12
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
static func as_int(value: Variant, fallback: int = 0) -> int:
|
|
27
30
|
if value is int:
|
|
@@ -114,3 +117,25 @@ static func from_the_root(said: String, node_path: String, root_name: String) ->
|
|
|
114
117
|
if not rest.is_empty():
|
|
115
118
|
how += ', so this one is "%s"' % rest
|
|
116
119
|
return "%s%s %s." % [said, "" if said.ends_with(".") else ".", how]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## [param said], the refusal for a path a scene does not have, with where the path leaves it: the
|
|
123
|
+
## last node of the path it has, [param reached] from the root, called [param root_name], when that
|
|
124
|
+
## is empty; the name below it that is missing; and [param children], the names that node has, as
|
|
125
|
+
## after a node was moved or renamed. Names up to [constant NAMED_CHILDREN] of them.
|
|
126
|
+
static func missing_below(
|
|
127
|
+
said: String, root_name: String, reached: PackedStringArray, missing: String, children: PackedStringArray
|
|
128
|
+
) -> String:
|
|
129
|
+
var shown: String = ", ".join(children.slice(0, NAMED_CHILDREN))
|
|
130
|
+
if children.size() > NAMED_CHILDREN:
|
|
131
|
+
shown += " and %d more" % (children.size() - NAMED_CHILDREN)
|
|
132
|
+
return (
|
|
133
|
+
"%s%s %s has no %s; %s."
|
|
134
|
+
% [
|
|
135
|
+
said,
|
|
136
|
+
"" if said.ends_with(".") else ".",
|
|
137
|
+
"The root, %s," % root_name if reached.is_empty() else "/".join(reached),
|
|
138
|
+
missing,
|
|
139
|
+
"its children are " + shown if not children.is_empty() else "it has no children",
|
|
140
|
+
]
|
|
141
|
+
)
|