gdharness 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/build/cli.js +14 -13
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
  3. package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
  6. package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
  7. package/build/godot/addons/gdharness_editor/property_values.gd +120 -53
  8. package/build/godot/addons/gdharness_editor/reading.gd +28 -0
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
  10. package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
  11. package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
  12. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
  13. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  15. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
  16. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +48 -41
  17. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
  18. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
  19. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
  20. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +81 -77
  21. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
  22. package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
  23. package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
  25. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
  26. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
  27. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
  28. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
  29. package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
  30. package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
  31. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
  32. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
  33. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
  34. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
  35. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
  36. package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
  37. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
  38. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
  39. package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
  40. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
  41. package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
  42. package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
  43. package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
  44. package/build/godot/operations/animation_reads.gd +7 -19
  45. package/build/godot/operations/audio_buses.gd +10 -10
  46. package/build/godot/operations/class_cache.gd +16 -16
  47. package/build/godot/operations/classdb_queries.gd +12 -12
  48. package/build/godot/operations/dependencies.gd +33 -29
  49. package/build/godot/operations/editor_tools.gd +12 -12
  50. package/build/godot/operations/export_presets.gd +10 -8
  51. package/build/godot/operations/file_reads.gd +60 -25
  52. package/build/godot/operations/gdscript_analysis.gd +20 -20
  53. package/build/godot/operations/gdscript_authoring.gd +17 -15
  54. package/build/godot/operations/godot_operations.gd +64 -63
  55. package/build/godot/operations/import_pipeline.gd +20 -20
  56. package/build/godot/operations/input_actions.gd +17 -17
  57. package/build/godot/operations/logger.gd +1 -1
  58. package/build/godot/operations/plugins.gd +8 -8
  59. package/build/godot/operations/project_config.gd +24 -17
  60. package/build/godot/operations/project_diagnostics.gd +9 -9
  61. package/build/godot/operations/property_values.gd +120 -53
  62. package/build/godot/operations/reading.gd +28 -0
  63. package/build/godot/operations/rename_checks.gd +3 -3
  64. package/build/godot/operations/resource_files.gd +5 -5
  65. package/build/godot/operations/script_check.gd +7 -7
  66. package/build/godot/operations/serialisation.gd +45 -94
  67. package/build/godot/operations/value_shapes.gd +111 -0
  68. package/package.json +1 -1
@@ -1,15 +1,16 @@
1
1
  extends RefCounted
2
2
 
3
- const Read = preload("reading.gd")
4
- const Log = preload("logger.gd")
5
- const ProjectFile = preload("project_file.gd")
6
- const Serialisation = preload("serialisation.gd")
3
+ const GdhRead = preload("reading.gd")
4
+ const GdhLog = preload("logger.gd")
5
+ const GdhProjectFile = preload("project_file.gd")
6
+ const GdhSerialisation = preload("serialisation.gd")
7
+ const GdhShapes = preload("value_shapes.gd")
7
8
 
8
- var _log: Log
9
- var _values: Serialisation = Serialisation.new()
9
+ var _log: GdhLog
10
+ var _values: GdhSerialisation = GdhSerialisation.new()
10
11
 
11
12
 
12
- func _init(p_log: Log) -> void:
13
+ func _init(p_log: GdhLog) -> void:
13
14
  _log = p_log
14
15
 
15
16
 
@@ -76,7 +77,7 @@ func _settings_under(prefix: String) -> Dictionary:
76
77
  var name: String = str(property.get("name", ""))
77
78
  if not name.begins_with(prefix):
78
79
  continue
79
- var kind: int = Read.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
80
+ var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
80
81
  var value: Variant = ProjectSettings.get_setting(name)
81
82
  var described: Dictionary = {
82
83
  "setting": name,
@@ -113,8 +114,13 @@ func set_project_setting(params: Dictionary) -> Dictionary:
113
114
  var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
114
115
  var was_default: bool = had_value and default_value != null and old_value == default_value
115
116
 
116
- var final_value: Variant = _values.deserialize_value(value)
117
117
  var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
118
+ # A value of fields or a colour's html code is fitted as a property write fits it, so a value one
119
+ # of them takes the other takes too. A number is left to the check below, which refuses one the
120
+ # setting would cut where fitting it would cut it.
121
+ var final_value: Variant = _values.deserialize_value(value)
122
+ if final_value is Dictionary or (wanted == TYPE_COLOR and final_value is String):
123
+ final_value = _values.fitted(value, wanted)
118
124
  if wanted > TYPE_NIL and final_value != null and typeof(final_value) != wanted:
119
125
  if not _converts_faithfully(final_value, wanted):
120
126
  return _log.failure(_cannot_hold(setting_path, wanted, final_value))
@@ -136,7 +142,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
136
142
  return answer
137
143
 
138
144
  ProjectSettings.set_setting(setting_path, final_value)
139
- var err: Error = ProjectFile.save()
145
+ var err: Error = GdhProjectFile.save()
140
146
  if err != OK:
141
147
  return _log.failure("Failed to save project.godot: " + error_string(err))
142
148
  return answer
@@ -157,7 +163,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
157
163
  func _wanted_type(setting_path: String, current: Variant) -> int:
158
164
  for info: Dictionary in ProjectSettings.get_property_list():
159
165
  if str(info.get("name", "")) == setting_path:
160
- return Read.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
166
+ return GdhRead.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
161
167
  return typeof(current)
162
168
 
163
169
 
@@ -181,19 +187,20 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
181
187
  if wanted in numbers and typeof(value) in numbers:
182
188
  return "%s, and %s is not the same number as one.%s" % [declared, str(value), cost]
183
189
  var given: String = type_string(typeof(value))
184
- return "%s, and %s (%s) cannot become one." % [declared, JSON.stringify(value), given]
190
+ var remedy: String = GdhShapes.remedy(value, wanted)
191
+ return "%s, and %s (%s) cannot become one.%s" % [declared, JSON.stringify(value), given, remedy]
185
192
 
186
193
 
187
194
  # Add an autoload singleton
188
195
  func add_autoload(params: Dictionary) -> Dictionary:
189
196
  var name: String = str(params.get("name", ""))
190
197
  var path: String = str(params.get("path", ""))
191
- var global: bool = Read.as_bool(params.get("global", true), true)
198
+ var global: bool = GdhRead.as_bool(params.get("global", true), true)
192
199
 
193
200
  # There is no disabled autoload in Godot 4: every entry in the list is loaded under the root at
194
201
  # every start. `enabled: false` used to write the entry without its asterisk and answer that it
195
202
  # was disabled, and the autoload ran all the same.
196
- if not Read.as_bool(params.get("enabled", true), true):
203
+ if not GdhRead.as_bool(params.get("enabled", true), true):
197
204
  return _log.failure(
198
205
  (
199
206
  "An autoload cannot be added disabled: Godot loads every autoload in the list at every"
@@ -219,7 +226,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
219
226
  # "Global Variable" column. Without it the node still comes up under the root; only the name
220
227
  # stops resolving.
221
228
  ProjectSettings.set_setting(setting, ("*" if global else "") + path)
222
- var err: Error = ProjectFile.save()
229
+ var err: Error = GdhProjectFile.save()
223
230
  if err != OK:
224
231
  return _log.failure("Failed to save project.godot: " + error_string(err))
225
232
 
@@ -244,7 +251,7 @@ func remove_autoload(params: Dictionary) -> Dictionary:
244
251
 
245
252
  var old_value: String = str(ProjectSettings.get_setting(setting))
246
253
  ProjectSettings.set_setting(setting, null)
247
- var err: Error = ProjectFile.save()
254
+ var err: Error = GdhProjectFile.save()
248
255
  if err != OK:
249
256
  return _log.failure("Failed to save project.godot: " + error_string(err))
250
257
 
@@ -293,7 +300,7 @@ func set_main_scene(params: Dictionary) -> Dictionary:
293
300
  var old_main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
294
301
 
295
302
  ProjectSettings.set_setting("application/run/main_scene", scene_path)
296
- var err: Error = ProjectFile.save()
303
+ var err: Error = GdhProjectFile.save()
297
304
 
298
305
  if err != OK:
299
306
  return _log.failure("Failed to save project settings: " + str(err))
@@ -1,14 +1,14 @@
1
1
  extends RefCounted
2
2
 
3
- const FileWalk = preload("file_walk.gd")
4
- const ImportPipeline = preload("import_pipeline.gd")
5
- const Log = preload("logger.gd")
3
+ const GdhFileWalk = preload("file_walk.gd")
4
+ const GdhImportPipeline = preload("import_pipeline.gd")
5
+ const GdhLog = preload("logger.gd")
6
6
 
7
- var _log: Log
8
- var _files: FileWalk = FileWalk.new()
7
+ var _log: GdhLog
8
+ var _files: GdhFileWalk = GdhFileWalk.new()
9
9
 
10
10
 
11
- func _init(p_log: Log) -> void:
11
+ func _init(p_log: GdhLog) -> void:
12
12
  _log = p_log
13
13
 
14
14
 
@@ -44,7 +44,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
44
44
  # Every format the engine imports, the list the import status reads: five of them were checked
45
45
  # here, so a project of glTF models, fonts and WebP textures had nothing to reimport.
46
46
  var resources: Array[String] = _files.find_files_with_extensions(
47
- "res://", ImportPipeline.IMPORTABLE_EXTENSIONS
47
+ "res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS
48
48
  )
49
49
 
50
50
  var missing_imports: int = 0
@@ -119,8 +119,8 @@ func get_project_health(params: Dictionary) -> Dictionary:
119
119
  if main_scene.is_empty():
120
120
  details.append("No main scene configured")
121
121
  deductions += 10
122
- elif not FileWalk.missing_reason(main_scene).is_empty():
123
- details.append("Main scene " + FileWalk.missing_reason(main_scene))
122
+ elif not GdhFileWalk.missing_reason(main_scene).is_empty():
123
+ details.append("Main scene " + GdhFileWalk.missing_reason(main_scene))
124
124
  deductions += 15
125
125
 
126
126
  var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))
@@ -14,8 +14,9 @@ extends RefCounted
14
14
  ## write accepts. What is here on top of it is the part only a property knows: the type wanted, so a
15
15
  ## dictionary shaped like a vector or a bare pair of numbers can be read as one.
16
16
 
17
- const Read = preload("reading.gd")
18
- const Serialisation = preload("serialisation.gd")
17
+ const GdhRead = preload("reading.gd")
18
+ const GdhSerialisation = preload("serialisation.gd")
19
+ const GdhShapes = preload("value_shapes.gd")
19
20
 
20
21
  # The data types a Theme names its items by, as in "Button/colors/font_color", and what each holds.
21
22
  const THEME_ITEMS: Dictionary[String, Array] = {
@@ -65,12 +66,18 @@ const FLOAT_SHAPES: Array[int] = [
65
66
  # names, so a write to "Visibility" or "Transform" changed nothing and was answered as made.
66
67
  const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
67
68
 
68
- var _values: Serialisation = Serialisation.new()
69
+ var _values: GdhSerialisation = GdhSerialisation.new()
69
70
 
70
71
  # Why the value being parsed cannot be written, when the parse is what found out. A resource is
71
72
  # built inside the parse and there is nowhere in a parsed value to put the reason it is wrong.
72
73
  var _refused: String = ""
73
74
 
75
+ # The stored properties the last write_all changed without being given them, by name, as [what it
76
+ # held before, what it holds now]. A setter keeps other properties in step with the one it is given:
77
+ # a Gradient given a third colour gives that point an offset of 0, a Control given a size moves its
78
+ # offsets. An answer naming only what was given leaves the caller to guess the rest.
79
+ var _also_changed: Dictionary = {}
80
+
74
81
 
75
82
  ## Turns what arrived over the wire into the Godot value a property wants.
76
83
  ##
@@ -85,7 +92,7 @@ func parse(value: Variant, expected_type: int = TYPE_NIL, elements: Array = [])
85
92
  var fields: Dictionary = value
86
93
  # A dictionary with keys that are not text arrives as its entries, and is read against the
87
94
  # declared element types like any other.
88
- if str(fields.get("_type", "")) == "Dictionary" and Serialisation.pairs(fields.get("entries")):
95
+ if str(fields.get("_type", "")) == "Dictionary" and GdhSerialisation.pairs(fields.get("entries")):
89
96
  var built: Dictionary = _values.deserialize_value(fields)
90
97
  return _parse_entries(built, elements)
91
98
  var tagged: Array = _parse_tagged_dictionary(fields)
@@ -113,38 +120,39 @@ func cannot_hold(
113
120
  ) -> String:
114
121
  var held: String = type_string(expected_type)
115
122
  var cost: String = " Setting it would save a different value than the one asked for."
116
- if not Serialisation.acceptable(value, expected_type):
123
+ if not GdhSerialisation.acceptable(value, expected_type):
117
124
  var shape: String = "%s.%s is %s and the value given is %s, which cannot become one."
118
- return shape % [holder, property, held, type_string(typeof(value))] + cost
125
+ var remedy: String = GdhShapes.remedy(value, expected_type)
126
+ return shape % [holder, property, held, type_string(typeof(value))] + remedy + cost
119
127
 
120
128
  # And the same question of each element, because a list is accepted for a packed or typed array
121
129
  # whatever is in it: a polygon written as [{"x": 1}] passes the check above and lands as one zero
122
130
  # vector, and [1.5, "x"] for a PackedInt32Array as [1, 0].
123
- var element: int = Read.as_int(PACKED_ELEMENTS.get(expected_type, TYPE_NIL), TYPE_NIL)
131
+ var element: int = GdhRead.as_int(PACKED_ELEMENTS.get(expected_type, TYPE_NIL), TYPE_NIL)
124
132
  if expected_type == TYPE_ARRAY and not elements.is_empty():
125
- element = Read.as_int(elements[0], TYPE_NIL)
133
+ element = GdhRead.as_int(elements[0], TYPE_NIL)
126
134
  if element != TYPE_NIL and value is Array:
127
135
  var items: Array = value
128
136
  for index: int in items.size():
129
- if not Serialisation.acceptable(items[index], element):
137
+ if not GdhSerialisation.acceptable(items[index], element):
130
138
  var shape: String = "%s.%s is %s and item %d of the list given is %s, not %s."
131
139
  var named: Array = [
132
140
  holder, property, held, index, type_string(typeof(items[index])), type_string(element)
133
141
  ]
134
- return shape % named + cost
142
+ return shape % named + GdhShapes.remedy(items[index], element) + cost
135
143
  if expected_type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
136
144
  var entries: Dictionary = value
137
- var key_type: int = Read.as_int(elements[0], TYPE_NIL)
138
- var value_type: int = Read.as_int(elements[1], TYPE_NIL)
145
+ var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
146
+ var value_type: int = GdhRead.as_int(elements[1], TYPE_NIL)
139
147
  for key: Variant in entries:
140
- if not Serialisation.acceptable(key, key_type):
148
+ if not GdhSerialisation.acceptable(key, key_type):
141
149
  var shape: String = "%s.%s has %s keys, and %s is not one."
142
150
  return shape % [holder, property, type_string(key_type), shown(key)] + cost
143
151
  var entry: Variant = entries[key]
144
- if not Serialisation.acceptable(entry, value_type):
152
+ if not GdhSerialisation.acceptable(entry, value_type):
145
153
  var shape: String = "%s.%s holds %s values, and the one given for %s is %s."
146
154
  var named: Array = [holder, property, type_string(value_type), shown(key), shown(entry)]
147
- return shape % named + cost
155
+ return shape % named + GdhShapes.remedy(entry, value_type) + cost
148
156
  return ""
149
157
 
150
158
 
@@ -168,6 +176,11 @@ func cannot_hold(
168
176
  ## Nothing here saves, so a refusal partway leaves the change in memory and the file as it was. The
169
177
  ## caller returns the refusal instead of saving.
170
178
  func write_all(target: Object, properties: Dictionary) -> String:
179
+ _also_changed = {}
180
+ var named: Dictionary = {}
181
+ for key: Variant in properties:
182
+ named[str(key).get_slice(":", 0)] = true
183
+ var before: Dictionary = _stored(target, named)
171
184
  var given: Dictionary = {}
172
185
  for key: Variant in properties:
173
186
  var property: String = str(key)
@@ -197,9 +210,62 @@ func write_all(target: Object, properties: Dictionary) -> String:
197
210
  )
198
211
  % [target.get_class(), property, shown(given[property]), shown(held)]
199
212
  )
213
+ for property: String in before:
214
+ var now: Variant = target.get(property)
215
+ if not same(now, before[property]):
216
+ _also_changed[property] = [before[property], now]
200
217
  return ""
201
218
 
202
219
 
220
+ ## Puts under alsoChanged in [param answer] each property the last [method write_all] changed
221
+ ## without being given it, as {was, now}, when there are any. A node is named by its path from
222
+ ## [param relative_to], as a scene answer names one. Called before the scene is written, since the
223
+ ## write frees the nodes these values can hold.
224
+ func note_also_changed(answer: Dictionary, relative_to: Node = null) -> void:
225
+ if _also_changed.is_empty():
226
+ return
227
+ var noted: Dictionary = {}
228
+ for property: Variant in _also_changed:
229
+ var pair: Array = _also_changed[property]
230
+ noted[property] = {"was": _readable(pair[0], relative_to), "now": _readable(pair[1], relative_to)}
231
+ answer["alsoChanged"] = noted
232
+
233
+
234
+ func _readable(value: Variant, relative_to: Node) -> Variant:
235
+ if value is Node and relative_to != null:
236
+ var target: Node = value
237
+ return _values.serialize_value(relative_to.get_path_to(target))
238
+ if value is Array:
239
+ var items: Array = value
240
+ return items.map(func(item: Variant) -> Variant: return _readable(item, relative_to))
241
+ return _values.serialize_value(value)
242
+
243
+
244
+ ## What [param target] stores, by name, but its script and the properties in [param leave]. A list
245
+ ## or a map is copied, since a setter can change the one it holds in place.
246
+ func _stored(target: Object, leave: Dictionary) -> Dictionary:
247
+ var held: Dictionary = {}
248
+ for entry: Dictionary in target.get_property_list():
249
+ var name: String = str(entry["name"])
250
+ var usage: int = GdhRead.as_int(entry["usage"])
251
+ if (
252
+ usage & PROPERTY_USAGE_STORAGE == 0
253
+ or usage & HEADINGS != 0
254
+ or name == "script"
255
+ or leave.has(name)
256
+ ):
257
+ continue
258
+ var value: Variant = target.get(name)
259
+ if value is Array:
260
+ var listed: Array = value
261
+ value = listed.duplicate(true)
262
+ elif value is Dictionary:
263
+ var mapped: Dictionary = value
264
+ value = mapped.duplicate(true)
265
+ held[name] = value
266
+ return held
267
+
268
+
203
269
  ## What [param target] holds under [param key], a property or a colon path into one's components.
204
270
  func read_at(target: Object, key: String) -> Variant:
205
271
  if ":" in key and declaration(target, key).is_empty():
@@ -228,9 +294,8 @@ func _component_slot(target: Object, path: String) -> Dictionary:
228
294
  var walked: String = steps[0]
229
295
  for index: int in range(1, steps.size()):
230
296
  var step: String = steps[index]
231
- var has: PackedStringArray = Serialisation.components_of(value)
232
- var kind: String = type_string(typeof(value))
233
- var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
297
+ var has: PackedStringArray = GdhSerialisation.components_of(value)
298
+ var sort: String = GdhRead.with_article(type_string(typeof(value)))
234
299
  if has.is_empty():
235
300
  return {
236
301
  "refused":
@@ -255,8 +320,8 @@ func _component_slot(target: Object, path: String) -> Dictionary:
255
320
  ## [param raw] as the value [param declared] takes, as [ok, value] or [false, why not].
256
321
  func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
257
322
  var property: String = str(declared.get("name", ""))
258
- var expected_type: int = Read.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
259
- var hint: int = Read.as_int(declared.get("hint", PROPERTY_HINT_NONE))
323
+ var expected_type: int = GdhRead.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
324
+ var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
260
325
  if expected_type == TYPE_OBJECT and hint == PROPERTY_HINT_NODE_TYPE:
261
326
  return _node_reference(target, property, raw)
262
327
 
@@ -272,7 +337,7 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
272
337
  var elements: Array = elements_of(declared)
273
338
  if _fraction(raw, expected_type, elements):
274
339
  return [false, _fraction_refusal(target, property, raw)]
275
- var rounded: String = Serialisation.inexact(raw, expected_type)
340
+ var rounded: String = GdhSerialisation.inexact(raw, expected_type)
276
341
  if not rounded.is_empty():
277
342
  return [false, "%s.%s holds int: %s." % [target.get_class(), property, rounded]]
278
343
  _refused = ""
@@ -328,7 +393,7 @@ func _node_reference(target: Object, property: String, raw: Variant) -> Array:
328
393
  ## an object of the wrong class: measured on 4.7.2, a Sprite2D given a Material for its texture
329
394
  ## held nothing afterwards, and the scene was saved without the texture it had.
330
395
  func _wrong_class(value: Object, declared: Dictionary) -> String:
331
- var hint: int = Read.as_int(declared.get("hint", PROPERTY_HINT_NONE))
396
+ var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
332
397
  var wanted: String = str(declared.get("hint_string", ""))
333
398
  if hint != PROPERTY_HINT_RESOURCE_TYPE or wanted.is_empty():
334
399
  return ""
@@ -354,8 +419,8 @@ func _wrong_class(value: Object, declared: Dictionary) -> String:
354
419
  ## behind a placeholder in the editor, and measured on 4.7.2 that placeholder stores [1.5, 2.0]
355
420
  ## as given, which the game then fails to load into the typed array.
356
421
  static func elements_of(declared: Dictionary) -> Array:
357
- var type: int = Read.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
358
- var hint: int = Read.as_int(declared.get("hint", PROPERTY_HINT_NONE))
422
+ var type: int = GdhRead.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
423
+ var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
359
424
  var wanted: String = str(declared.get("hint_string", ""))
360
425
  if wanted.is_empty():
361
426
  return []
@@ -397,21 +462,21 @@ func _fraction(value: Variant, type: int, elements: Array = []) -> bool:
397
462
  if type == TYPE_INT:
398
463
  var number: Variant = value
399
464
  if typeof(value) == TYPE_STRING:
400
- number = Read.json_or_null(str(value))
465
+ number = GdhRead.json_or_null(str(value))
401
466
  if typeof(number) != TYPE_FLOAT:
402
467
  return false
403
468
  var whole: float = number
404
469
  return is_finite(whole) and whole != floorf(whole)
405
- var element: int = Read.as_int(PACKED_ELEMENTS.get(type, TYPE_NIL), TYPE_NIL)
470
+ var element: int = GdhRead.as_int(PACKED_ELEMENTS.get(type, TYPE_NIL), TYPE_NIL)
406
471
  if type == TYPE_ARRAY and not elements.is_empty():
407
- element = Read.as_int(elements[0], TYPE_NIL)
472
+ element = GdhRead.as_int(elements[0], TYPE_NIL)
408
473
  if value is Array and element != TYPE_NIL:
409
474
  var items: Array = value
410
475
  return items.any(func(item: Variant) -> bool: return _fraction(item, element))
411
476
  if type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
412
477
  var entries: Dictionary = value
413
- var key_type: int = Read.as_int(elements[0], TYPE_NIL)
414
- var value_type: int = Read.as_int(elements[1], TYPE_NIL)
478
+ var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
479
+ var value_type: int = GdhRead.as_int(elements[1], TYPE_NIL)
415
480
  for key: Variant in entries:
416
481
  if _fraction(key, key_type) or _fraction(entries[key], value_type):
417
482
  return true
@@ -463,7 +528,7 @@ func declaration(target: Object, property: String) -> Dictionary:
463
528
  for declared: Dictionary in target.get_property_list():
464
529
  if str(declared.get("name", "")) != property:
465
530
  continue
466
- if Read.as_int(declared.get("usage", 0)) & HEADINGS:
531
+ if GdhRead.as_int(declared.get("usage", 0)) & HEADINGS:
467
532
  continue
468
533
  return declared
469
534
  if target is Theme:
@@ -503,13 +568,13 @@ static func same(held: Variant, given: Variant) -> bool:
503
568
  return held_none == given_none
504
569
  if held_type == TYPE_OBJECT or given_type == TYPE_OBJECT:
505
570
  return _same_object(held, given)
506
- if Serialisation.NUMBERS.has(held_type) and Serialisation.NUMBERS.has(given_type):
571
+ if GdhSerialisation.NUMBERS.has(held_type) and GdhSerialisation.NUMBERS.has(given_type):
507
572
  if held_type == TYPE_BOOL or given_type == TYPE_BOOL:
508
573
  return held_type == given_type and held == given
509
- var held_number: float = Read.as_float(held)
510
- var given_number: float = Read.as_float(given)
574
+ var held_number: float = GdhRead.as_float(held)
575
+ var given_number: float = GdhRead.as_float(given)
511
576
  return (is_nan(held_number) and is_nan(given_number)) or is_equal_approx(held_number, given_number)
512
- if Serialisation.TEXTS.has(held_type) and Serialisation.TEXTS.has(given_type):
577
+ if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
513
578
  return str(held) == str(given)
514
579
  if _listed(held_type) and _listed(given_type):
515
580
  var held_items: Array = type_convert(held, TYPE_ARRAY)
@@ -675,7 +740,7 @@ func _parse_shaped_dictionary(value: Dictionary, expected_type: int, elements: A
675
740
  if expected_type == TYPE_NIL or expected_type == TYPE_DICTIONARY:
676
741
  return _parse_entries(value, elements)
677
742
  var tag: String = type_string(expected_type)
678
- if Serialisation.REQUIRED.has(tag):
743
+ if GdhShapes.REQUIRED.has(tag):
679
744
  var tagged: Dictionary = value.duplicate()
680
745
  tagged["_type"] = tag
681
746
  var built: Variant = _values.deserialize_value(tagged)
@@ -690,8 +755,8 @@ func _parse_entries(value: Dictionary, elements: Array) -> Dictionary:
690
755
  var key_type: int = TYPE_NIL
691
756
  var value_type: int = TYPE_NIL
692
757
  if elements.size() == 2:
693
- key_type = Read.as_int(elements[0], TYPE_NIL)
694
- value_type = Read.as_int(elements[1], TYPE_NIL)
758
+ key_type = GdhRead.as_int(elements[0], TYPE_NIL)
759
+ value_type = GdhRead.as_int(elements[1], TYPE_NIL)
695
760
  var entries: Dictionary = {}
696
761
  for key: Variant in value:
697
762
  entries[_values.fitted(key, key_type)] = parse(value[key], value_type)
@@ -709,7 +774,7 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
709
774
  for key: Variant in entries:
710
775
  if key_type != TYPE_NIL and typeof(key) != key_type:
711
776
  return entries
712
- if not Serialisation.acceptable(entries[key], value_type):
777
+ if not GdhSerialisation.acceptable(entries[key], value_type):
713
778
  return entries
714
779
  var fitted: Dictionary = {}
715
780
  for key: Variant in entries:
@@ -720,46 +785,48 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
720
785
  ## An array, either positional for a vector the property declares, or a list to parse per item.
721
786
  func _parse_array(value: Array, expected_type: int, elements: Array) -> Variant:
722
787
  if PACKED_ELEMENTS.has(expected_type):
723
- var element: int = Read.as_int(PACKED_ELEMENTS[expected_type], TYPE_NIL)
788
+ var element: int = GdhRead.as_int(PACKED_ELEMENTS[expected_type], TYPE_NIL)
724
789
  return value.map(func(item: Variant) -> Variant: return parse(item, element))
725
790
 
726
791
  match expected_type:
727
792
  TYPE_VECTOR2:
728
793
  if value.size() == 2:
729
- return Vector2(Read.as_float(value[0]), Read.as_float(value[1]))
794
+ return Vector2(GdhRead.as_float(value[0]), GdhRead.as_float(value[1]))
730
795
  TYPE_VECTOR2I:
731
796
  if value.size() == 2:
732
- return Vector2i(Read.as_int(value[0]), Read.as_int(value[1]))
797
+ return Vector2i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]))
733
798
  TYPE_VECTOR3:
734
799
  if value.size() == 3:
735
- return Vector3(Read.as_float(value[0]), Read.as_float(value[1]), Read.as_float(value[2]))
800
+ return Vector3(
801
+ GdhRead.as_float(value[0]), GdhRead.as_float(value[1]), GdhRead.as_float(value[2])
802
+ )
736
803
  TYPE_VECTOR3I:
737
804
  if value.size() == 3:
738
- return Vector3i(Read.as_int(value[0]), Read.as_int(value[1]), Read.as_int(value[2]))
805
+ return Vector3i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]), GdhRead.as_int(value[2]))
739
806
  TYPE_VECTOR4:
740
807
  if value.size() == 4:
741
808
  return Vector4(
742
- Read.as_float(value[0]),
743
- Read.as_float(value[1]),
744
- Read.as_float(value[2]),
745
- Read.as_float(value[3])
809
+ GdhRead.as_float(value[0]),
810
+ GdhRead.as_float(value[1]),
811
+ GdhRead.as_float(value[2]),
812
+ GdhRead.as_float(value[3])
746
813
  )
747
814
  TYPE_COLOR:
748
815
  if value.size() == 3 or value.size() == 4:
749
816
  return Color(
750
- Read.as_float(value[0]),
751
- Read.as_float(value[1]),
752
- Read.as_float(value[2]),
753
- Read.as_float(value[3]) if value.size() == 4 else 1.0
817
+ GdhRead.as_float(value[0]),
818
+ GdhRead.as_float(value[1]),
819
+ GdhRead.as_float(value[2]),
820
+ GdhRead.as_float(value[3]) if value.size() == 4 else 1.0
754
821
  )
755
822
 
756
823
  var element_type: int = TYPE_NIL
757
824
  if expected_type == TYPE_ARRAY and not elements.is_empty():
758
- element_type = Read.as_int(elements[0], TYPE_NIL)
825
+ element_type = GdhRead.as_int(elements[0], TYPE_NIL)
759
826
  var items: Array = value.map(func(item: Variant) -> Variant: return parse(item, element_type))
760
827
  if element_type == TYPE_NIL or element_type == TYPE_OBJECT:
761
828
  return items
762
- if not items.all(func(item: Variant) -> bool: return Serialisation.acceptable(item, element_type)):
829
+ if not items.all(func(item: Variant) -> bool: return GdhSerialisation.acceptable(item, element_type)):
763
830
  return items
764
831
  var fitted: Array = items.map(func(item: Variant) -> Variant: return type_convert(item, element_type))
765
832
  return Array(fitted, element_type, &"", null)
@@ -139,3 +139,31 @@ static func missing_below(
139
139
  "its children are " + shown if not children.is_empty() else "it has no children",
140
140
  ]
141
141
  )
142
+
143
+
144
+ ## [param said], the refusal for a [param kind] the scene does not have at [param node_path], with
145
+ ## what is there when the scene has a node there, [param found] its class, and [param holders], the
146
+ ## paths of the nodes in the scene that are one, so the caller has the path to pass. Names up to
147
+ ## [constant NAMED_CHILDREN] of them.
148
+ static func not_of_kind(
149
+ said: String, node_path: String, found: String, kind: String, holders: PackedStringArray
150
+ ) -> String:
151
+ var told: String = said + ("" if said.ends_with(".") else ".")
152
+ if not found.is_empty():
153
+ told += ' The node at "%s" is %s, not %s.' % [node_path, with_article(found), with_article(kind)]
154
+ if holders.is_empty():
155
+ return told + " The scene holds no %s." % kind
156
+ var quoted: PackedStringArray = []
157
+ for holder: String in holders.slice(0, NAMED_CHILDREN):
158
+ var _quoted: bool = quoted.append('"%s"' % holder)
159
+ var shown: String = ", ".join(quoted)
160
+ if holders.size() > NAMED_CHILDREN:
161
+ shown += " and %d more" % (holders.size() - NAMED_CHILDREN)
162
+ if holders.size() == 1:
163
+ return told + " The scene's %s is at %s." % [kind, shown]
164
+ return told + " The scene's %ss are at %s." % [kind, shown]
165
+
166
+
167
+ ## [param word] after the article it takes, "an AnimationTree" or "a Node2D".
168
+ static func with_article(word: String) -> String:
169
+ return ("an " if word.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + word
@@ -1,11 +1,11 @@
1
1
  extends RefCounted
2
2
 
3
- const Log = preload("logger.gd")
3
+ const GdhLog = preload("logger.gd")
4
4
 
5
- var _log: Log
5
+ var _log: GdhLog
6
6
 
7
7
 
8
- func _init(p_log: Log) -> void:
8
+ func _init(p_log: GdhLog) -> void:
9
9
  _log = p_log
10
10
 
11
11
 
@@ -1,7 +1,7 @@
1
1
  extends RefCounted
2
2
 
3
- const Log = preload("logger.gd")
4
- const Patterns = preload("patterns.gd")
3
+ const GdhLog = preload("logger.gd")
4
+ const GdhPatterns = preload("patterns.gd")
5
5
 
6
6
  ## Where the engine keeps a file's UID, by what kind of file it is. Measured on 4.7.2: a script or
7
7
  ## shader has a `.uid` file beside it, an imported file has it in its `.import`, and a text scene or
@@ -10,10 +10,10 @@ const Patterns = preload("patterns.gd")
10
10
  const SIDECAR_KINDS: Array[String] = ["gd", "gdshader", "gdshaderinc", "cs"]
11
11
  const TEXT_RESOURCE_KINDS: Array[String] = ["tscn", "tres", "escn"]
12
12
 
13
- var _log: Log
13
+ var _log: GdhLog
14
14
 
15
15
 
16
- func _init(p_log: Log) -> void:
16
+ func _init(p_log: GdhLog) -> void:
17
17
  _log = p_log
18
18
 
19
19
 
@@ -59,7 +59,7 @@ static func uid_of(file_path: String) -> Dictionary:
59
59
  if file != null:
60
60
  var header: String = file.get_line()
61
61
  file.close()
62
- var matched: RegExMatch = Patterns.compiled('\\buid="(uid://[^"]+)"').search(header)
62
+ var matched: RegExMatch = GdhPatterns.compiled('\\buid="(uid://[^"]+)"').search(header)
63
63
  if matched != null:
64
64
  return {"uid": matched.get_string(1), "from": "the file's header"}
65
65
  var id: int = ResourceLoader.get_resource_uid(file_path)
@@ -13,17 +13,17 @@ extends RefCounted
13
13
  ## reaches, and every script loaded after it that depends on it compiles in silence, reload() and all.
14
14
  ## So a run ends at the first script that raised anything, and the rest go to a fresh engine.
15
15
 
16
- const Read = preload("reading.gd")
17
- const Log = preload("logger.gd")
16
+ const GdhRead = preload("reading.gd")
17
+ const GdhLog = preload("logger.gd")
18
18
 
19
19
  ## Where the engine raises a script's compile errors from.
20
20
  const RELOAD: String = "GDScript::reload"
21
21
 
22
- var _log: Log
22
+ var _log: GdhLog
23
23
 
24
24
 
25
25
  ## Every compile error the engine raises while it is attached, in order.
26
- class Caught:
26
+ class GdhCaught:
27
27
  extends Logger
28
28
 
29
29
  var raised: Array[Dictionary] = []
@@ -50,7 +50,7 @@ class Caught:
50
50
  )
51
51
 
52
52
 
53
- func _init(p_log: Log) -> void:
53
+ func _init(p_log: GdhLog) -> void:
54
54
  _log = p_log
55
55
 
56
56
 
@@ -62,7 +62,7 @@ func _init(p_log: Log) -> void:
62
62
  ## resource cache, so a script loaded for an earlier one is compiled again for its own entry.
63
63
  func check_scripts(params: Dictionary) -> Dictionary:
64
64
  var asked: Array = params.get("paths", [])
65
- var caught: Caught = Caught.new()
65
+ var caught: GdhCaught = GdhCaught.new()
66
66
  OS.add_logger(caught)
67
67
  var checked: Array[String] = []
68
68
  var failed: Array[String] = []
@@ -80,7 +80,7 @@ func check_scripts(params: Dictionary) -> Dictionary:
80
80
  if not at in others:
81
81
  others.append(at)
82
82
  continue
83
- var found: Dictionary = {"line": Read.as_int(error["line"]), "message": str(error["message"])}
83
+ var found: Dictionary = {"line": GdhRead.as_int(error["line"]), "message": str(error["message"])}
84
84
  if not found in errors:
85
85
  errors.append(found)
86
86
  if loaded == null and errors.is_empty():