gdharness 1.3.8 → 1.3.10
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 +4668 -3229
- package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
- package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +115 -18
- package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
- package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
- package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
- package/build/godot/addons/gdharness_editor/reading.gd +112 -6
- package/build/godot/addons/gdharness_editor/scene_file.gd +117 -174
- package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
- package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +19 -29
- package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
- package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +39 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +263 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -414
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +162 -483
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +8 -4
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +151 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +190 -38
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
- package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +112 -6
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +188 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +107 -104
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +320 -27
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +165 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +31 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +250 -10
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +19 -29
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
- package/build/godot/operations/animation_reads.gd +115 -4
- package/build/godot/operations/audio_buses.gd +93 -7
- package/build/godot/operations/classdb_queries.gd +119 -7
- package/build/godot/operations/declared_values.gd +272 -0
- package/build/godot/operations/dependencies.gd +154 -41
- package/build/godot/operations/editor_tools.gd +29 -13
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +115 -18
- package/build/godot/operations/gdscript_analysis.gd +14 -0
- package/build/godot/operations/gdscript_authoring.gd +12 -2
- package/build/godot/operations/godot_operations.gd +18 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +127 -51
- package/build/godot/operations/project_config.gd +113 -68
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +266 -0
- package/build/godot/operations/property_paths.gd +89 -16
- package/build/godot/operations/property_values.gd +180 -121
- package/build/godot/operations/reading.gd +112 -6
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +19 -29
- package/build/godot/operations/setting_reads.gd +121 -0
- package/build/godot/operations/value_shapes.gd +128 -1
- package/build/godot/tests/listed.gd +20 -13
- package/build/keeper.js +1402 -707
- package/package.json +1 -1
|
@@ -16,15 +16,19 @@ const GdhSerialisation = preload("serialisation.gd")
|
|
|
16
16
|
|
|
17
17
|
|
|
18
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
|
|
20
|
-
## [param include_properties] is set, the file's value or the default where it sets none
|
|
19
|
+
## editableChildren on a node that instances another scene and its stored properties when
|
|
20
|
+
## [param include_properties] is set, the file's value or the default where it sets none, and on a
|
|
21
|
+
## node another file makes setHere, the properties this file sets on it. Defaults the file leaves
|
|
22
|
+
## alone are counted as defaultsLeftOut rather than listed unless [param include_defaults] is set.
|
|
21
23
|
## [param depth] -1 reads every level.
|
|
22
|
-
static func scene_tree(
|
|
24
|
+
static func scene_tree(
|
|
25
|
+
scene_path: String, depth: int, include_properties: bool, include_defaults: bool
|
|
26
|
+
) -> Dictionary:
|
|
23
27
|
var laid: Dictionary = _laid(scene_path)
|
|
24
28
|
if not laid.get("ok", false):
|
|
25
29
|
return laid
|
|
26
30
|
var nodes: Dictionary = laid["nodes"]
|
|
27
|
-
return {"ok": true, "tree": _entry(nodes, ".", include_properties, depth, 0)}
|
|
31
|
+
return {"ok": true, "tree": _entry(nodes, ".", include_properties, include_defaults, depth, 0)}
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
## The node at [param node_path] in [param scene_path] as {ok, name, type, properties}, laid as
|
|
@@ -119,6 +123,9 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
119
123
|
return {"ok": false, "error": refused}
|
|
120
124
|
if not nodes.has("."):
|
|
121
125
|
return {"ok": false, "error": "%s holds no nodes" % scene_path}
|
|
126
|
+
var base: SceneState = packed.get_state().get_base_scene_state()
|
|
127
|
+
if base != null:
|
|
128
|
+
nodes["."]["inherits"] = base.get_path()
|
|
122
129
|
return {"ok": true, "nodes": nodes}
|
|
123
130
|
|
|
124
131
|
|
|
@@ -173,8 +180,13 @@ static func _lay(
|
|
|
173
180
|
continue
|
|
174
181
|
else:
|
|
175
182
|
nodes[path] = _made(str(state.get_node_type(index)))
|
|
183
|
+
nodes[path]["madeHere"] = loaded != null
|
|
176
184
|
if ids[index] >= 0:
|
|
177
185
|
nodes[path]["uniqueId"] = ids[index]
|
|
186
|
+
# Laid at the root with no scene handed in is a scene the one being read inherits, and the
|
|
187
|
+
# deepest is laid first, so this is the file that makes the node.
|
|
188
|
+
if prefix == "." and loaded == null:
|
|
189
|
+
nodes[path]["inheritedFrom"] = state.get_path()
|
|
178
190
|
# The name is the state's even for an entry laid already, since an inherited scene names its
|
|
179
191
|
# root itself; a root instanced inside another scene is renamed by that scene once laid.
|
|
180
192
|
if not recovered:
|
|
@@ -195,10 +207,15 @@ static func _lay(
|
|
|
195
207
|
if not grouped.has(str(group)):
|
|
196
208
|
grouped.append(str(group))
|
|
197
209
|
var held: Dictionary = nodes[path]["properties"]
|
|
210
|
+
# What the file being read sets, kept apart for a node another file makes, whose values read
|
|
211
|
+
# the same whether this scene changed them or that one gave them.
|
|
212
|
+
var laid: Dictionary = nodes[path]
|
|
213
|
+
var set_here: Array = laid.get_or_add("setHere", [])
|
|
198
214
|
for property: int in state.get_node_property_count(index):
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
215
|
+
var named: String = str(state.get_node_property_name(index, property))
|
|
216
|
+
held[named] = state.get_node_property_value(index, property)
|
|
217
|
+
if loaded != null and not set_here.has(named):
|
|
218
|
+
set_here.append(named)
|
|
202
219
|
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
203
220
|
# instances a write here sets; a file instanced below makes its own owner's editable.
|
|
204
221
|
if prefix == ".":
|
|
@@ -314,7 +331,12 @@ static func _joined(prefix: String, given: String) -> String:
|
|
|
314
331
|
|
|
315
332
|
|
|
316
333
|
static func _entry(
|
|
317
|
-
nodes: Dictionary,
|
|
334
|
+
nodes: Dictionary,
|
|
335
|
+
path: String,
|
|
336
|
+
include_properties: bool,
|
|
337
|
+
include_defaults: bool,
|
|
338
|
+
depth: int,
|
|
339
|
+
current_depth: int
|
|
318
340
|
) -> Dictionary:
|
|
319
341
|
var node: Dictionary = nodes[path]
|
|
320
342
|
var children: Array[Dictionary] = []
|
|
@@ -324,6 +346,12 @@ static func _entry(
|
|
|
324
346
|
data["instanceOf"] = node["instanceOf"]
|
|
325
347
|
if node.has("editableChildren"):
|
|
326
348
|
data["editableChildren"] = node["editableChildren"]
|
|
349
|
+
# An inherited scene's file holds only what it changes, so the read says what it inherits and
|
|
350
|
+
# which nodes come from there, as the editor greys them.
|
|
351
|
+
if node.has("inherits"):
|
|
352
|
+
data["inherits"] = node["inherits"]
|
|
353
|
+
elif node.has("inheritedFrom"):
|
|
354
|
+
data["inheritedFrom"] = node["inheritedFrom"]
|
|
327
355
|
var groups: Array[String] = node["groups"]
|
|
328
356
|
if not groups.is_empty():
|
|
329
357
|
var sorted: Array[String] = groups.duplicate()
|
|
@@ -331,7 +359,21 @@ static func _entry(
|
|
|
331
359
|
data["groups"] = sorted
|
|
332
360
|
if include_properties:
|
|
333
361
|
var given: Dictionary = node["properties"]
|
|
334
|
-
|
|
362
|
+
var stored: Dictionary = _stored(str(node["type"]), given, include_defaults)
|
|
363
|
+
data["properties"] = stored["properties"]
|
|
364
|
+
if stored["left_out"] > 0:
|
|
365
|
+
data["defaultsLeftOut"] = stored["left_out"]
|
|
366
|
+
var cells: int = _cell_count(str(node["type"]), given)
|
|
367
|
+
if cells >= 0:
|
|
368
|
+
data["cellCount"] = cells
|
|
369
|
+
data["cellsListedBy"] = (
|
|
370
|
+
"scene_node get with nodePath %s, which lists them as set_tilemap_cells takes them" % path
|
|
371
|
+
)
|
|
372
|
+
if not node.get("madeHere", false):
|
|
373
|
+
var kept: Array = node.get("setHere", [])
|
|
374
|
+
var set_here: Array = kept.duplicate()
|
|
375
|
+
set_here.sort()
|
|
376
|
+
data["setHere"] = set_here
|
|
335
377
|
var below: Array[String] = node["children"]
|
|
336
378
|
# Counted rather than left as an empty list, which reads as a node with no children.
|
|
337
379
|
if depth >= 0 and current_depth >= depth:
|
|
@@ -339,19 +381,56 @@ static func _entry(
|
|
|
339
381
|
data["childrenNotListed"] = below.size()
|
|
340
382
|
return data
|
|
341
383
|
for child: String in below:
|
|
342
|
-
children.append(_entry(nodes, child, include_properties, depth, current_depth + 1))
|
|
384
|
+
children.append(_entry(nodes, child, include_properties, include_defaults, depth, current_depth + 1))
|
|
343
385
|
return data
|
|
344
386
|
|
|
345
387
|
|
|
388
|
+
## How many cells a TileMapLayer or TileMap of [param type] holds under the values [param given], or
|
|
389
|
+
## -1 when it is another node or the file sets none of its cells. The file keeps them as packed bytes,
|
|
390
|
+
## which a read of the tree answered with alone, so a caller decoded the base64 to learn what was
|
|
391
|
+
## painted. Only cells the file sets are counted: an instance's come from its own scene.
|
|
392
|
+
static func _cell_count(type: String, given: Dictionary) -> int:
|
|
393
|
+
if not ClassDB.class_exists(type) or not ClassDB.can_instantiate(type):
|
|
394
|
+
return -1
|
|
395
|
+
var single: bool = ClassDB.is_parent_class(type, "TileMapLayer")
|
|
396
|
+
if not single and not ClassDB.is_parent_class(type, "TileMap"):
|
|
397
|
+
return -1
|
|
398
|
+
var painted: bool = false
|
|
399
|
+
for property: Variant in given:
|
|
400
|
+
var named: String = str(property)
|
|
401
|
+
if named == "tile_map_data" or (named.begins_with("layer_") and named.ends_with("/tile_data")):
|
|
402
|
+
painted = true
|
|
403
|
+
if not painted:
|
|
404
|
+
return -1
|
|
405
|
+
var bare: Node = ClassDB.instantiate(type)
|
|
406
|
+
for property: Variant in given:
|
|
407
|
+
if property != "script":
|
|
408
|
+
bare.set(str(property), given[property])
|
|
409
|
+
var count: int = 0
|
|
410
|
+
if single:
|
|
411
|
+
count = (bare as TileMapLayer).get_used_cells().size()
|
|
412
|
+
else:
|
|
413
|
+
var layered: TileMap = bare as TileMap
|
|
414
|
+
for layer: int in layered.get_layers_count():
|
|
415
|
+
count += layered.get_used_cells(layer).size()
|
|
416
|
+
bare.free()
|
|
417
|
+
return count
|
|
418
|
+
|
|
419
|
+
|
|
346
420
|
## Every property a node of [param type] stores, with the value [param given] sets or the default,
|
|
347
|
-
## read as a node holding those values would answer them.
|
|
421
|
+
## read as a node holding those values would answer them, as {properties, left_out}.
|
|
348
422
|
##
|
|
349
423
|
## The engine's half is read off a bare node of the class with the file's values set on it, which
|
|
350
424
|
## runs no script: what a node lists depends on its values (a thread group's settings appear only
|
|
351
425
|
## once it has one) and some setters set others (a Control's anchors preset sets how it grows), so
|
|
352
426
|
## the class's defaults and the file's entries alone answered neither. The script's half is its
|
|
353
427
|
## declared defaults under the file's values.
|
|
354
|
-
|
|
428
|
+
##
|
|
429
|
+
## Without [param include_defaults], properties keeps only what the file sets and what differs from
|
|
430
|
+
## a node of the class holding none of its values, and left_out counts the rest. Every Control
|
|
431
|
+
## carries a hundred properties, and ten of them came to 57 KB of defaults around the few values a
|
|
432
|
+
## menu scene set.
|
|
433
|
+
static func _stored(type: String, given: Dictionary, include_defaults: bool) -> Dictionary:
|
|
355
434
|
var attached: Variant = given.get("script")
|
|
356
435
|
var script: Script = attached if attached is Script else null
|
|
357
436
|
var scripted: Dictionary = {}
|
|
@@ -361,8 +440,10 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
361
440
|
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
362
441
|
scripted[property] = script.get_property_default_value(property)
|
|
363
442
|
var values: Dictionary = {}
|
|
443
|
+
var defaults: Dictionary = {}
|
|
364
444
|
if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
365
445
|
var bare: Object = ClassDB.instantiate(type)
|
|
446
|
+
var plain: Object = ClassDB.instantiate(type)
|
|
366
447
|
for property: Variant in given:
|
|
367
448
|
if property != "script" and not scripted.has(property):
|
|
368
449
|
bare.set(str(property), given[property])
|
|
@@ -370,18 +451,30 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
370
451
|
var property: String = str(declared.get("name", ""))
|
|
371
452
|
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
372
453
|
values[property] = bare.get(property)
|
|
373
|
-
|
|
374
|
-
|
|
454
|
+
defaults[property] = plain.get(property)
|
|
455
|
+
for made: Object in [bare, plain]:
|
|
456
|
+
if made is Node:
|
|
457
|
+
made.free()
|
|
375
458
|
for property: String in scripted:
|
|
376
459
|
values[property] = scripted[property]
|
|
460
|
+
defaults[property] = scripted[property]
|
|
377
461
|
# Given last, which keeps what a bare node cannot hold: the script itself, and a path to a node
|
|
378
462
|
# for a property that holds the node once it is in a tree.
|
|
379
463
|
values.merge(given, true)
|
|
380
464
|
var serialiser: GdhSerialisation = GdhSerialisation.new()
|
|
381
465
|
var answered: Dictionary = {}
|
|
466
|
+
var left_out: int = 0
|
|
382
467
|
for property: Variant in values:
|
|
468
|
+
var as_default: bool = (
|
|
469
|
+
not given.has(property)
|
|
470
|
+
and defaults.has(property)
|
|
471
|
+
and GdhSame.same(values[property], defaults[property])
|
|
472
|
+
)
|
|
473
|
+
if as_default and not include_defaults:
|
|
474
|
+
left_out += 1
|
|
475
|
+
continue
|
|
383
476
|
answered[property] = serialiser.serialize_value(values[property])
|
|
384
|
-
return answered
|
|
477
|
+
return {"properties": answered, "left_out": left_out}
|
|
385
478
|
|
|
386
479
|
|
|
387
480
|
## What a resource file holds, in the form modify takes, so a value read here can be written back.
|
|
@@ -394,12 +487,16 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
394
487
|
var file: String = res_path.get_slice("::", 0)
|
|
395
488
|
if not FileAccess.file_exists(file):
|
|
396
489
|
return {"ok": false, "error": "No resource at " + file}
|
|
397
|
-
var
|
|
398
|
-
if
|
|
490
|
+
var whole: Resource = ResourceLoader.load(file, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
491
|
+
if whole == null:
|
|
399
492
|
return {"ok": false, "error": "%s did not load as a resource." % file}
|
|
493
|
+
# The file's resource is held until the read is done, since what it keeps can depend on it: a
|
|
494
|
+
# TileSet that goes unlinks its sources, and an atlas source with no TileSet lists none of its
|
|
495
|
+
# tiles' physics, navigation or custom data.
|
|
496
|
+
var loaded: Resource = whole
|
|
400
497
|
if file != res_path:
|
|
401
498
|
var id: String = res_path.trim_prefix(file + "::")
|
|
402
|
-
var inner: Resource = _kept_in(
|
|
499
|
+
var inner: Resource = _kept_in(whole, file, id, {})
|
|
403
500
|
if inner == null:
|
|
404
501
|
return {"ok": false, "error": "%s keeps no resource with the id %s." % [file, id]}
|
|
405
502
|
loaded = inner
|
|
@@ -68,6 +68,18 @@ static func through_script(object: Object, names: Dictionary) -> bool:
|
|
|
68
68
|
return false
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
## Whether [param object] still holds [param held] for [param name] once set to [param wanted]
|
|
72
|
+
## because the engine derives it, as it keeps layout_mode at Container under a container: an engine
|
|
73
|
+
## property, so no script code ran for the set, and an editor saves the same.
|
|
74
|
+
static func kept_by_engine(object: Object, name: String, wanted: Variant, held: Variant) -> bool:
|
|
75
|
+
return (
|
|
76
|
+
not GdhSame.same(object.get(name), wanted)
|
|
77
|
+
and GdhSame.same(object.get(name), held)
|
|
78
|
+
and name != "script"
|
|
79
|
+
and not through_script(object, {name: true})
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
71
83
|
## The stored properties of [param object] that are not in [param names] and no longer hold what
|
|
72
84
|
## [param before] recorded. One the object did not have before, which a script set on it declares,
|
|
73
85
|
## counts once it holds anything but its default; one it no longer has went with the old script.
|
|
@@ -366,8 +378,14 @@ static func embedded_changes(before: String, after: String, reached_ids: Diction
|
|
|
366
378
|
## Compares the file just saved at [param path] with [param before], what it held, and puts back
|
|
367
379
|
## what a script that is not a tool changed in it: in its embedded resources, which [param values]
|
|
368
380
|
## reach, and in [param main], its main resource, unless that is null. [param asked] and
|
|
369
|
-
## [param reached_ids] are what the call changed
|
|
370
|
-
##
|
|
381
|
+
## [param reached_ids] are what the call changed, and [param carried] is [method embedded_in] of
|
|
382
|
+
## [param values] taken before the save. Saved again through [param resave], which answers an Error,
|
|
383
|
+
## and answers "" or why the file still does not hold what it should.
|
|
384
|
+
##
|
|
385
|
+
## An id is compared only where the resource holding it after the save held it before. Each headless
|
|
386
|
+
## engine hands out the ids of one sequence, so a resource the call added can be saved under the id
|
|
387
|
+
## the file had kept for one it dropped. Read as one resource, the new third line of a dialogue was
|
|
388
|
+
## taken for the old first one changed by its script, and given the old one's text back.
|
|
371
389
|
static func settle_saved(
|
|
372
390
|
path: String,
|
|
373
391
|
before: String,
|
|
@@ -375,6 +393,7 @@ static func settle_saved(
|
|
|
375
393
|
main: Object,
|
|
376
394
|
asked: Dictionary,
|
|
377
395
|
reached_ids: Dictionary,
|
|
396
|
+
carried: Dictionary,
|
|
378
397
|
resave: Callable
|
|
379
398
|
) -> String:
|
|
380
399
|
for attempt: int in 2:
|
|
@@ -383,6 +402,8 @@ static func settle_saved(
|
|
|
383
402
|
var embedded: Array[Array] = []
|
|
384
403
|
for found: Array in embedded_changes(before, after, reached_ids):
|
|
385
404
|
var holder: Resource = by_id.get(found[0])
|
|
405
|
+
if not carried.has(found[0]) or (holder != null and not is_same(holder, carried[found[0]])):
|
|
406
|
+
continue
|
|
386
407
|
if holder != null and _unchanged(holder, found.slice(1)):
|
|
387
408
|
continue
|
|
388
409
|
if found[2] or holder == null or not at_default(holder, str(found[1])):
|
|
@@ -458,6 +479,32 @@ static func put_back_value(object: Object, name: String, was_set: bool, held: St
|
|
|
458
479
|
return ""
|
|
459
480
|
|
|
460
481
|
|
|
482
|
+
## [param refused], a refusal of [param property] on [param object] for not holding what it was given,
|
|
483
|
+
## with why when a headless engine set it through a script that is not a tool. That setter runs here
|
|
484
|
+
## and in the game, and an editor runs none and saves the value given, so a setter clamping stock to
|
|
485
|
+
## 0 refused -5 here and the same call through an editor wrote it.
|
|
486
|
+
static func setter_ran(object: Object, property: String, refused: String) -> String:
|
|
487
|
+
if Engine.is_editor_hint() or property.is_empty():
|
|
488
|
+
return refused
|
|
489
|
+
if not through_script(object, {property.get_slice(":", 0): true}):
|
|
490
|
+
return refused
|
|
491
|
+
var attached: Variant = object.get_script()
|
|
492
|
+
var named: String = "Its script"
|
|
493
|
+
if attached is Script:
|
|
494
|
+
var script: Script = attached
|
|
495
|
+
named = script.resource_path
|
|
496
|
+
return (
|
|
497
|
+
(
|
|
498
|
+
(
|
|
499
|
+
"%s %s is not a tool script, so its setter ran in the headless engine writing this as it runs "
|
|
500
|
+
+ "in the game; an editor runs none and would have saved the value given. "
|
|
501
|
+
)
|
|
502
|
+
% [refused, named]
|
|
503
|
+
)
|
|
504
|
+
+ REMEDY
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
461
508
|
## Why [param name] on [param what] cannot be saved as the file held it.
|
|
462
509
|
static func unasked(what: String, name: String, why: String = "") -> String:
|
|
463
510
|
return (
|
|
@@ -26,38 +26,109 @@ static func declared_on(target: Object, property: String) -> Dictionary:
|
|
|
26
26
|
## start with the name of one holding a resource built into it, then a slash and a key of that
|
|
27
27
|
## resource: "sources/0/0:0/0/probability" is the probability of tile 0:0 in the atlas source a
|
|
28
28
|
## TileSet keeps as sources/0. A colon path cannot reach it, since the tile's own name holds colons.
|
|
29
|
-
## A
|
|
30
|
-
##
|
|
31
|
-
## file
|
|
29
|
+
## A list of resources is stepped into by an element's index: "lines/1/text" is the text of the
|
|
30
|
+
## second resource in lines. A resource kept in a file of its own is not reached, nor, given the
|
|
31
|
+
## file [param home] that [param target] is saved in, one built into another file, since writing it
|
|
32
|
+
## here would leave that file as it was.
|
|
32
33
|
static func holder_of(target: Object, key: String, home: String = "") -> Array:
|
|
33
34
|
var through: Array[Resource] = []
|
|
34
35
|
var holder: Object = target
|
|
35
36
|
var name: String = key
|
|
36
37
|
while declared_on(holder, name).is_empty():
|
|
37
38
|
var reached: Resource = null
|
|
39
|
+
var rest: int = -1
|
|
38
40
|
var at: int = name.find("/")
|
|
39
41
|
while at > 0:
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
+
var stepped: Array = _stepped(holder, name, at)
|
|
43
|
+
var held: Resource = stepped[0]
|
|
44
|
+
if held != null and _built_into(held, home):
|
|
42
45
|
reached = held
|
|
46
|
+
rest = GdhRead.as_int(stepped[1])
|
|
43
47
|
break
|
|
44
48
|
at = name.find("/", at + 1)
|
|
45
49
|
if reached == null:
|
|
46
50
|
break
|
|
47
51
|
through.append(reached)
|
|
48
52
|
holder = reached
|
|
49
|
-
name = name.substr(
|
|
53
|
+
name = name.substr(rest)
|
|
50
54
|
return [holder, name, through]
|
|
51
55
|
|
|
52
56
|
|
|
53
|
-
## The resource [param
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
## The resource the part of [param name] before the slash at [param at] reaches on [param target],
|
|
58
|
+
## as [resource, where the rest of [param name] starts], or [null, -1]: the one held under that key,
|
|
59
|
+
## or the element of the list held there that the step after the slash indexes.
|
|
60
|
+
static func _stepped(target: Object, name: String, at: int) -> Array:
|
|
61
|
+
var head: String = name.left(at)
|
|
62
|
+
var held: Resource = held_at(target, head)
|
|
63
|
+
if held != null:
|
|
64
|
+
return [held, at + 1]
|
|
65
|
+
var end: int = name.find("/", at + 1)
|
|
66
|
+
if end < 0 or declared_on(target, head).is_empty():
|
|
67
|
+
return [null, -1]
|
|
68
|
+
var index: String = name.substr(at + 1, end - at - 1)
|
|
69
|
+
var listed: Variant = target.get(head)
|
|
70
|
+
if not listed is Array or not index.is_valid_int():
|
|
71
|
+
return [null, -1]
|
|
72
|
+
var items: Array = listed
|
|
73
|
+
var place: int = index.to_int()
|
|
74
|
+
if place < 0 or place >= items.size() or not items[place] is Resource:
|
|
75
|
+
return [null, -1]
|
|
76
|
+
return [items[place], end + 1]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## The refusal for [param name], which [param target] does not declare, when a step of it indexes a
|
|
80
|
+
## list [param target] holds and names no resource in it; or "".
|
|
81
|
+
static func unlisted(target: Object, name: String) -> String:
|
|
82
|
+
var at: int = name.find("/")
|
|
83
|
+
while at > 0:
|
|
84
|
+
var head: String = name.left(at)
|
|
85
|
+
var listed: Variant = null if declared_on(target, head).is_empty() else target.get(head)
|
|
86
|
+
if listed is Array:
|
|
87
|
+
var items: Array = listed
|
|
88
|
+
return _unlisted_in(items, "%s.%s" % [class_called(target), head], name, at)
|
|
89
|
+
at = name.find("/", at + 1)
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
static func _unlisted_in(items: Array, called: String, name: String, at: int) -> String:
|
|
94
|
+
var head: String = name.left(at)
|
|
95
|
+
var end: int = name.find("/", at + 1)
|
|
96
|
+
var index: String = name.substr(at + 1) if end < 0 else name.substr(at + 1, end - at - 1)
|
|
97
|
+
if items.is_empty():
|
|
98
|
+
return "%s holds no elements, so %s names none: give %s a whole list first." % [called, name, head]
|
|
99
|
+
var last: int = items.size() - 1
|
|
100
|
+
if not index.is_valid_int():
|
|
101
|
+
return (
|
|
102
|
+
"%s is a list, so the step after it is the index of one of its elements, from 0 to %d, not %s."
|
|
103
|
+
% [called, last, index]
|
|
104
|
+
)
|
|
105
|
+
var place: int = index.to_int()
|
|
106
|
+
if place < 0 or place > last:
|
|
107
|
+
return (
|
|
108
|
+
"%s holds %d element%s, so %s names none: an index goes from 0 to %d."
|
|
109
|
+
% [called, items.size(), "" if items.size() == 1 else "s", name, last]
|
|
110
|
+
)
|
|
111
|
+
if not items[place] is Resource:
|
|
112
|
+
return (
|
|
113
|
+
"%s/%d holds %s, which has no properties to write: give %s a whole list instead."
|
|
114
|
+
% [head, place, type_string(typeof(items[place])), head]
|
|
115
|
+
)
|
|
116
|
+
if end < 0:
|
|
117
|
+
return (
|
|
118
|
+
(
|
|
119
|
+
"%s/%d is an element of %s, which a path does not replace alone: give %s a whole list, or "
|
|
120
|
+
+ "write a property of the element, as %s/%d/<property>."
|
|
121
|
+
)
|
|
122
|
+
% [head, place, called, head, head, place]
|
|
123
|
+
)
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
static func _built_into(held: Resource, home: String) -> bool:
|
|
128
|
+
if not held.is_built_in():
|
|
129
|
+
return false
|
|
59
130
|
var file: String = held.resource_path.get_slice("::", 0)
|
|
60
|
-
return
|
|
131
|
+
return home.is_empty() or file.is_empty() or file == home
|
|
61
132
|
|
|
62
133
|
|
|
63
134
|
## The file [param target] is saved in: its scene's for a node, as the nodes of an instance are
|
|
@@ -184,9 +255,11 @@ static func class_called(target: Object) -> String:
|
|
|
184
255
|
static func held_elsewhere(target: Object, name: String, home: String) -> String:
|
|
185
256
|
var at: int = name.find("/")
|
|
186
257
|
while at > 0:
|
|
187
|
-
var
|
|
188
|
-
var
|
|
189
|
-
var
|
|
258
|
+
var stepped: Array = _stepped(target, name, at)
|
|
259
|
+
var held: Resource = stepped[0]
|
|
260
|
+
var past: int = GdhRead.as_int(stepped[1])
|
|
261
|
+
var head: String = "" if held == null else name.left(past - 1)
|
|
262
|
+
var rest: String = "" if held == null else name.substr(past)
|
|
190
263
|
var file: String = "" if held == null else held.resource_path.get_slice("::", 0)
|
|
191
264
|
if held != null and not held.is_built_in():
|
|
192
265
|
return (
|