gdharness 1.3.41 → 1.3.43

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.
Files changed (107) hide show
  1. package/build/cli.js +131 -47
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +37 -12
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
  33. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
  35. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  36. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  37. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  38. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  39. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  40. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  41. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  42. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  43. package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
  44. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  45. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
  46. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  47. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  48. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  49. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  50. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  51. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  52. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  53. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
  54. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  55. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  56. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  57. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  58. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  59. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  60. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
  61. package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
  62. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  63. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  64. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  65. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  66. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  67. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  68. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  69. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  70. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  71. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  72. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  73. package/build/godot/loader/loader.gd +9 -9
  74. package/build/godot/operations/animation_reads.gd +84 -35
  75. package/build/godot/operations/audio_buses.gd +48 -19
  76. package/build/godot/operations/class_cache.gd +16 -11
  77. package/build/godot/operations/classdb_queries.gd +89 -44
  78. package/build/godot/operations/declared_values.gd +68 -32
  79. package/build/godot/operations/dependencies.gd +139 -93
  80. package/build/godot/operations/editor_tools.gd +9 -10
  81. package/build/godot/operations/export_presets.gd +100 -54
  82. package/build/godot/operations/file_reads.gd +114 -39
  83. package/build/godot/operations/file_walk.gd +12 -5
  84. package/build/godot/operations/gdscript_analysis.gd +105 -66
  85. package/build/godot/operations/gdscript_authoring.gd +138 -39
  86. package/build/godot/operations/godot_operations.gd +17 -11
  87. package/build/godot/operations/import_pipeline.gd +110 -59
  88. package/build/godot/operations/input_actions.gd +63 -39
  89. package/build/godot/operations/patterns.gd +3 -1
  90. package/build/godot/operations/plugins.gd +36 -16
  91. package/build/godot/operations/project_config.gd +144 -27
  92. package/build/godot/operations/project_diagnostics.gd +24 -11
  93. package/build/godot/operations/project_file.gd +16 -16
  94. package/build/godot/operations/project_validation.gd +71 -44
  95. package/build/godot/operations/property_paths.gd +22 -12
  96. package/build/godot/operations/property_values.gd +143 -78
  97. package/build/godot/operations/reading.gd +37 -12
  98. package/build/godot/operations/rename_checks.gd +2 -2
  99. package/build/godot/operations/resource_files.gd +11 -9
  100. package/build/godot/operations/same_values.gd +4 -4
  101. package/build/godot/operations/scene_connections.gd +27 -13
  102. package/build/godot/operations/script_check.gd +52 -49
  103. package/build/godot/operations/serialisation.gd +75 -51
  104. package/build/godot/operations/setting_reads.gd +7 -6
  105. package/build/godot/operations/value_shapes.gd +5 -6
  106. package/build/godot/tests/listed.gd +321 -268
  107. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## The groups a node of a scene is in. A scene's own file puts a node in a group and can take it
5
4
  ## out of one it put it in; a group the scene that makes the node puts it in, the one it is
6
5
  ## instanced from or one this scene inherits, can be changed only there, as the editor's group dock
@@ -15,20 +14,20 @@ static func read(scene: GdhSceneFile, node: Node) -> Dictionary:
15
14
  var all: Array = _sorted(node.get_groups())
16
15
  if all.is_empty():
17
16
  return {}
18
- var answer: Dictionary = {"groups": all}
17
+ var answer: Dictionary = { "groups": all }
19
18
  var here: Array = _sorted(scene.groups_at(scene.path_of(node)))
20
19
  if here.size() != all.size():
21
20
  answer["groupsHere"] = here
22
21
  return answer
23
22
 
24
23
 
25
- ## The groups of [param node], just added to this scene and put in [param adding] there, in the shape
26
- ## [method read] answers: this scene's file does not hold the node until it is saved.
24
+ ## The groups of [param node], just added to this scene and put in [param adding] there, in the
25
+ ## shape [method read] answers: this scene's file does not hold the node until it is saved.
27
26
  static func read_added(node: Node, adding: Array) -> Dictionary:
28
27
  var all: Array = _sorted(node.get_groups())
29
28
  if all.is_empty():
30
29
  return {}
31
- var answer: Dictionary = {"groups": all}
30
+ var answer: Dictionary = { "groups": all }
32
31
  var here: Array = []
33
32
  for group: String in _sorted(adding):
34
33
  if not here.has(group):
@@ -65,19 +64,27 @@ static func change(scene: GdhSceneFile, node: Node, adding: Array, removing: Arr
65
64
  var held: Array = _sorted(node.get_groups())
66
65
  return (
67
66
  "%s is in no group %s; %s."
68
- % [node.name, name, "it is in " + ", ".join(held) if not held.is_empty() else "it is in none"]
67
+ % [
68
+ node.name,
69
+ name,
70
+ "it is in " + ", ".join(held) if not held.is_empty() else "it is in none",
71
+ ]
69
72
  )
70
73
  if not here.has(name):
71
74
  var from: String = scene.values_from(node, at)
72
75
  if from.is_empty():
73
76
  return (
74
- "%s is in %s, which no scene file puts it in: something puts it there as it is made, such as its script."
77
+ (
78
+ "%s is in %s, which no scene file puts it in: something puts it there as "
79
+ + "it is made, such as its script."
80
+ )
75
81
  % [node.name, name]
76
82
  )
77
83
  return (
78
84
  (
79
- "%s is in %s because %s puts it there, and no scene using that one can take it out: "
80
- + "remove the group in %s, which takes it out in every scene using that one."
85
+ "%s is in %s because %s puts it there, and no scene using that one can take it "
86
+ + "out: remove the group in %s, which takes it out in every scene using that "
87
+ + "one."
81
88
  )
82
89
  % [node.name, name, from, from]
83
90
  )
@@ -1,8 +1,7 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
- ## The two ways a scene keeps its own values for nodes another scene makes: by inheriting that scene,
5
- ## or by instancing it with its children editable.
3
+ ## The two ways a scene keeps its own values for nodes another scene makes: by inheriting that
4
+ ## scene, or by instancing it with its children editable.
6
5
 
7
6
  const GdhFileReads = preload("../file_reads.gd")
8
7
  const GdhPaths = preload("scene_paths.gd")
@@ -18,11 +17,11 @@ static func taken(scene_path: String) -> Dictionary:
18
17
  "error":
19
18
  (
20
19
  (
21
- "%s already exists, and scene_create op=create would replace everything in it. Edit it "
22
- + "with scene_node, or create the scene under another path."
20
+ "%s already exists, and scene_create op=create would replace everything in it. "
21
+ + "Edit it with scene_node, or create the scene under another path."
23
22
  )
24
23
  % scene_path
25
- )
24
+ ),
26
25
  }
27
26
 
28
27
 
@@ -32,12 +31,15 @@ static func inherited(
32
31
  args: Dictionary, scene_path: String, base_path: String, script_path: String
33
32
  ) -> Dictionary:
34
33
  if scene_path.is_empty():
35
- return {"ok": false, "error": "Missing scenePath"}
34
+ return { "ok": false, "error": "Missing scenePath" }
36
35
  if args.has("rootNodeType"):
37
36
  return {
38
37
  "ok": false,
39
38
  "error":
40
- "inherits takes the root's class from %s, so rootNodeType is not given with it." % base_path
39
+ (
40
+ "inherits takes the root's class from %s, so rootNodeType is not given with it."
41
+ % base_path
42
+ ),
41
43
  }
42
44
  if FileAccess.file_exists(scene_path):
43
45
  return taken(scene_path)
@@ -45,10 +47,10 @@ static func inherited(
45
47
  if not script_path.is_empty():
46
48
  script = load(script_path) as Script
47
49
  if script == null:
48
- return {"ok": false, "error": "No script loads from " + script_path}
50
+ return { "ok": false, "error": "No script loads from " + script_path }
49
51
  var uncompiled: String = GdhSceneFile.uncompiled(script, script_path)
50
52
  if not uncompiled.is_empty():
51
- return {"ok": false, "error": uncompiled}
53
+ return { "ok": false, "error": uncompiled }
52
54
  return inherit(scene_path, base_path, str(args.get("rootNodeName", "")), script)
53
55
 
54
56
 
@@ -57,17 +59,22 @@ static func inherited(
57
59
  ## given. Answers what scene_create answers, or a refusal.
58
60
  ##
59
61
  ## Written as the text the editor's New Inherited Scene saves rather than packed from an instance of
60
- ## the base: a headless engine making that instance runs every script in the base, and the pack would
61
- ## keep what they set as this scene's own values.
62
- static func inherit(scene_path: String, base_path: String, root_name: String, script: Script) -> Dictionary:
62
+ ## the base: a headless engine making that instance runs every script in the base, and the pack
63
+ ## would keep what they set as this scene's own values.
64
+ static func inherit(
65
+ scene_path: String, base_path: String, root_name: String, script: Script
66
+ ) -> Dictionary:
63
67
  var base: Dictionary = GdhFileReads.scene_node(base_path, ".")
64
68
  if not base.get("ok", false):
65
- return {"ok": false, "error": "inherits: " + str(base.get("error", "%s holds no nodes" % base_path))}
69
+ return {
70
+ "ok": false,
71
+ "error": "inherits: " + str(base.get("error", "%s holds no nodes" % base_path)),
72
+ }
66
73
  var root_type: String = str(base["type"])
67
74
  var name: String = str(base["name"]) if root_name.is_empty() else root_name
68
75
  var unfit: String = GdhSceneFile.unfit_name(name)
69
76
  if not unfit.is_empty():
70
- return {"ok": false, "error": unfit}
77
+ return { "ok": false, "error": unfit }
71
78
  if script != null:
72
79
  var extended: String = str(script.get_instance_base_type())
73
80
  if not ClassDB.is_parent_class(root_type, extended):
@@ -77,11 +84,11 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
77
84
  (
78
85
  "%s extends %s, so it cannot be on the %s root %s inherits."
79
86
  % [script.resource_path, extended, root_type, base_path]
80
- )
87
+ ),
81
88
  }
82
89
  var folder: String = GdhSceneFile.make_folder_for(scene_path)
83
90
  if not folder.is_empty():
84
- return {"ok": false, "error": folder}
91
+ return { "ok": false, "error": folder }
85
92
 
86
93
  var lines: PackedStringArray = [
87
94
  '[gd_scene format=3 uid="%s"]' % ResourceUID.id_to_text(ResourceUID.create_id()),
@@ -105,18 +112,24 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
105
112
  if file == null or not file.store_string("\n".join(lines) + "\n"):
106
113
  return {
107
114
  "ok": false,
108
- "error": "Could not write %s: %s" % [scene_path, error_string(FileAccess.get_open_error())]
115
+ "error":
116
+ "Could not write %s: %s" % [scene_path, error_string(FileAccess.get_open_error())],
109
117
  }
110
118
  file.close()
111
119
 
112
- var loaded: PackedScene = ResourceLoader.load(scene_path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE)
120
+ var loaded: PackedScene = ResourceLoader.load(
121
+ scene_path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE
122
+ )
113
123
  var instanced: PackedScene = loaded.get_state().get_node_instance(0) if loaded != null else null
114
124
  if instanced == null or instanced.resource_path != base_path:
115
125
  var _removed: Error = DirAccess.remove_absolute(scene_path)
116
126
  return {
117
127
  "ok": false,
118
128
  "error":
119
- "%s did not load back as a scene inheriting %s, so it was removed." % [scene_path, base_path]
129
+ (
130
+ "%s did not load back as a scene inheriting %s, so it was removed."
131
+ % [scene_path, base_path]
132
+ ),
120
133
  }
121
134
  GdhSceneFile.refresh(scene_path)
122
135
  var answer: Dictionary = {
@@ -124,17 +137,23 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
124
137
  "scenePath": scene_path,
125
138
  "inherits": base_path,
126
139
  "rootNodeType": root_type,
127
- "rootNodeName": name
140
+ "rootNodeName": name,
128
141
  }
129
- if script != null:
130
- answer["script"] = script.resource_path
142
+ var properties: Dictionary = base["properties"]
143
+ var carried: Variant = script if script != null else properties.get("script")
144
+ if carried is Script:
145
+ var carried_script: Script = carried
146
+ answer["script"] = carried_script.resource_path
147
+ if not str(carried_script.get_global_name()).is_empty():
148
+ answer["scriptClass"] = str(carried_script.get_global_name())
131
149
  return answer
132
150
 
133
151
 
134
152
  ## Makes the children of [param node], an instance in [param scene], editable or not, answering why
135
153
  ## it cannot or "". With them editable the scene keeps its own values for them, as the editor's
136
154
  ## Editable Children does; made not editable again, the values it kept for them are dropped. The
137
- ## caller has had [method GdhSceneFile.foreign] refuse a node inside an instance that is not editable.
155
+ ## caller has had [method GdhSceneFile.foreign] refuse a node inside an instance that is not
156
+ ## editable.
138
157
  static func set_editable(scene: GdhSceneFile, node: Node, editable: bool) -> String:
139
158
  if node.owner == null or node.scene_file_path.is_empty():
140
159
  return (
@@ -156,7 +175,9 @@ static func set_editable(scene: GdhSceneFile, node: Node, editable: bool) -> Str
156
175
  ## for a node deleted, they are only named. Answers followed, the scenes rewritten, and
157
176
  ## notFollowed, each one left as it was with why, each only when it names any, with what
158
177
  ## [param carried] gives.
159
- static func follow(changed: String, old_path: String, new_path: String, carried: GdhPaths) -> Dictionary:
178
+ static func follow(
179
+ changed: String, old_path: String, new_path: String, carried: GdhPaths
180
+ ) -> Dictionary:
160
181
  var texts: Dictionary = {}
161
182
  _read_scenes("res://", texts)
162
183
  var kin: Array[String] = [changed]
@@ -182,7 +203,7 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
182
203
  moves[old_path] = new_path
183
204
  var read: Dictionary = GdhFileReads.scene_tree(scene_path, -1, false, false)
184
205
  if not read.get("ok", false):
185
- not_followed.append({"scenePath": scene_path, "reason": str(read.get("error", ""))})
206
+ not_followed.append({ "scenePath": scene_path, "reason": str(read.get("error", "")) })
186
207
  continue
187
208
  var tree: Dictionary = read["tree"]
188
209
  _instances_of(tree, changed, old_path, new_path, moves)
@@ -193,28 +214,29 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
193
214
  rewritten = _rewritten(text, moves, old_path.get_base_dir() != new_path.get_base_dir())
194
215
  if rewritten != text:
195
216
  if not unsaved.is_empty():
196
- not_followed.append({"scenePath": scene_path, "reason": unsaved})
217
+ not_followed.append({ "scenePath": scene_path, "reason": unsaved })
197
218
  continue
198
219
  var file: FileAccess = FileAccess.open(scene_path, FileAccess.WRITE)
199
220
  if file == null or not file.store_string(rewritten):
200
221
  not_followed.append(
201
- {"scenePath": scene_path, "reason": error_string(FileAccess.get_open_error())}
222
+ { "scenePath": scene_path, "reason": error_string(FileAccess.get_open_error()) }
202
223
  )
203
224
  continue
204
225
  file.close()
205
226
  GdhSceneFile.refresh(scene_path)
206
227
  followed.append(scene_path)
207
228
  var holds_paths: bool = text.contains("NodePath(")
208
- # A script is only read, for the lines naming the node, so it does not need the file written.
229
+ # A script is only read, for the lines naming the node, so it does not need the file
230
+ # written.
209
231
  if moves.is_empty() or not (holds_paths or text.contains('Script"')):
210
232
  continue
211
233
  if holds_paths and not unsaved.is_empty() and not new_path.is_empty():
212
- not_followed.append({"scenePath": scene_path, "reason": unsaved})
234
+ not_followed.append({ "scenePath": scene_path, "reason": unsaved })
213
235
  continue
214
236
  var before: int = carried.retargeted.size()
215
237
  var unwritten: String = carried.carry_into(scene_path, moves)
216
238
  if not unwritten.is_empty() and not followed.has(scene_path):
217
- not_followed.append({"scenePath": scene_path, "reason": unwritten})
239
+ not_followed.append({ "scenePath": scene_path, "reason": unwritten })
218
240
  elif carried.retargeted.size() > before and not followed.has(scene_path):
219
241
  followed.append(scene_path)
220
242
  var answer: Dictionary = carried.answer()
@@ -233,7 +255,9 @@ static func _read_scenes(directory: String, texts: Dictionary) -> void:
233
255
  _read_scenes(directory.path_join(name), texts)
234
256
  for name: String in DirAccess.get_files_at(directory):
235
257
  if name.ends_with(".tscn"):
236
- texts[directory.path_join(name)] = FileAccess.get_file_as_string(directory.path_join(name))
258
+ texts[directory.path_join(name)] = FileAccess.get_file_as_string(
259
+ directory.path_join(name)
260
+ )
237
261
 
238
262
 
239
263
  ## Whether the scene at [param scene_path] inherits [param changed], through any number of scenes.
@@ -248,19 +272,22 @@ static func _inherits(scene_path: String, changed: String, texts: Dictionary) ->
248
272
  return false
249
273
 
250
274
 
251
- ## The scene the root of [param text] inherits, or "": a root entry naming an instance and no parent.
275
+ ## The scene the root of [param text] inherits, or "": a root entry naming an instance and no
276
+ ## parent.
252
277
  static func _base_of(text: String) -> String:
253
278
  for line: String in text.split("\n"):
254
279
  if not line.begins_with("[node "):
255
280
  continue
256
- var instanced: RegExMatch = RegEx.create_from_string('instance=ExtResource\\("([^"]+)"\\)').search(
257
- line
281
+ var instanced: RegExMatch = (
282
+ RegEx.create_from_string('instance=ExtResource\\("([^"]+)"\\)').search(line)
258
283
  )
259
284
  if instanced == null or line.contains(" parent="):
260
285
  return ""
261
286
  var named: RegExMatch = (
262
287
  RegEx
263
- . create_from_string('\\[ext_resource [^\\n]*path="([^"]+)" id="%s"\\]' % instanced.get_string(1))
288
+ . create_from_string(
289
+ '\\[ext_resource [^\\n]*path="([^"]+)" id="%s"\\]' % instanced.get_string(1)
290
+ )
264
291
  . search(text)
265
292
  )
266
293
  return named.get_string(1) if named != null else ""
@@ -284,10 +311,11 @@ static func _instances_of(
284
311
 
285
312
  ## [param text] with every path [param moves] names, and every path below one, moved.
286
313
  ##
287
- ## Beside a path the saver writes the unique_ids of the nodes along it, which the loader follows when
288
- ## the path has vanished. A rename leaves those ids as they were; with [param reparented] they run
289
- ## through the old parent, which still exists, so a recovery would find the wrong node. They are left
290
- ## out of each path moved, as a hand-written scene leaves them, and the next save writes them anew.
314
+ ## Beside a path the saver writes the unique_ids of the nodes along it, which the loader follows
315
+ ## when the path has vanished. A rename leaves those ids as they were; with [param reparented] they
316
+ ## run through the old parent, which still exists, so a recovery would find the wrong node. They are
317
+ ## left out of each path moved, as a hand-written scene leaves them, and the next save writes them
318
+ ## anew.
291
319
  static func _rewritten(text: String, moves: Dictionary, reparented: bool) -> String:
292
320
  if moves.is_empty():
293
321
  return text
@@ -309,7 +337,11 @@ static func _rewritten(text: String, moves: Dictionary, reparented: bool) -> Str
309
337
  var placed: String = _attribute_set(
310
338
  line, "parent", "." if moved_parent.is_empty() else moved_parent
311
339
  )
312
- line = _without_ids(placed, "parent_id_path") if placed != line and reparented else placed
340
+ line = (
341
+ _without_ids(placed, "parent_id_path")
342
+ if placed != line and reparented
343
+ else placed
344
+ )
313
345
  else:
314
346
  line = _attribute_moved(line, "parent", "parent_id_path", moves, reparented)
315
347
  lines[at] = line
@@ -333,8 +365,8 @@ static func _attribute_set(line: String, attribute: String, value: String) -> St
333
365
  return line.substr(0, found.get_start(1)) + value + line.substr(found.get_end(1))
334
366
 
335
367
 
336
- ## [param line] with the path under [param attribute] moved by [param moves], and, when that moved it
337
- ## and the move was a reparent, without the ids under [param ids] that went along the old path.
368
+ ## [param line] with the path under [param attribute] moved by [param moves], and, when that moved
369
+ ## it and the move was a reparent, without the ids under [param ids] that went along the old path.
338
370
  static func _attribute_moved(
339
371
  line: String, attribute: String, ids: String, moves: Dictionary, reparented: bool
340
372
  ) -> String:
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## The node paths scenes hold, carried after a node moves as the editor's own rename carries them:
5
4
  ## the NodePath values on their nodes, inside arrays, dictionaries and the resources built into the
6
5
  ## scene too, and the tracks of the animations their players play. After a delete they are named
@@ -9,7 +8,11 @@ extends RefCounted
9
8
  ## A path is worked out from where its holder was and where it pointed before the move, so it is
10
9
  ## followed in a scene loaded after the move as well, where the old path no longer resolves.
11
10
 
12
- enum { UNCHANGED, REWRITTEN, DELETED }
11
+ enum {
12
+ UNCHANGED,
13
+ REWRITTEN,
14
+ DELETED,
15
+ }
13
16
 
14
17
  const GdhHeadlessCheck = preload("../headless_check.gd")
15
18
  const GdhRead = preload("../reading.gd")
@@ -87,7 +90,7 @@ func carry(scene: GdhSceneFile, moves: Dictionary) -> int:
87
90
  var name: String = str(property["name"])
88
91
  if not _may_hold_paths(property):
89
92
  continue
90
- var entry: Dictionary = {"scenePath": scene.path, "nodePath": now, "property": name}
93
+ var entry: Dictionary = { "scenePath": scene.path, "nodePath": now, "property": name }
91
94
  var elsewhere: String = "" if set_here.has(name) else from
92
95
  var carried: Array = _carried(node.get(name), was, now, entry, elsewhere)
93
96
  if carried[0]:
@@ -241,10 +244,12 @@ static func _may_hold_paths(property: Dictionary) -> bool:
241
244
  ## it carried, as [whether any was, the value]. A resource built into the scene is changed in place.
242
245
  ## [param entry] says where the value is held, for the record of each path. A path in a resource may
243
246
  ## be relative to something other than its holder, as the editor allows; it is followed only when it
244
- ## names a node from where its holder was, as one relative to the holder does. A value [param
245
- ## elsewhere], a file other than the scene's, gives is named only when it names nothing since the
246
- ## move, which its own file's pass could not see.
247
- func _carried(value: Variant, was: String, now: String, entry: Dictionary, elsewhere: String) -> Array:
247
+ ## names a node from where its holder was, as one relative to the holder does. A value
248
+ ## [param elsewhere], a file other than the scene's, gives is named only when it names nothing since
249
+ ## the move, which its own file's pass could not see.
250
+ func _carried(
251
+ value: Variant, was: String, now: String, entry: Dictionary, elsewhere: String
252
+ ) -> Array:
248
253
  match typeof(value):
249
254
  TYPE_NODE_PATH:
250
255
  var path: NodePath = value
@@ -256,7 +261,10 @@ func _carried(value: Variant, was: String, now: String, entry: Dictionary, elsew
256
261
  not_retargeted.append(
257
262
  _left(
258
263
  record,
259
- "The value is set in %s, and an edit here would not be saved there." % elsewhere
264
+ (
265
+ "The value is set in %s, and an edit here would not be saved there."
266
+ % elsewhere
267
+ )
260
268
  )
261
269
  )
262
270
  elif after[0] == DELETED:
@@ -350,7 +358,9 @@ func _after(path: NodePath, was: String, now: String) -> Array:
350
358
  ## Whether [param path], held by the node at [param now], names no node.
351
359
  func _names_nothing(path: NodePath, now: String) -> bool:
352
360
  var holder: Node = _scene.root.get_node_or_null(now)
353
- return holder != null and holder.get_node_or_null(NodePath(path.get_concatenated_names())) == null
361
+ return (
362
+ holder != null and holder.get_node_or_null(NodePath(path.get_concatenated_names())) == null
363
+ )
354
364
 
355
365
 
356
366
  ## The path to [param target] through the unique node that was at [param start], written through its
@@ -424,8 +434,9 @@ func _tracks(mixer: AnimationMixer, now: String) -> void:
424
434
  var animation: Animation = library.get_animation(animation_name)
425
435
  var home: Dictionary = _home_of(animation, library)
426
436
  var why: String = home["why"]
427
- # Carried once, from the first scene playing it. One kept in a file of its own that this call
428
- # rewrote is held in _homes, so a later scene loading that file is given this same copy.
437
+ # Carried once, from the first scene playing it. One kept in a file of its own that this
438
+ # call rewrote is held in _homes, so a later scene loading that file is given this same
439
+ # copy.
429
440
  if _seen.has(animation):
430
441
  continue
431
442
  _seen[animation] = true
@@ -466,13 +477,14 @@ func _tracks(mixer: AnimationMixer, now: String) -> void:
466
477
 
467
478
 
468
479
  ## Where an edit to [param animation], played from [param library], is saved, as {file, home, why,
469
- ## theirs}: file "" with the scene, or the file of its own it or its library is kept in, with home the
470
- ## resource saved there; why, when nothing written here would keep the edit; theirs, when that is
471
- ## because it is kept in another scene or an imported file, whose tracks are written in its terms.
480
+ ## theirs}: file "" with the scene, or the file of its own it or its library is kept in, with home
481
+ ## the resource saved there; why, when nothing written here would keep the edit; theirs, when that
482
+ ## is because it is kept in another scene or an imported file, whose tracks are written in its
483
+ ## terms.
472
484
  func _home_of(animation: Animation, library: AnimationLibrary) -> Dictionary:
473
485
  var file: String = animation.resource_path.get_slice("::", 0)
474
486
  if file.is_empty() or file == _scene.path:
475
- return {"file": "", "why": ""}
487
+ return { "file": "", "why": "" }
476
488
  var home: Resource = animation
477
489
  if animation.resource_path.contains("::"):
478
490
  home = library if library.resource_path == file else null
@@ -483,18 +495,21 @@ func _home_of(animation: Animation, library: AnimationLibrary) -> Dictionary:
483
495
  }
484
496
  if not Engine.is_editor_hint():
485
497
  var script: Script = home.get_script()
486
- if (script != null and not script.is_tool()) or GdhHeadlessCheck.holds_scripted_embedded(home, {}):
498
+ if (
499
+ (script != null and not script.is_tool())
500
+ or GdhHeadlessCheck.holds_scripted_embedded(home, {})
501
+ ):
487
502
  return {
488
503
  "why":
489
504
  (
490
505
  (
491
506
  (
492
- "%s holds a resource with a script that is not a tool, which a headless engine runs as "
493
- + "it loads the file. "
507
+ "%s holds a resource with a script that is not a tool, which a "
508
+ + "headless engine runs as it loads the file. "
494
509
  )
495
510
  % file
496
511
  )
497
512
  + GdhHeadlessCheck.REMEDY
498
- )
513
+ ),
499
514
  }
500
- return {"file": file, "home": home, "why": ""}
515
+ return { "file": file, "home": home, "why": "" }
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## What a new node in a scene is made or copied from, what the scene owns of it, and where among its
5
4
  ## siblings a node may go.
6
5
 
@@ -15,7 +14,8 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
15
14
  if node_type.is_empty() == instance_path.is_empty():
16
15
  return [
17
16
  null,
18
- "add takes nodeType for a node of a class, or instancePath for a scene, and one of them only."
17
+ "add takes nodeType for a node of a class, or instancePath for a scene, and one of "
18
+ + "them only.",
19
19
  ]
20
20
  if not instance_path.is_empty():
21
21
  # Asked before the cycle walk, which reads the file's dependencies and so loaded a file that
@@ -35,9 +35,12 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
35
35
  return [
36
36
  null,
37
37
  (
38
- "nodeType must be a node class, and %s is a scene: pass it as instancePath to instance it."
38
+ (
39
+ "nodeType must be a node class, and %s is a scene: pass it as instancePath to "
40
+ + "instance it."
41
+ )
39
42
  % node_type
40
- )
43
+ ),
41
44
  ]
42
45
  if not ClassDB.class_exists(node_type) or not ClassDB.is_parent_class(node_type, "Node"):
43
46
  return [null, not_a_node_class("nodeType", node_type)]
@@ -73,10 +76,12 @@ static func made(packed: PackedScene, node_type: String) -> Node:
73
76
  static func copied(source: Node, root: Node, copies: Dictionary) -> Node:
74
77
  var copy: Node = null
75
78
  if not source.scene_file_path.is_empty():
76
- # An instanced scene made again from its file, so the copy is an instance too. Node.duplicate
77
- # makes a plain node of the instance's class, and the pack saved it with every property of the
78
- # instance's root frozen into this scene.
79
- var packed: PackedScene = ResourceLoader.load(source.scene_file_path, "PackedScene") as PackedScene
79
+ # An instanced scene made again from its file, so the copy is an instance too.
80
+ # Node.duplicate makes a plain node of the instance's class, and the pack saved it with
81
+ # every property of the instance's root frozen into this scene.
82
+ var packed: PackedScene = (
83
+ ResourceLoader.load(source.scene_file_path, "PackedScene") as PackedScene
84
+ )
80
85
  if packed == null:
81
86
  return null
82
87
  copy = packed.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
@@ -123,7 +128,11 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
123
128
  if not flags & Object.CONNECT_PERSIST or target == null:
124
129
  continue
125
130
  var to: Object = copies.get(target, target)
126
- if to == target and target is Node and (target == top or top.is_ancestor_of(target as Node)):
131
+ if (
132
+ to == target
133
+ and target is Node
134
+ and (target == top or top.is_ancestor_of(target as Node))
135
+ ):
127
136
  continue
128
137
  var aimed: Callable = Callable(to, callable.get_method())
129
138
  if (
@@ -131,7 +140,10 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
131
140
  and copy.connect(signal_name, aimed, flags) != OK
132
141
  ):
133
142
  push_error(
134
- "gdharness: could not copy the connection %s of %s" % [signal_name, source.name]
143
+ (
144
+ "gdharness: could not copy the connection %s of %s"
145
+ % [signal_name, source.name]
146
+ )
135
147
  )
136
148
 
137
149
 
@@ -150,7 +162,9 @@ static func own(node: Node, root: Node) -> void:
150
162
  static func unplaceable(parent: Node, node: Node, asked: Variant) -> String:
151
163
  if asked == null:
152
164
  return ""
153
- var others: int = parent.get_child_count() - (1 if node != null and node.get_parent() == parent else 0)
165
+ var others: int = (
166
+ parent.get_child_count() - (1 if node != null and node.get_parent() == parent else 0)
167
+ )
154
168
  var at: int = GdhRead.as_int(asked, -1)
155
169
  if at < 0 or at > others:
156
170
  return (
@@ -159,7 +173,7 @@ static func unplaceable(parent: Node, node: Node, asked: Variant) -> String:
159
173
  str(at) if asked is int or asked is float else JSON.stringify(asked),
160
174
  others,
161
175
  parent.name,
162
- others
176
+ others,
163
177
  ]
164
178
  )
165
179
  return ""
@@ -234,7 +248,8 @@ static func _spatial_space(parent: Node) -> Transform3D:
234
248
  return space
235
249
 
236
250
 
237
- ## Where the children of [param parent] are drawn in 2D, as [method _spatial_space] works it out in 3D.
251
+ ## Where the children of [param parent] are drawn in 2D, as [method _spatial_space] works it out in
252
+ ## 3D.
238
253
  static func _canvas_space(parent: Node) -> Transform2D:
239
254
  var space: Transform2D = Transform2D.IDENTITY
240
255
  var at: Node = parent
@@ -0,0 +1,66 @@
1
+ @tool
2
+ extends RefCounted
3
+ ## Where else in a scene a resource built into it is held, since such a resource is one object
4
+ ## wherever the scene uses it.
5
+
6
+ const GdhPropertyPaths = preload("../property_paths.gd")
7
+ const GdhRead = preload("../reading.gd")
8
+ const GdhSceneFile = preload("../scene_file.gd")
9
+
10
+
11
+ ## Every other place in [param scene] holding a resource that [param key] was written through on
12
+ ## [param node], as {node, property}. A copy made by duplicate shares its original's resources, so
13
+ ## the write changed each of them too.
14
+ static func holders(scene: GdhSceneFile, node: Node, key: String) -> Array[Dictionary]:
15
+ var found_at: Array[Dictionary] = []
16
+ var through: Array = GdhPropertyPaths.holder_of(node, key, GdhPropertyPaths.home_of(node))[2]
17
+ if through.is_empty():
18
+ return found_at
19
+ var wanted: Dictionary = {}
20
+ for reached: Resource in through:
21
+ wanted[reached.get_instance_id()] = true
22
+ var nodes: Array[Node] = [scene.root]
23
+ nodes.append_array(scene.root.find_children("*", "", true, false))
24
+ for at: Node in nodes:
25
+ for declared: Dictionary in at.get_property_list():
26
+ var property: String = str(declared.get("name", ""))
27
+ if (
28
+ property.is_empty()
29
+ or not (GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE)
30
+ ):
31
+ continue
32
+ for found: String in _paths_to(at.get(property), property, wanted, {}):
33
+ if at != node or not key.begins_with(found + "/"):
34
+ found_at.append({ "node": scene.path_of(at), "property": found })
35
+ return found_at
36
+
37
+
38
+ ## The paths below [param path] at which [param value] holds a resource of [param wanted], by
39
+ ## instance id, stepping into lists and into resources built into a scene.
40
+ static func _paths_to(
41
+ value: Variant, path: String, wanted: Dictionary, seen: Dictionary
42
+ ) -> Array[String]:
43
+ var paths: Array[String] = []
44
+ if value is Array:
45
+ var items: Array = value
46
+ for index: int in items.size():
47
+ paths.append_array(_paths_to(items[index], "%s/%d" % [path, index], wanted, seen))
48
+ return paths
49
+ if not value is Resource:
50
+ return paths
51
+ var held: Resource = value
52
+ var id: int = held.get_instance_id()
53
+ if wanted.has(id):
54
+ paths.append(path)
55
+ var built_in: bool = held.resource_path.is_empty() or held.resource_path.contains("::")
56
+ if seen.has(id) or not built_in:
57
+ return paths
58
+ seen[id] = true
59
+ for declared: Dictionary in held.get_property_list():
60
+ var property: String = str(declared.get("name", ""))
61
+ if (
62
+ not property.is_empty()
63
+ and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE
64
+ ):
65
+ paths.append_array(_paths_to(held.get(property), path + "/" + property, wanted, seen))
66
+ return paths
@@ -0,0 +1 @@
1
+ uid://blndo1urj5l2w