gdharness 1.3.0 → 1.3.2
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 +322 -96
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
- package/build/godot/addons/gdharness_editor/declared_values.gd +116 -0
- package/build/godot/addons/gdharness_editor/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +65 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +222 -59
- package/build/godot/addons/gdharness_editor/reading.gd +65 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +175 -74
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +88 -84
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +65 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +52 -25
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +25 -15
- package/build/godot/operations/declared_values.gd +116 -0
- package/build/godot/operations/dependencies.gd +141 -33
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +14 -14
- package/build/godot/operations/file_reads.gd +65 -25
- package/build/godot/operations/file_walk.gd +34 -0
- package/build/godot/operations/gdscript_analysis.gd +27 -22
- package/build/godot/operations/gdscript_authoring.gd +122 -17
- package/build/godot/operations/godot_operations.gd +66 -63
- package/build/godot/operations/import_pipeline.gd +59 -20
- package/build/godot/operations/input_actions.gd +90 -18
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +83 -20
- package/build/godot/operations/project_diagnostics.gd +36 -11
- package/build/godot/operations/property_values.gd +222 -59
- package/build/godot/operations/reading.gd +65 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +9 -6
- package/build/godot/operations/script_check.gd +23 -7
- package/build/godot/operations/serialisation.gd +49 -94
- package/build/godot/operations/value_shapes.gd +118 -0
- package/package.json +1 -1
|
@@ -3,11 +3,11 @@ extends Node
|
|
|
3
3
|
|
|
4
4
|
## Resource files, shaders, tilesets and themes, written through the open editor.
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
6
|
+
const GdhFileReads = preload("../file_reads.gd")
|
|
7
|
+
const GdhHeadlessCheck = preload("../headless_check.gd")
|
|
8
|
+
const GdhPropertyValues = preload("../property_values.gd")
|
|
9
|
+
const GdhRead = preload("../reading.gd")
|
|
10
|
+
const GdhSceneFile = preload("../scene_file.gd")
|
|
11
11
|
|
|
12
12
|
# The minimal shader of a type: its declaration and the one function the engine calls for it.
|
|
13
13
|
const SHADER_EMPTY: String = """shader_type %s;
|
|
@@ -23,7 +23,7 @@ const SHADER_ENTRY: Dictionary[String, String] = {
|
|
|
23
23
|
const CELLS_SHOWN: int = 1000
|
|
24
24
|
|
|
25
25
|
var _editor_plugin: EditorPlugin = null
|
|
26
|
-
var _properties:
|
|
26
|
+
var _properties: GdhPropertyValues = GdhPropertyValues.new()
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
@@ -48,7 +48,7 @@ func _written(path: String) -> void:
|
|
|
48
48
|
if ResourceLoader.has_cached(path):
|
|
49
49
|
var _replaced: Resource = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
|
|
50
50
|
if _editor_plugin:
|
|
51
|
-
|
|
51
|
+
GdhSceneFile.scan_later()
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
## Why a tool that creates [param path] must not write there, or "". Saving over a file that was
|
|
@@ -89,12 +89,15 @@ func _xy(value: Variant, label: String) -> Array:
|
|
|
89
89
|
x = pair[0]
|
|
90
90
|
y = pair[1]
|
|
91
91
|
for number: Variant in [x, y]:
|
|
92
|
-
if
|
|
92
|
+
if (
|
|
93
|
+
not (number is float or number is int)
|
|
94
|
+
or GdhRead.as_float(number) != floorf(GdhRead.as_float(number))
|
|
95
|
+
):
|
|
93
96
|
return [
|
|
94
97
|
false,
|
|
95
98
|
'%s must be two whole numbers, {"x": .., "y": ..}, not %s.' % [label, JSON.stringify(value)]
|
|
96
99
|
]
|
|
97
|
-
return [true, Vector2i(
|
|
100
|
+
return [true, Vector2i(GdhRead.as_int(x), GdhRead.as_int(y))]
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
## The theme on disk at this path, or null when there is not one.
|
|
@@ -119,12 +122,12 @@ func _load_theme(theme_path: String) -> Theme:
|
|
|
119
122
|
## Read back because a property can be set and still not be saved: one without storage, a
|
|
120
123
|
## script variable that is not exported. Both were answered as changed.
|
|
121
124
|
func _save_checked(resource: Resource, path: String, names: Array, previous: Variant) -> String:
|
|
122
|
-
var saving: Error =
|
|
125
|
+
var saving: Error = GdhSceneFile.save_keeping_uid(resource, path)
|
|
123
126
|
if saving != OK:
|
|
124
127
|
return "Failed to save %s: %s" % [path, error_string(saving)]
|
|
125
128
|
var unkept: String = _unkept_headless(resource, path, names, previous)
|
|
126
129
|
if not unkept.is_empty():
|
|
127
|
-
return "Nothing was written: " + unkept +
|
|
130
|
+
return "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(path, previous)
|
|
128
131
|
var loaded: Resource = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
129
132
|
var lost: String = ""
|
|
130
133
|
if loaded == null:
|
|
@@ -133,7 +136,7 @@ func _save_checked(resource: Resource, path: String, names: Array, previous: Var
|
|
|
133
136
|
for key: Variant in names:
|
|
134
137
|
var property: String = str(key)
|
|
135
138
|
var read: Variant = _properties.read_at(loaded, property)
|
|
136
|
-
if not
|
|
139
|
+
if not GdhPropertyValues.same(read, _properties.read_at(resource, property)):
|
|
137
140
|
lost = (
|
|
138
141
|
(
|
|
139
142
|
"%s.%s would load from the file as %s: it is not a property the file keeps, so "
|
|
@@ -145,19 +148,19 @@ func _save_checked(resource: Resource, path: String, names: Array, previous: Var
|
|
|
145
148
|
if lost.is_empty():
|
|
146
149
|
_written(path)
|
|
147
150
|
return ""
|
|
148
|
-
return lost +
|
|
151
|
+
return lost + GdhHeadlessCheck.put_back(path, previous)
|
|
149
152
|
|
|
150
153
|
|
|
151
154
|
## Outside the editor, puts back in the file just saved at [param path] what a script that is not a
|
|
152
155
|
## tool changed and the call did not ask for, [param names] being what it asked for, and answers ""
|
|
153
|
-
## or why that did not hold. See [
|
|
156
|
+
## or why that did not hold. See [GdhHeadlessCheck].
|
|
154
157
|
func _unkept_headless(resource: Resource, path: String, names: Array, previous: Variant) -> String:
|
|
155
158
|
if Engine.is_editor_hint():
|
|
156
159
|
return ""
|
|
157
160
|
var script: Script = resource.get_script()
|
|
158
161
|
var scripted: bool = script != null and not script.is_tool()
|
|
159
162
|
if not path.ends_with(".tres"):
|
|
160
|
-
if scripted or
|
|
163
|
+
if scripted or GdhHeadlessCheck.holds_scripted_embedded(resource, {}):
|
|
161
164
|
return (
|
|
162
165
|
(
|
|
163
166
|
(
|
|
@@ -166,7 +169,7 @@ func _unkept_headless(resource: Resource, path: String, names: Array, previous:
|
|
|
166
169
|
)
|
|
167
170
|
% path
|
|
168
171
|
)
|
|
169
|
-
+
|
|
172
|
+
+ GdhHeadlessCheck.REMEDY
|
|
170
173
|
)
|
|
171
174
|
return ""
|
|
172
175
|
var before: String = ""
|
|
@@ -179,15 +182,15 @@ func _unkept_headless(resource: Resource, path: String, names: Array, previous:
|
|
|
179
182
|
var property: String = str(key)
|
|
180
183
|
asked[property] = true
|
|
181
184
|
asked[property.get_slice(":", 0)] = true
|
|
182
|
-
reached.merge(
|
|
183
|
-
return
|
|
185
|
+
reached.merge(GdhHeadlessCheck.reached(resource, property))
|
|
186
|
+
return GdhHeadlessCheck.settle_saved(
|
|
184
187
|
path,
|
|
185
188
|
before,
|
|
186
|
-
|
|
189
|
+
GdhHeadlessCheck.stored(resource).values(),
|
|
187
190
|
resource if scripted else null,
|
|
188
191
|
asked,
|
|
189
192
|
reached,
|
|
190
|
-
func() -> Error: return
|
|
193
|
+
func() -> Error: return GdhSceneFile.save_keeping_uid(resource, path)
|
|
191
194
|
)
|
|
192
195
|
|
|
193
196
|
|
|
@@ -202,7 +205,7 @@ func create_resource(args: Dictionary) -> Dictionary:
|
|
|
202
205
|
return {"ok": false, "error": taken}
|
|
203
206
|
|
|
204
207
|
var script_path: String = str(args.get("script", ""))
|
|
205
|
-
var made: Dictionary =
|
|
208
|
+
var made: Dictionary = GdhSceneFile.made_as(
|
|
206
209
|
resource_type, "" if script_path == "" else _ensure_res_path(script_path), "Resource", "resourceType"
|
|
207
210
|
)
|
|
208
211
|
if made.has("error"):
|
|
@@ -229,7 +232,7 @@ func create_resource(args: Dictionary) -> Dictionary:
|
|
|
229
232
|
var script: Script = load(script_file) as Script
|
|
230
233
|
if script == null:
|
|
231
234
|
return {"ok": false, "error": "No script loads from " + script_file}
|
|
232
|
-
var uncompiled: String =
|
|
235
|
+
var uncompiled: String = GdhSceneFile.uncompiled(script, script_file)
|
|
233
236
|
if not uncompiled.is_empty():
|
|
234
237
|
return {"ok": false, "error": uncompiled}
|
|
235
238
|
var base: String = str(script.get_instance_base_type())
|
|
@@ -245,13 +248,15 @@ func create_resource(args: Dictionary) -> Dictionary:
|
|
|
245
248
|
if not refused.is_empty():
|
|
246
249
|
return {"ok": false, "error": refused}
|
|
247
250
|
|
|
248
|
-
var folder: String =
|
|
251
|
+
var folder: String = GdhSceneFile.make_folder_for(res_path)
|
|
249
252
|
if not folder.is_empty():
|
|
250
253
|
return {"ok": false, "error": folder}
|
|
254
|
+
var answer: Dictionary = {"ok": true, "resourcePath": res_path, "resourceType": asked_type}
|
|
255
|
+
_properties.note_write(answer)
|
|
251
256
|
var failed: String = _save_checked(resource, res_path, properties.keys(), null)
|
|
252
257
|
if not failed.is_empty():
|
|
253
258
|
return {"ok": false, "error": failed}
|
|
254
|
-
return
|
|
259
|
+
return answer
|
|
255
260
|
|
|
256
261
|
|
|
257
262
|
func modify_resource(args: Dictionary) -> Dictionary:
|
|
@@ -284,11 +289,13 @@ func modify_resource(args: Dictionary) -> Dictionary:
|
|
|
284
289
|
var refused: String = _properties.write_all(resource, properties)
|
|
285
290
|
if not refused.is_empty():
|
|
286
291
|
return {"ok": false, "error": refused}
|
|
292
|
+
var answer: Dictionary = {"ok": true, "resourcePath": res_path}
|
|
293
|
+
_properties.note_write(answer)
|
|
287
294
|
var previous: PackedByteArray = FileAccess.get_file_as_bytes(res_path)
|
|
288
295
|
var failed: String = _save_checked(resource, res_path, properties.keys(), previous)
|
|
289
296
|
if not failed.is_empty():
|
|
290
297
|
return {"ok": false, "error": failed}
|
|
291
|
-
return
|
|
298
|
+
return answer
|
|
292
299
|
|
|
293
300
|
|
|
294
301
|
## What a resource file holds, read as a headless engine reads it with no editor connected.
|
|
@@ -300,7 +307,7 @@ func read_resource(args: Dictionary) -> Dictionary:
|
|
|
300
307
|
var inner: String = str(args.get("subResource", ""))
|
|
301
308
|
if not inner.is_empty():
|
|
302
309
|
res_path += "::" + inner
|
|
303
|
-
return
|
|
310
|
+
return GdhFileReads.resource(res_path, GdhRead.as_bool(args.get("includeDefaults", false)))
|
|
304
311
|
|
|
305
312
|
|
|
306
313
|
func create_shader(args: Dictionary) -> Dictionary:
|
|
@@ -316,7 +323,7 @@ func create_shader(args: Dictionary) -> Dictionary:
|
|
|
316
323
|
if code == "":
|
|
317
324
|
code = SHADER_EMPTY % [shader_type, SHADER_ENTRY.get(shader_type, "fragment")]
|
|
318
325
|
|
|
319
|
-
var folder: String =
|
|
326
|
+
var folder: String = GdhSceneFile.make_folder_for(shader_path)
|
|
320
327
|
if not folder.is_empty():
|
|
321
328
|
return {"ok": false, "error": folder}
|
|
322
329
|
var file: FileAccess = FileAccess.open(shader_path, FileAccess.WRITE)
|
|
@@ -339,7 +346,19 @@ func create_tileset(args: Dictionary) -> Dictionary:
|
|
|
339
346
|
var tileset_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
|
|
340
347
|
if tileset_path == "res://":
|
|
341
348
|
return {"ok": false, "error": "resourcePath is required"}
|
|
342
|
-
|
|
349
|
+
# modify sets properties and builds no atlas, so the other path is the only way on offered.
|
|
350
|
+
var taken: String = (
|
|
351
|
+
""
|
|
352
|
+
if not FileAccess.file_exists(tileset_path)
|
|
353
|
+
else (
|
|
354
|
+
(
|
|
355
|
+
"%s already exists, and create_tileset builds a whole new TileSet, which would replace "
|
|
356
|
+
+ "everything in it, the tiles a scene paints with among them. Give resourcePath a path "
|
|
357
|
+
+ "nothing is at."
|
|
358
|
+
)
|
|
359
|
+
% tileset_path
|
|
360
|
+
)
|
|
361
|
+
)
|
|
343
362
|
if not taken.is_empty():
|
|
344
363
|
return {"ok": false, "error": taken}
|
|
345
364
|
|
|
@@ -367,14 +386,21 @@ func create_tileset(args: Dictionary) -> Dictionary:
|
|
|
367
386
|
"ok": false,
|
|
368
387
|
"error": "The TileSet did not take the atlas for %s" % atlas.texture.resource_path
|
|
369
388
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
389
|
+
var entry: Dictionary = {
|
|
390
|
+
"sourceId": added, "texture": atlas.texture.resource_path, "tiles": atlas.get_tiles_count()
|
|
391
|
+
}
|
|
392
|
+
var blank: Array = built[2]
|
|
393
|
+
if not blank.is_empty():
|
|
394
|
+
entry["leftBlank"] = blank
|
|
395
|
+
var spare: String = built[3]
|
|
396
|
+
if not spare.is_empty():
|
|
397
|
+
entry["notInAnyTile"] = spare
|
|
398
|
+
made.append(entry)
|
|
399
|
+
|
|
400
|
+
var folder: String = GdhSceneFile.make_folder_for(tileset_path)
|
|
375
401
|
if not folder.is_empty():
|
|
376
402
|
return {"ok": false, "error": folder}
|
|
377
|
-
var saving: Error =
|
|
403
|
+
var saving: Error = GdhSceneFile.save_keeping_uid(tileset, tileset_path)
|
|
378
404
|
if saving != OK:
|
|
379
405
|
return {"ok": false, "error": "Failed to save %s: %s" % [tileset_path, error_string(saving)]}
|
|
380
406
|
|
|
@@ -382,11 +408,14 @@ func create_tileset(args: Dictionary) -> Dictionary:
|
|
|
382
408
|
return {"ok": true, "tilesetPath": tileset_path, "sources": made}
|
|
383
409
|
|
|
384
410
|
|
|
385
|
-
## One atlas source from its description, with its tiles made, as [true, source
|
|
411
|
+
## One atlas source from its description, with its tiles made, as [true, source, the grid cells left
|
|
412
|
+
## without a tile for being wholly transparent, a sentence on the pixels no tile covers or ""] or
|
|
413
|
+
## [false, why].
|
|
386
414
|
##
|
|
387
415
|
## Tiles are made in every cell of the grid that has a pixel that is not transparent, which is
|
|
388
416
|
## what the editor offers to do when an atlas gets a texture. Without them the atlas held no tiles,
|
|
389
|
-
## and every cell painted from it drew nothing.
|
|
417
|
+
## and every cell painted from it drew nothing. The cells passed over and the strip a tile size that
|
|
418
|
+
## does not divide the picture leaves are said, since a count alone left a caller to work out which.
|
|
390
419
|
func _atlas(entry: Variant, index: int) -> Array:
|
|
391
420
|
if not entry is Dictionary:
|
|
392
421
|
return [false, "sources[%d] must be an object with texture and tileSize" % index]
|
|
@@ -448,6 +477,7 @@ func _atlas(entry: Variant, index: int) -> Array:
|
|
|
448
477
|
)
|
|
449
478
|
]
|
|
450
479
|
var image: Image = texture.get_image()
|
|
480
|
+
var blank: Array = []
|
|
451
481
|
for y: int in grid.y:
|
|
452
482
|
for x: int in grid.x:
|
|
453
483
|
var cell: Vector2i = Vector2i(x, y)
|
|
@@ -457,9 +487,33 @@ func _atlas(entry: Variant, index: int) -> Array:
|
|
|
457
487
|
atlas.texture_region_size
|
|
458
488
|
)
|
|
459
489
|
if image != null and _blank(image, region):
|
|
490
|
+
blank.append({"x": x, "y": y})
|
|
460
491
|
continue
|
|
461
492
|
atlas.create_tile(cell)
|
|
462
|
-
|
|
493
|
+
var covered: Vector2i = atlas.margins + grid * tile_size + (grid - Vector2i.ONE) * atlas.separation
|
|
494
|
+
var left: Vector2i = Vector2i(texture.get_size()) - covered
|
|
495
|
+
var sides: PackedStringArray = []
|
|
496
|
+
if left.x > 0:
|
|
497
|
+
var _right: bool = sides.append("%d px on the right" % left.x)
|
|
498
|
+
if left.y > 0:
|
|
499
|
+
var _bottom: bool = sides.append("%d px at the bottom" % left.y)
|
|
500
|
+
var spare: String = (
|
|
501
|
+
""
|
|
502
|
+
if sides.is_empty()
|
|
503
|
+
else (
|
|
504
|
+
"%s is %d by %d, so %s %s in no %d by %d tile."
|
|
505
|
+
% [
|
|
506
|
+
texture_path,
|
|
507
|
+
texture.get_width(),
|
|
508
|
+
texture.get_height(),
|
|
509
|
+
" and ".join(sides),
|
|
510
|
+
"is" if sides.size() == 1 else "are",
|
|
511
|
+
tile_size.x,
|
|
512
|
+
tile_size.y
|
|
513
|
+
]
|
|
514
|
+
)
|
|
515
|
+
)
|
|
516
|
+
return [true, atlas, blank, spare]
|
|
463
517
|
|
|
464
518
|
|
|
465
519
|
func _blank(image: Image, region: Rect2i) -> bool:
|
|
@@ -468,12 +522,15 @@ func _blank(image: Image, region: Rect2i) -> bool:
|
|
|
468
522
|
return image.get_region(region).is_invisible()
|
|
469
523
|
|
|
470
524
|
|
|
471
|
-
## The
|
|
472
|
-
|
|
473
|
-
|
|
525
|
+
## The sources a tile set holds, as "source 0", "sources 0, 2" or "no sources", for saying what a
|
|
526
|
+
## cell could have named instead.
|
|
527
|
+
func _sources_of(tile_set: TileSet) -> String:
|
|
528
|
+
var ids: PackedStringArray = []
|
|
474
529
|
for index: int in tile_set.get_source_count():
|
|
475
|
-
ids.append(tile_set.get_source_id(index))
|
|
476
|
-
|
|
530
|
+
var _listed: bool = ids.append(str(tile_set.get_source_id(index)))
|
|
531
|
+
if ids.is_empty():
|
|
532
|
+
return "no sources"
|
|
533
|
+
return ("source " if ids.size() == 1 else "sources ") + ", ".join(ids)
|
|
477
534
|
|
|
478
535
|
|
|
479
536
|
func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
@@ -491,12 +548,12 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
491
548
|
if placed.is_empty():
|
|
492
549
|
return {"ok": false, "error": "cells is empty, so there is nothing to place"}
|
|
493
550
|
|
|
494
|
-
var scene:
|
|
551
|
+
var scene: GdhSceneFile = GdhSceneFile.new()
|
|
495
552
|
var refused: Dictionary = scene.open(scene_path, true)
|
|
496
553
|
if not refused.is_empty():
|
|
497
554
|
return refused
|
|
498
555
|
|
|
499
|
-
var layer: int =
|
|
556
|
+
var layer: int = GdhRead.as_int(args.get("layer", 0))
|
|
500
557
|
var found: Array = _tile_node(scene, node_path, layer)
|
|
501
558
|
if found[0] == null:
|
|
502
559
|
return scene.refuse(str(found[1]))
|
|
@@ -510,11 +567,20 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
510
567
|
var tile_set: TileSet = layered.tile_set if layered != null else single.tile_set
|
|
511
568
|
|
|
512
569
|
scene.asked(node, ["layer_%d/tile_data" % layer if layered != null else "tile_map_data"])
|
|
513
|
-
|
|
570
|
+
# Every cell is checked before any is placed, and every one refused is named: stopping at the first
|
|
571
|
+
# left a caller fixing one cell a call.
|
|
572
|
+
var checked: Array = []
|
|
573
|
+
var refusals: PackedStringArray = []
|
|
514
574
|
for index: int in placed.size():
|
|
515
575
|
var cell: Array = _cell(placed[index], index, tile_set)
|
|
516
576
|
if not cell[0]:
|
|
517
|
-
|
|
577
|
+
var _named: bool = refusals.append(str(cell[1]))
|
|
578
|
+
checked.append(cell)
|
|
579
|
+
if not refusals.is_empty():
|
|
580
|
+
return scene.refuse("%s. Nothing was placed." % "; ".join(refusals))
|
|
581
|
+
var wanted: Array = []
|
|
582
|
+
var erased: int = 0
|
|
583
|
+
for cell: Array in checked:
|
|
518
584
|
var at: Vector2i = cell[1]
|
|
519
585
|
var source_id: int = cell[2]
|
|
520
586
|
var atlas_coords: Vector2i = cell[3]
|
|
@@ -524,6 +590,8 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
524
590
|
else:
|
|
525
591
|
single.set_cell(at, source_id, atlas_coords, alternative)
|
|
526
592
|
wanted.append([at, source_id, atlas_coords, alternative])
|
|
593
|
+
if source_id == -1:
|
|
594
|
+
erased += 1
|
|
527
595
|
|
|
528
596
|
var at_path: String = scene.path_of(node)
|
|
529
597
|
var written: Dictionary = scene.write(
|
|
@@ -539,12 +607,18 @@ func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
|
539
607
|
if not written.is_empty():
|
|
540
608
|
return written
|
|
541
609
|
|
|
542
|
-
return {
|
|
610
|
+
return {
|
|
611
|
+
"ok": true,
|
|
612
|
+
"layer": layer,
|
|
613
|
+
"placed": wanted.size() - erased,
|
|
614
|
+
"erased": erased,
|
|
615
|
+
"requested": placed.size()
|
|
616
|
+
}
|
|
543
617
|
|
|
544
618
|
|
|
545
619
|
## The node at [param node_path] cells are painted on, with a TileSet, as [node, why not].
|
|
546
620
|
## TileMapLayer is the node since 4.3, and TileMap the one it replaced, which keeps its layers.
|
|
547
|
-
func _tile_node(scene:
|
|
621
|
+
func _tile_node(scene: GdhSceneFile, node_path: String, layer: int) -> Array:
|
|
548
622
|
var node: Node = (
|
|
549
623
|
scene.root if node_path == "." or node_path == "" else scene.root.get_node_or_null(node_path)
|
|
550
624
|
)
|
|
@@ -568,40 +642,55 @@ func _tile_node(scene: SceneFile, node_path: String, layer: int) -> Array:
|
|
|
568
642
|
|
|
569
643
|
|
|
570
644
|
## One cell from its description, checked against [param tile_set], as
|
|
571
|
-
## [true, coords, source id, atlas coords, alternative] or [false, why].
|
|
645
|
+
## [true, coords, source id, atlas coords, alternative] or [false, why]. A source of -1 erases the
|
|
646
|
+
## cell, as set_cell takes it, and needs no atlasCoords.
|
|
572
647
|
func _cell(entry: Variant, index: int, tile_set: TileSet) -> Array:
|
|
573
648
|
if not entry is Dictionary:
|
|
574
649
|
return [false, "cells[%d] must be an object with coords, sourceId and atlasCoords" % index]
|
|
575
650
|
var cell: Dictionary = entry
|
|
576
|
-
for field: String in ["coords", "sourceId"
|
|
651
|
+
for field: String in ["coords", "sourceId"]:
|
|
577
652
|
if not cell.has(field):
|
|
578
653
|
return [false, "cells[%d] has no %s" % [index, field]]
|
|
579
654
|
var at: Array = _xy(cell["coords"], "cells[%d].coords" % index)
|
|
580
655
|
if not at[0]:
|
|
581
656
|
return at
|
|
657
|
+
var coords: Vector2i = at[1]
|
|
658
|
+
var named: String = "cells[%d] at (%d, %d)" % [index, coords.x, coords.y]
|
|
659
|
+
var source_id: int = GdhRead.as_int(cell["sourceId"], -2)
|
|
660
|
+
if source_id == -1:
|
|
661
|
+
return [true, coords, -1, Vector2i(-1, -1), -1]
|
|
662
|
+
if not cell.has("atlasCoords"):
|
|
663
|
+
return [
|
|
664
|
+
false, "%s has no atlasCoords, which every cell but an erased one (sourceId -1) needs" % named
|
|
665
|
+
]
|
|
582
666
|
var atlas_at: Array = _xy(cell["atlasCoords"], "cells[%d].atlasCoords" % index)
|
|
583
667
|
if not atlas_at[0]:
|
|
584
668
|
return atlas_at
|
|
585
|
-
var source_id: int = Read.as_int(cell["sourceId"], -1)
|
|
586
669
|
if not tile_set.has_source(source_id):
|
|
587
|
-
return [
|
|
670
|
+
return [
|
|
671
|
+
false,
|
|
672
|
+
(
|
|
673
|
+
"%s names source %d, and the TileSet has %s; sourceId -1 erases a cell"
|
|
674
|
+
% [named, source_id, _sources_of(tile_set)]
|
|
675
|
+
)
|
|
676
|
+
]
|
|
588
677
|
var source: TileSetSource = tile_set.get_source(source_id)
|
|
589
678
|
var atlas_coords: Vector2i = atlas_at[1]
|
|
590
|
-
var alternative: int =
|
|
679
|
+
var alternative: int = GdhRead.as_int(cell.get("alternativeTile", 0))
|
|
591
680
|
if not source.has_tile(atlas_coords):
|
|
592
681
|
return [
|
|
593
682
|
false,
|
|
594
|
-
(
|
|
595
|
-
"cells[%d]: source %d has no tile at %s; it has %s. Nothing was placed."
|
|
596
|
-
% [index, source_id, atlas_coords, _tiles_of(source)]
|
|
597
|
-
)
|
|
683
|
+
"%s: source %d has no tile at %s; it has %s" % [named, source_id, atlas_coords, _tiles_of(source)]
|
|
598
684
|
]
|
|
599
685
|
if not source.has_alternative_tile(atlas_coords, alternative):
|
|
600
686
|
return [
|
|
601
687
|
false,
|
|
602
|
-
|
|
688
|
+
(
|
|
689
|
+
"%s: the tile at %s in source %d has no alternative %d"
|
|
690
|
+
% [named, atlas_coords, source_id, alternative]
|
|
691
|
+
)
|
|
603
692
|
]
|
|
604
|
-
return [true,
|
|
693
|
+
return [true, coords, source_id, atlas_coords, alternative]
|
|
605
694
|
|
|
606
695
|
|
|
607
696
|
## The atlas coordinates [param source] has tiles at, the first twelve and how many more, or that it
|
|
@@ -691,12 +780,12 @@ func _cell_at(node: Node, layer: int, at: Vector2i) -> Array:
|
|
|
691
780
|
## Saves [param theme] over [param theme_path] with [param item] changed, answering "" or why not.
|
|
692
781
|
func _save_theme(theme: Theme, theme_path: String, item: String) -> String:
|
|
693
782
|
var previous: PackedByteArray = FileAccess.get_file_as_bytes(theme_path)
|
|
694
|
-
var saving: Error =
|
|
783
|
+
var saving: Error = GdhSceneFile.save_keeping_uid(theme, theme_path)
|
|
695
784
|
if saving != OK:
|
|
696
785
|
return "Failed to save %s: %s" % [theme_path, error_string(saving)]
|
|
697
786
|
var unkept: String = _unkept_headless(theme, theme_path, [item], previous)
|
|
698
787
|
if not unkept.is_empty():
|
|
699
|
-
return "Nothing was written: " + unkept +
|
|
788
|
+
return "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(theme_path, previous)
|
|
700
789
|
_written(theme_path)
|
|
701
790
|
return ""
|
|
702
791
|
|
|
@@ -715,13 +804,12 @@ func set_theme_color(args: Dictionary) -> Dictionary:
|
|
|
715
804
|
if theme == null:
|
|
716
805
|
return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
|
|
717
806
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
)
|
|
807
|
+
# Read as a property write reads a colour, html code included, so a value scene_node takes for a
|
|
808
|
+
# Button's modulate is taken for its theme colour too; "#ff8000" was refused here alone.
|
|
809
|
+
var parsed: Array = _properties.colour_from(args.get("color", {}), "color")
|
|
810
|
+
if not parsed[0]:
|
|
811
|
+
return {"ok": false, "error": parsed[1]}
|
|
812
|
+
var color: Color = parsed[1]
|
|
725
813
|
theme.set_color(color_name, control_type, color)
|
|
726
814
|
|
|
727
815
|
var unsaved: String = _save_theme(theme, theme_path, "%s/colors/%s" % [control_type, color_name])
|
|
@@ -760,18 +848,19 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
|
|
|
760
848
|
|
|
761
849
|
# Godot reads a size of 0 or less as unset and answers the theme's default, so -3 would be read
|
|
762
850
|
# back as 16.
|
|
763
|
-
var pixels: float =
|
|
764
|
-
if not is_finite(pixels) or pixels
|
|
765
|
-
|
|
851
|
+
var pixels: float = GdhRead.as_float(args.get("size"), 0.0)
|
|
852
|
+
if not is_finite(pixels) or pixels != floorf(pixels):
|
|
853
|
+
return {"ok": false, "error": "size is a whole number of pixels, and %s is not one." % str(pixels)}
|
|
854
|
+
if pixels < 1:
|
|
766
855
|
return {
|
|
767
856
|
"ok": false,
|
|
768
857
|
"error":
|
|
769
858
|
(
|
|
770
859
|
(
|
|
771
|
-
"size must be
|
|
772
|
-
|
|
860
|
+
"size must be 1 or more, not %d: the theme would hold it, and Godot reads a size of 0 or "
|
|
861
|
+
+ "less as unset and draws the default size instead."
|
|
773
862
|
)
|
|
774
|
-
|
|
863
|
+
% int(pixels)
|
|
775
864
|
)
|
|
776
865
|
}
|
|
777
866
|
theme.set_font_size(font_size_name, control_type, int(pixels))
|
|
@@ -812,4 +901,16 @@ func _noting_variation(answer: Dictionary) -> Dictionary:
|
|
|
812
901
|
)
|
|
813
902
|
% [type, type]
|
|
814
903
|
)
|
|
904
|
+
# A variation is any name, so a misspelt class is written all the same, and the classes nearest
|
|
905
|
+
# it are what tells a caller which one was meant.
|
|
906
|
+
var themed: PackedStringArray = []
|
|
907
|
+
for known: String in ClassDB.get_class_list():
|
|
908
|
+
if ClassDB.is_parent_class(known, "Control") or ClassDB.is_parent_class(known, "Window"):
|
|
909
|
+
var _added: bool = themed.append(known)
|
|
910
|
+
var close: PackedStringArray = GdhRead.nearest(type, themed)
|
|
911
|
+
if not close.is_empty():
|
|
912
|
+
answer["note"] += (
|
|
913
|
+
" If a class was meant, the nearest %s %s."
|
|
914
|
+
% ["is" if close.size() == 1 else "are", ", ".join(close)]
|
|
915
|
+
)
|
|
815
916
|
return answer
|
|
@@ -4,9 +4,9 @@ extends RefCounted
|
|
|
4
4
|
## The two ways a scene keeps its own values for nodes another scene makes: by inheriting that scene,
|
|
5
5
|
## or by instancing it with its children editable.
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
7
|
+
const GdhFileReads = preload("../file_reads.gd")
|
|
8
|
+
const GdhPaths = preload("scene_paths.gd")
|
|
9
|
+
const GdhSceneFile = preload("../scene_file.gd")
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
## Why a create at [param scene_path] is refused when a file is there already, which saving over
|
|
@@ -45,7 +45,7 @@ static func inherited(
|
|
|
45
45
|
script = load(script_path) as Script
|
|
46
46
|
if script == null:
|
|
47
47
|
return {"ok": false, "error": "No script loads from " + script_path}
|
|
48
|
-
var uncompiled: String =
|
|
48
|
+
var uncompiled: String = GdhSceneFile.uncompiled(script, script_path)
|
|
49
49
|
if not uncompiled.is_empty():
|
|
50
50
|
return {"ok": false, "error": uncompiled}
|
|
51
51
|
return inherit(scene_path, base_path, str(args.get("rootNodeName", "")), script)
|
|
@@ -59,12 +59,12 @@ static func inherited(
|
|
|
59
59
|
## the base: a headless engine making that instance runs every script in the base, and the pack would
|
|
60
60
|
## keep what they set as this scene's own values.
|
|
61
61
|
static func inherit(scene_path: String, base_path: String, root_name: String, script: Script) -> Dictionary:
|
|
62
|
-
var base: Dictionary =
|
|
62
|
+
var base: Dictionary = GdhFileReads.scene_node(base_path, ".")
|
|
63
63
|
if not base.get("ok", false):
|
|
64
64
|
return {"ok": false, "error": "inherits: " + str(base.get("error", "%s holds no nodes" % base_path))}
|
|
65
65
|
var root_type: String = str(base["type"])
|
|
66
66
|
var name: String = str(base["name"]) if root_name.is_empty() else root_name
|
|
67
|
-
var unfit: String =
|
|
67
|
+
var unfit: String = GdhSceneFile.unfit_name(name)
|
|
68
68
|
if not unfit.is_empty():
|
|
69
69
|
return {"ok": false, "error": unfit}
|
|
70
70
|
if script != null:
|
|
@@ -78,7 +78,7 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
78
78
|
% [script.resource_path, extended, root_type, base_path]
|
|
79
79
|
)
|
|
80
80
|
}
|
|
81
|
-
var folder: String =
|
|
81
|
+
var folder: String = GdhSceneFile.make_folder_for(scene_path)
|
|
82
82
|
if not folder.is_empty():
|
|
83
83
|
return {"ok": false, "error": folder}
|
|
84
84
|
|
|
@@ -87,14 +87,14 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
87
87
|
"",
|
|
88
88
|
(
|
|
89
89
|
'[ext_resource type="PackedScene"%s path="%s" id="1_base"]'
|
|
90
|
-
% [
|
|
90
|
+
% [GdhSceneFile.reference_uid(base_path), base_path]
|
|
91
91
|
),
|
|
92
92
|
]
|
|
93
93
|
if script != null:
|
|
94
94
|
var _resourced: bool = lines.append(
|
|
95
95
|
(
|
|
96
96
|
'[ext_resource type="Script"%s path="%s" id="2_script"]'
|
|
97
|
-
% [
|
|
97
|
+
% [GdhSceneFile.reference_uid(script.resource_path), script.resource_path]
|
|
98
98
|
)
|
|
99
99
|
)
|
|
100
100
|
lines.append_array(["", '[node name="%s" instance=ExtResource("1_base")]' % name])
|
|
@@ -117,7 +117,7 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
117
117
|
"error":
|
|
118
118
|
"%s did not load back as a scene inheriting %s, so it was removed." % [scene_path, base_path]
|
|
119
119
|
}
|
|
120
|
-
|
|
120
|
+
GdhSceneFile.refresh(scene_path)
|
|
121
121
|
return {
|
|
122
122
|
"ok": true,
|
|
123
123
|
"scenePath": scene_path,
|
|
@@ -130,8 +130,8 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
130
130
|
## Makes the children of [param node], an instance in [param scene], editable or not, answering why
|
|
131
131
|
## it cannot or "". With them editable the scene keeps its own values for them, as the editor's
|
|
132
132
|
## Editable Children does; made not editable again, the values it kept for them are dropped. The
|
|
133
|
-
## caller has had [method
|
|
134
|
-
static func set_editable(scene:
|
|
133
|
+
## caller has had [method GdhSceneFile.foreign] refuse a node inside an instance that is not editable.
|
|
134
|
+
static func set_editable(scene: GdhSceneFile, node: Node, editable: bool) -> String:
|
|
135
135
|
if node.owner == null or node.scene_file_path.is_empty():
|
|
136
136
|
return (
|
|
137
137
|
"editableChildren is a setting of an instance in this scene, and %s is not one."
|
|
@@ -152,7 +152,7 @@ static func set_editable(scene: SceneFile, node: Node, editable: bool) -> String
|
|
|
152
152
|
## for a node deleted, they are only named. Answers followed, the scenes rewritten, and
|
|
153
153
|
## notFollowed, each one left as it was with why, each only when it names any, with what
|
|
154
154
|
## [param carried] gives.
|
|
155
|
-
static func follow(changed: String, old_path: String, new_path: String, carried:
|
|
155
|
+
static func follow(changed: String, old_path: String, new_path: String, carried: GdhPaths) -> Dictionary:
|
|
156
156
|
var texts: Dictionary = {}
|
|
157
157
|
_read_scenes("res://", texts)
|
|
158
158
|
var kin: Array[String] = [changed]
|
|
@@ -176,14 +176,14 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
|
|
|
176
176
|
var moves: Dictionary = {}
|
|
177
177
|
if _inherits(scene_path, changed, texts):
|
|
178
178
|
moves[old_path] = new_path
|
|
179
|
-
var read: Dictionary =
|
|
179
|
+
var read: Dictionary = GdhFileReads.scene_tree(scene_path, -1, false)
|
|
180
180
|
if not read.get("ok", false):
|
|
181
181
|
not_followed.append({"scenePath": scene_path, "reason": str(read.get("error", ""))})
|
|
182
182
|
continue
|
|
183
183
|
var tree: Dictionary = read["tree"]
|
|
184
184
|
_instances_of(tree, changed, old_path, new_path, moves)
|
|
185
185
|
var text: String = texts[scene_path]
|
|
186
|
-
var unsaved: String =
|
|
186
|
+
var unsaved: String = GdhSceneFile.unsaved_in_editor(scene_path)
|
|
187
187
|
var rewritten: String = text
|
|
188
188
|
if not new_path.is_empty():
|
|
189
189
|
rewritten = _rewritten(text, moves, old_path.get_base_dir() != new_path.get_base_dir())
|
|
@@ -198,7 +198,7 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
|
|
|
198
198
|
)
|
|
199
199
|
continue
|
|
200
200
|
file.close()
|
|
201
|
-
|
|
201
|
+
GdhSceneFile.refresh(scene_path)
|
|
202
202
|
followed.append(scene_path)
|
|
203
203
|
if moves.is_empty() or not text.contains("NodePath("):
|
|
204
204
|
continue
|
|
@@ -335,7 +335,7 @@ static func _attribute_moved(
|
|
|
335
335
|
var found: RegExMatch = RegEx.create_from_string(' %s="([^"]*)"' % attribute).search(line)
|
|
336
336
|
if found == null:
|
|
337
337
|
return line
|
|
338
|
-
var moved: String =
|
|
338
|
+
var moved: String = GdhPaths.moved(found.get_string(1), moves)
|
|
339
339
|
if moved == found.get_string(1):
|
|
340
340
|
return line
|
|
341
341
|
var placed: String = _attribute_set(line, attribute, moved)
|