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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## Whether a project is ready to run and export: its settings, main scene, imports, presets,
|
|
4
|
+
## scripts, references and signal connections, each named under checks_performed.
|
|
5
|
+
|
|
6
|
+
const GdhDependencies = preload("dependencies.gd")
|
|
7
|
+
const GdhExportPresets = preload("export_presets.gd")
|
|
8
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
9
|
+
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
10
|
+
const GdhLog = preload("logger.gd")
|
|
11
|
+
const GdhRead = preload("reading.gd")
|
|
12
|
+
const GdhSceneConnections = preload("scene_connections.gd")
|
|
13
|
+
const GdhScriptCheck = preload("script_check.gd")
|
|
14
|
+
const CONNECTION_FIX: String = (
|
|
15
|
+
"Write the method or signal, or point the connection at the one that replaced it with" + " scene_signal"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
var _log: GdhLog
|
|
19
|
+
var _files: GdhFileWalk = GdhFileWalk.new()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
func _init(p_log: GdhLog) -> void:
|
|
23
|
+
_log = p_log
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
func validate_project(params: Dictionary) -> Dictionary:
|
|
27
|
+
var preset_name: String = str(params.get("preset", ""))
|
|
28
|
+
var include_suggestions: bool = GdhRead.as_bool(params.get("include_suggestions", true), true)
|
|
29
|
+
|
|
30
|
+
_log.info("Validating project" + (" for preset: " + preset_name if not preset_name.is_empty() else ""))
|
|
31
|
+
|
|
32
|
+
var issues: Array[Dictionary] = []
|
|
33
|
+
var warnings: Array[Dictionary] = []
|
|
34
|
+
var checks_performed: Array[String] = []
|
|
35
|
+
|
|
36
|
+
checks_performed.append("project_file")
|
|
37
|
+
if not FileAccess.file_exists("res://project.godot"):
|
|
38
|
+
issues.append(
|
|
39
|
+
_finding(
|
|
40
|
+
"error",
|
|
41
|
+
"project_file",
|
|
42
|
+
"project.godot not found",
|
|
43
|
+
"Ensure you are running this from a valid Godot project directory",
|
|
44
|
+
include_suggestions
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
checks_performed.append("main_scene")
|
|
49
|
+
var main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
|
|
50
|
+
if main_scene.is_empty():
|
|
51
|
+
issues.append(
|
|
52
|
+
_finding(
|
|
53
|
+
"error",
|
|
54
|
+
"main_scene",
|
|
55
|
+
"No main scene set",
|
|
56
|
+
"Set a main scene in Project Settings > Application > Run > Main Scene",
|
|
57
|
+
include_suggestions
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
elif not GdhFileWalk.missing_reason(main_scene).is_empty():
|
|
61
|
+
issues.append(
|
|
62
|
+
_finding(
|
|
63
|
+
"error",
|
|
64
|
+
"main_scene",
|
|
65
|
+
"Main scene " + GdhFileWalk.missing_reason(main_scene),
|
|
66
|
+
"Update the main scene setting or create the missing scene file",
|
|
67
|
+
include_suggestions
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# A file never imported is on disk, so the reference checks pass it, and still nothing loads it:
|
|
72
|
+
# a scene using one fails to load in a run or an export.
|
|
73
|
+
checks_performed.append("imports")
|
|
74
|
+
var never: Array[String] = []
|
|
75
|
+
for path: String in _files.find_files_with_extensions("res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS):
|
|
76
|
+
if not FileAccess.file_exists(path + ".import"):
|
|
77
|
+
never.append(path)
|
|
78
|
+
if not never.is_empty():
|
|
79
|
+
never.sort()
|
|
80
|
+
warnings.append(
|
|
81
|
+
_finding(
|
|
82
|
+
"warning",
|
|
83
|
+
"imports",
|
|
84
|
+
GdhImportPipeline.never_imported_line(never),
|
|
85
|
+
"Run project_import reimport, or open the project in the editor, whose scan imports them",
|
|
86
|
+
include_suggestions
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
checks_performed.append("export_presets")
|
|
91
|
+
if not FileAccess.file_exists("res://export_presets.cfg"):
|
|
92
|
+
warnings.append(
|
|
93
|
+
_finding(
|
|
94
|
+
"warning",
|
|
95
|
+
"export_presets",
|
|
96
|
+
"No export presets configured",
|
|
97
|
+
"Add a preset with project_export add_preset, or in the editor: Project > Export",
|
|
98
|
+
include_suggestions
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# The preset asked about is looked for rather than only logged: validating "for" a preset that
|
|
103
|
+
# does not exist answered valid, as though it had been checked.
|
|
104
|
+
if not preset_name.is_empty():
|
|
105
|
+
checks_performed.append("export_preset")
|
|
106
|
+
var named: Array[String] = GdhExportPresets.names()
|
|
107
|
+
if preset_name not in named:
|
|
108
|
+
issues.append(
|
|
109
|
+
_finding(
|
|
110
|
+
"error",
|
|
111
|
+
"export_preset",
|
|
112
|
+
(
|
|
113
|
+
"No export preset is named "
|
|
114
|
+
+ preset_name
|
|
115
|
+
+ (
|
|
116
|
+
": the project has none"
|
|
117
|
+
if named.is_empty()
|
|
118
|
+
else ": the presets are " + ", ".join(named)
|
|
119
|
+
)
|
|
120
|
+
),
|
|
121
|
+
"Pass one of the preset names, or add the preset with project_export add_preset",
|
|
122
|
+
include_suggestions
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
var gone: Array[String] = GdhExportPresets.missing_named(preset_name)
|
|
127
|
+
if not gone.is_empty():
|
|
128
|
+
warnings.append(
|
|
129
|
+
_finding(
|
|
130
|
+
"warning",
|
|
131
|
+
"export_preset_files",
|
|
132
|
+
"The preset %s names files that are not there: %s" % [preset_name, "; ".join(gone)],
|
|
133
|
+
"Add the files, or point the options at others with project_export set_preset",
|
|
134
|
+
include_suggestions
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
checks_performed.append("icon")
|
|
139
|
+
var icon_path: String = str(ProjectSettings.get_setting("application/config/icon", ""))
|
|
140
|
+
if icon_path.is_empty():
|
|
141
|
+
warnings.append(
|
|
142
|
+
_finding(
|
|
143
|
+
"warning",
|
|
144
|
+
"icon",
|
|
145
|
+
"No application icon set",
|
|
146
|
+
"Set an icon in Project Settings > Application > Config > Icon",
|
|
147
|
+
include_suggestions
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
elif not FileAccess.file_exists(icon_path):
|
|
151
|
+
warnings.append(
|
|
152
|
+
_finding(
|
|
153
|
+
"warning",
|
|
154
|
+
"icon",
|
|
155
|
+
"Icon file does not exist: " + icon_path,
|
|
156
|
+
"Update the icon path or add the missing icon file",
|
|
157
|
+
include_suggestions
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
checks_performed.append("project_name")
|
|
162
|
+
var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))
|
|
163
|
+
if project_name.is_empty():
|
|
164
|
+
warnings.append(
|
|
165
|
+
_finding(
|
|
166
|
+
"warning",
|
|
167
|
+
"project_name",
|
|
168
|
+
"No project name set",
|
|
169
|
+
"Set a project name in Project Settings > Application > Config > Name",
|
|
170
|
+
include_suggestions
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
checks_performed.append("scripts")
|
|
175
|
+
var script_files: Array[String] = _files.find_files_with_extensions("res://", ["gd"])
|
|
176
|
+
var scripts_checked: int = 0
|
|
177
|
+
var script_issues: Array[Dictionary] = []
|
|
178
|
+
|
|
179
|
+
# A hundred scripts is enough to say whether the project is tidy without a large one
|
|
180
|
+
# turning validation into a full read of its source tree.
|
|
181
|
+
for script_path: String in script_files.slice(0, 100):
|
|
182
|
+
scripts_checked += 1
|
|
183
|
+
var file: FileAccess = FileAccess.open(script_path, FileAccess.READ)
|
|
184
|
+
if file:
|
|
185
|
+
var content: String = file.get_as_text()
|
|
186
|
+
file.close()
|
|
187
|
+
|
|
188
|
+
if "# TODO" in content or "# FIXME" in content:
|
|
189
|
+
script_issues.append({"path": script_path, "issue": "Contains TODO/FIXME comments"})
|
|
190
|
+
if "pass # TODO" in content:
|
|
191
|
+
script_issues.append({"path": script_path, "issue": "Contains unimplemented functions"})
|
|
192
|
+
|
|
193
|
+
# Compiled by the server and handed in, as for the health check: a script the engine refuses
|
|
194
|
+
# stops the export's game at the first scene using it, which the comment check above never saw.
|
|
195
|
+
var compiled: Dictionary = params.get("compiled", {})
|
|
196
|
+
if compiled.has("checked"):
|
|
197
|
+
checks_performed.append("compiles")
|
|
198
|
+
var failures: Array = compiled.get("failures", [])
|
|
199
|
+
for failure: Dictionary in failures:
|
|
200
|
+
issues.append(
|
|
201
|
+
_finding(
|
|
202
|
+
"error",
|
|
203
|
+
"compiles",
|
|
204
|
+
GdhScriptCheck.failure_line(failure),
|
|
205
|
+
"script_diagnostics gives every error in it with its line",
|
|
206
|
+
include_suggestions
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
checks_performed.append("references")
|
|
211
|
+
for entry: Dictionary in GdhDependencies.new(_log).missing_references():
|
|
212
|
+
issues.append(
|
|
213
|
+
_finding(
|
|
214
|
+
"error",
|
|
215
|
+
"references",
|
|
216
|
+
GdhDependencies.missing_line(entry),
|
|
217
|
+
"Restore the file, or point the reference at the file that replaced it",
|
|
218
|
+
include_suggestions
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# A connection whose method or signal is not there loads without a word and fails only once the
|
|
223
|
+
# game runs, on every emission or not at all; the references check sees files, not members.
|
|
224
|
+
checks_performed.append("connections")
|
|
225
|
+
for entry: Dictionary in GdhSceneConnections.new(_log).broken():
|
|
226
|
+
warnings.append(
|
|
227
|
+
_finding(
|
|
228
|
+
"warning",
|
|
229
|
+
"connections",
|
|
230
|
+
GdhSceneConnections.broken_line(entry),
|
|
231
|
+
CONNECTION_FIX,
|
|
232
|
+
include_suggestions
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if script_issues.size() > 0:
|
|
237
|
+
var warning: Dictionary = _finding(
|
|
238
|
+
"warning",
|
|
239
|
+
"scripts",
|
|
240
|
+
str(script_issues.size()) + " script issues found",
|
|
241
|
+
"Review and resolve TODO/FIXME items before release",
|
|
242
|
+
include_suggestions
|
|
243
|
+
)
|
|
244
|
+
warning["details"] = script_issues.slice(0, 5)
|
|
245
|
+
warnings.append(warning)
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"valid": issues.is_empty(),
|
|
249
|
+
"issues": issues,
|
|
250
|
+
"warnings": warnings,
|
|
251
|
+
"checks_performed": checks_performed,
|
|
252
|
+
"scripts_checked": scripts_checked,
|
|
253
|
+
# Said beside the count, so a project of four hundred scripts is not read as checked whole.
|
|
254
|
+
"scripts_total": script_files.size(),
|
|
255
|
+
"issue_count": issues.size(),
|
|
256
|
+
"warning_count": warnings.size()
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
func _finding(
|
|
261
|
+
type: String, check: String, message: String, suggestion: String, include_suggestion: bool
|
|
262
|
+
) -> Dictionary:
|
|
263
|
+
var finding: Dictionary = {"type": type, "check": check, "message": message}
|
|
264
|
+
if include_suggestion:
|
|
265
|
+
finding["suggestion"] = suggestion
|
|
266
|
+
return finding
|
|
@@ -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 (
|