gdharness 1.3.9 → 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 +4563 -3195
- 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 +102 -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 +51 -1
- package/build/godot/addons/gdharness_editor/scene_file.gd +77 -140
- 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 +11 -22
- 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 +28 -5
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +39 -20
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -362
- 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 +147 -492
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +79 -5
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +183 -37
- 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/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +51 -1
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +185 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +86 -79
- 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 +309 -22
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +163 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +5 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +146 -5
- 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 +11 -22
- 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 +22 -14
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +102 -18
- package/build/godot/operations/gdscript_authoring.gd +11 -1
- package/build/godot/operations/godot_operations.gd +16 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +108 -47
- package/build/godot/operations/project_config.gd +83 -69
- 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 +51 -1
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +11 -22
- 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 +1371 -706
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ const GdhHeadlessCheck = preload("../headless_check.gd")
|
|
|
9
9
|
const GdhPropertyValues = preload("../property_values.gd")
|
|
10
10
|
const GdhRead = preload("../reading.gd")
|
|
11
11
|
const GdhSceneFile = preload("../scene_file.gd")
|
|
12
|
+
const GdhSceneUids = preload("../scene_uids.gd")
|
|
12
13
|
const GdhSerialisation = preload("../serialisation.gd")
|
|
13
14
|
|
|
14
15
|
const LOOP_MODES: Dictionary = {
|
|
@@ -99,7 +100,7 @@ func _finish(scene: GdhSceneFile, check: Callable, homes: Dictionary, answer: Di
|
|
|
99
100
|
return written
|
|
100
101
|
for file: Variant in homes:
|
|
101
102
|
var resource: Resource = homes[file]
|
|
102
|
-
var saving: Error =
|
|
103
|
+
var saving: Error = GdhSceneUids.save_keeping_uid(resource, str(file))
|
|
103
104
|
if saving != OK:
|
|
104
105
|
return {"ok": false, "error": "Failed to save %s: %s" % [file, error_string(saving)]}
|
|
105
106
|
if Engine.is_editor_hint():
|
|
@@ -168,22 +169,24 @@ func _homes(scene: GdhSceneFile, held: Array) -> Array:
|
|
|
168
169
|
return [true, homes]
|
|
169
170
|
|
|
170
171
|
|
|
171
|
-
## The library a new animation called [param animation_name] goes in, as [library, why not]:
|
|
172
|
-
## default one,
|
|
173
|
-
##
|
|
172
|
+
## The library a new animation called [param animation_name] goes in, as [library, why not, made]:
|
|
173
|
+
## the default one, or the one named before a slash, as the player names them ("moves/walk"). One
|
|
174
|
+
## the player lacks is made, built into the scene, and made says so; no other op makes a library,
|
|
175
|
+
## so refusing one left "moves/walk" with no way to be written.
|
|
174
176
|
func _library_for(player: AnimationPlayer, animation_name: String) -> Array:
|
|
175
177
|
var library: String = ""
|
|
176
178
|
if animation_name.contains("/"):
|
|
177
179
|
library = animation_name.get_slice("/", 0)
|
|
178
180
|
if player.has_animation_library(StringName(library)):
|
|
179
|
-
return [player.get_animation_library(StringName(library)), ""]
|
|
180
|
-
if not library.is_empty():
|
|
181
|
-
return [null, "%s has no animation library named %s." % [player.name, library]]
|
|
181
|
+
return [player.get_animation_library(StringName(library)), "", false]
|
|
182
182
|
var made: AnimationLibrary = AnimationLibrary.new()
|
|
183
|
-
var adding: Error = player.add_animation_library(
|
|
183
|
+
var adding: Error = player.add_animation_library(StringName(library), made)
|
|
184
184
|
if adding != OK:
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
var called: String = (
|
|
186
|
+
"the default AnimationLibrary" if library.is_empty() else "the library " + library
|
|
187
|
+
)
|
|
188
|
+
return [null, "Failed to create %s: %s" % [called, error_string(adding)], false]
|
|
189
|
+
return [made, "", true]
|
|
187
190
|
|
|
188
191
|
|
|
189
192
|
## The library [param animation_name] is in on [param player].
|
|
@@ -256,7 +259,9 @@ func read_animation_states(args: Dictionary) -> Dictionary:
|
|
|
256
259
|
if opened[1] == null:
|
|
257
260
|
return opened[2]
|
|
258
261
|
var anim_tree: AnimationTree = opened[1]
|
|
259
|
-
|
|
262
|
+
var player: AnimationPlayer = anim_tree.get_node_or_null(anim_tree.anim_player) as AnimationPlayer
|
|
263
|
+
var player_path: String = "" if player == null else scene.path_of(player)
|
|
264
|
+
return _read_in(scene, GdhAnimationReads.states(anim_tree, state_machine_path, player, player_path))
|
|
260
265
|
|
|
261
266
|
|
|
262
267
|
# =============================================================================
|
|
@@ -272,6 +277,9 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
272
277
|
return {"ok": false, "error": "Missing scenePath"}
|
|
273
278
|
if animation_name.strip_edges().is_empty():
|
|
274
279
|
return {"ok": false, "error": "Missing animationName"}
|
|
280
|
+
var named: String = _name_refusal(animation_name)
|
|
281
|
+
if not named.is_empty():
|
|
282
|
+
return {"ok": false, "error": named}
|
|
275
283
|
var loop_modes: Dictionary = LOOP_MODES
|
|
276
284
|
if not loop_modes.has(loop_mode_name):
|
|
277
285
|
return {"ok": false, "error": "Unsupported loopMode: " + loop_mode_name}
|
|
@@ -299,6 +307,8 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
299
307
|
anim.loop_mode = GdhRead.as_int(loop_modes[loop_mode_name]) as Animation.LoopMode
|
|
300
308
|
anim.step = GdhRead.as_float(args.get("step", 0.1), 0.1)
|
|
301
309
|
var called: String = animation_name.get_slice("/", 1) if animation_name.contains("/") else animation_name
|
|
310
|
+
# As the editor names a new one: the file keeps it as resource_name, which the inspector shows.
|
|
311
|
+
anim.resource_name = called
|
|
302
312
|
var add_err: Error = anim_lib.add_animation(StringName(called), anim)
|
|
303
313
|
if add_err != OK:
|
|
304
314
|
return scene.refuse("Failed to add animation: " + error_string(add_err))
|
|
@@ -312,265 +322,30 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
312
322
|
var answer: Dictionary = {
|
|
313
323
|
"ok": true, "animationName": animation_name, "length": anim.length, "loopMode": loop_mode_name
|
|
314
324
|
}
|
|
325
|
+
if library[2]:
|
|
326
|
+
answer["libraryMade"] = animation_name.get_slice("/", 0) if animation_name.contains("/") else ""
|
|
315
327
|
var files: Dictionary = homes[1]
|
|
316
328
|
return _finish(scene, check, files, answer)
|
|
317
329
|
|
|
318
330
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
325
|
-
var animation_name: String = str(args.get("animationName", ""))
|
|
326
|
-
var given_track: Variant = args.get("track", {})
|
|
327
|
-
|
|
328
|
-
if scene_path.strip_edges() == "res://":
|
|
329
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
330
|
-
if animation_name.strip_edges().is_empty():
|
|
331
|
-
return {"ok": false, "error": "Missing animationName"}
|
|
332
|
-
var track: Dictionary = given_track if given_track is Dictionary else {}
|
|
333
|
-
if track.is_empty():
|
|
334
|
-
return {"ok": false, "error": "Missing track"}
|
|
335
|
-
# The schema leaves keyframes out of what track requires, since remove_track finds a track without
|
|
336
|
-
# them.
|
|
337
|
-
if not track.has("keyframes"):
|
|
338
|
-
return {"ok": false, "error": "Missing track.keyframes"}
|
|
339
|
-
var keyed: Array = _keyframes(track)
|
|
340
|
-
if not keyed[0]:
|
|
341
|
-
return {"ok": false, "error": keyed[1]}
|
|
342
|
-
|
|
343
|
-
# Asked of the player, which knows every library: looking only in the default one refused
|
|
344
|
-
# "moves/walk" as missing.
|
|
345
|
-
var opened: Array = _animation_at(scene_path, player_node_path, animation_name)
|
|
346
|
-
var scene: GdhSceneFile = opened[0]
|
|
347
|
-
if opened[1] == null:
|
|
348
|
-
return opened[2]
|
|
349
|
-
var player: AnimationPlayer = opened[1]
|
|
350
|
-
var anim: Animation = player.get_animation(StringName(animation_name))
|
|
351
|
-
var homes: Array = _homes(
|
|
352
|
-
scene, [[_library_of(player, animation_name), "animation library"], [anim, "animation"]]
|
|
353
|
-
)
|
|
354
|
-
if not homes[0]:
|
|
355
|
-
return scene.refuse(str(homes[1]))
|
|
356
|
-
|
|
357
|
-
var target_path: String = str(track.get("nodePath", ""))
|
|
358
|
-
if target_path.is_empty():
|
|
359
|
-
target_path = "."
|
|
360
|
-
var target: Node = _animated(player, target_path)
|
|
361
|
-
if target == null:
|
|
362
|
-
return scene.refuse(
|
|
331
|
+
## Why [param animation_name] cannot name an animation, or "". The player refuses : , and [ in an
|
|
332
|
+
## animation's or a library's name, and reads one slash as the end of the library's.
|
|
333
|
+
static func _name_refusal(animation_name: String) -> String:
|
|
334
|
+
if animation_name.count("/") > 1:
|
|
335
|
+
return (
|
|
363
336
|
(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
+ "against."
|
|
367
|
-
)
|
|
368
|
-
% [target_path, str(player.root_node)]
|
|
337
|
+
"%s names a library and an animation in it with one slash, as moves/walk; a library holds no "
|
|
338
|
+
+ "libraries."
|
|
369
339
|
)
|
|
340
|
+
% animation_name
|
|
370
341
|
)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
var keys: int = anim.track_get_key_count(track_idx)
|
|
379
|
-
var check: Callable = func(saved: Node) -> String:
|
|
380
|
-
var copy: AnimationPlayer = saved.get_node_or_null(at) as AnimationPlayer
|
|
381
|
-
if copy == null or not copy.has_animation(StringName(animation_name)):
|
|
382
|
-
return "%s would not be in the scene when it loads." % animation_name
|
|
383
|
-
var loaded: Animation = copy.get_animation(StringName(animation_name))
|
|
384
|
-
if loaded.get_track_count() <= track_idx or loaded.track_get_key_count(track_idx) != keys:
|
|
385
|
-
return "the track would not be in %s when the scene loads." % animation_name
|
|
386
|
-
return ""
|
|
387
|
-
var answer: Dictionary = {
|
|
388
|
-
"ok": true,
|
|
389
|
-
"trackType": str(track.get("type", "")),
|
|
390
|
-
"trackIndex": track_idx,
|
|
391
|
-
"keys": keys,
|
|
392
|
-
"keysAdded": keyframes.size(),
|
|
393
|
-
}
|
|
394
|
-
if built[3]:
|
|
395
|
-
answer["existingTrack"] = true
|
|
396
|
-
if built[2] != null:
|
|
397
|
-
answer["methodFound"] = built[2]
|
|
398
|
-
var past: String = _past_length(anim, animation_name)
|
|
399
|
-
if not past.is_empty():
|
|
400
|
-
answer["pastLength"] = past
|
|
401
|
-
var files: Dictionary = homes[1]
|
|
402
|
-
return _finish(scene, check, files, answer)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
## Adds [param track] to [param anim] with [param keyframes], as [index, why not, methodFound,
|
|
406
|
-
## existing]: methodFound is null for a property track, and existing is whether the keys went into
|
|
407
|
-
## a track the animation already had for that path.
|
|
408
|
-
##
|
|
409
|
-
## Into that track rather than a second one beside it, as the editor keys a property it already
|
|
410
|
-
## animates: two tracks for one path both play, the later overriding the earlier, and a key meant
|
|
411
|
-
## to extend the motion was split off where reading the animation showed two halves.
|
|
412
|
-
func _add_track(
|
|
413
|
-
anim: Animation, track: Dictionary, target: Node, target_path: String, keyframes: Array
|
|
414
|
-
) -> Array:
|
|
415
|
-
var track_type: String = str(track.get("type", ""))
|
|
416
|
-
var keys: Array = []
|
|
417
|
-
var method_found: Variant = null
|
|
418
|
-
var kind: Animation.TrackType = Animation.TYPE_VALUE
|
|
419
|
-
var path: NodePath = NodePath(target_path)
|
|
420
|
-
if track_type == "property":
|
|
421
|
-
var prop_name: String = str(track.get("property", ""))
|
|
422
|
-
if prop_name.is_empty():
|
|
423
|
-
return [-1, "track.property is required for property track", null, false]
|
|
424
|
-
var values: Array = _key_values(target, prop_name, keyframes)
|
|
425
|
-
if not values[0]:
|
|
426
|
-
return [-1, values[1], null, false]
|
|
427
|
-
keys = values[1]
|
|
428
|
-
path = NodePath(target_path + ":" + prop_name)
|
|
429
|
-
elif track_type == "method":
|
|
430
|
-
var method_name: String = str(track.get("method", ""))
|
|
431
|
-
if method_name.is_empty():
|
|
432
|
-
return [-1, "track.method is required for method track", null, false]
|
|
433
|
-
for index: int in keyframes.size():
|
|
434
|
-
var keyframe: Dictionary = keyframes[index]
|
|
435
|
-
var given: Variant = keyframe.get("args", [])
|
|
436
|
-
if not given is Array:
|
|
437
|
-
return [-1, "keyframes[%d].args must be a list." % index, null, false]
|
|
438
|
-
var arguments: Array = given
|
|
439
|
-
keys.append({"method": method_name, "args": _parse_method_args(arguments)})
|
|
440
|
-
method_found = target.has_method(method_name)
|
|
441
|
-
kind = Animation.TYPE_METHOD
|
|
442
|
-
else:
|
|
443
|
-
return [
|
|
444
|
-
-1,
|
|
445
|
-
(
|
|
446
|
-
"add_track makes property and method tracks, and track.type is %s; remove_track takes any."
|
|
447
|
-
% track_type
|
|
448
|
-
),
|
|
449
|
-
null,
|
|
450
|
-
false
|
|
451
|
-
]
|
|
452
|
-
|
|
453
|
-
var track_idx: int = anim.find_track(path, kind)
|
|
454
|
-
var existing: bool = track_idx >= 0
|
|
455
|
-
if existing:
|
|
456
|
-
# Checked before any key goes in, since track_insert_key replaces a key at the same time
|
|
457
|
-
# without a word, and a refusal partway would leave the editor's scene half changed.
|
|
458
|
-
for index: int in keyframes.size():
|
|
459
|
-
var keyframe: Dictionary = keyframes[index]
|
|
460
|
-
var time: float = GdhRead.as_float(keyframe["time"])
|
|
461
|
-
if anim.track_find_key(track_idx, time, Animation.FIND_MODE_APPROX) >= 0:
|
|
462
|
-
return [
|
|
463
|
-
-1,
|
|
464
|
-
(
|
|
465
|
-
(
|
|
466
|
-
"Track %d already animates %s and has a key at %s s, which keyframes[%d] would "
|
|
467
|
-
+ "replace. Nothing was written."
|
|
468
|
-
)
|
|
469
|
-
% [track_idx, str(path), _seconds(time), index]
|
|
470
|
-
),
|
|
471
|
-
null,
|
|
472
|
-
false
|
|
473
|
-
]
|
|
474
|
-
else:
|
|
475
|
-
track_idx = anim.add_track(kind)
|
|
476
|
-
anim.track_set_path(track_idx, path)
|
|
477
|
-
for index: int in keyframes.size():
|
|
478
|
-
var keyframe: Dictionary = keyframes[index]
|
|
479
|
-
if anim.track_insert_key(track_idx, GdhRead.as_float(keyframe["time"]), keys[index]) < 0:
|
|
480
|
-
return [-1, "Keyframe %d could not be inserted." % index, null, false]
|
|
481
|
-
return [track_idx, "", method_found, existing]
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
## Takes out the track [code]track[/code] names by its type, nodePath and property as read answers
|
|
485
|
-
## them, so a track read back can be passed as it stands; its keyframes are not looked at. Every
|
|
486
|
-
## track of that shape goes, since the editor can make two for one path and both play.
|
|
487
|
-
func remove_animation_track(args: Dictionary) -> Dictionary:
|
|
488
|
-
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
489
|
-
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
490
|
-
var animation_name: String = str(args.get("animationName", ""))
|
|
491
|
-
var given_track: Variant = args.get("track", {})
|
|
492
|
-
if scene_path.strip_edges() == "res://":
|
|
493
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
494
|
-
if animation_name.strip_edges().is_empty():
|
|
495
|
-
return {"ok": false, "error": "Missing animationName"}
|
|
496
|
-
var track: Dictionary = given_track if given_track is Dictionary else {}
|
|
497
|
-
if track.is_empty():
|
|
498
|
-
return {"ok": false, "error": "Missing track"}
|
|
499
|
-
var track_type: String = str(track.get("type", ""))
|
|
500
|
-
if not track_type in GdhAnimationReads.TRACK_TYPE_NAMES.values():
|
|
501
|
-
return {"ok": false, "error": "Unsupported track.type: " + track_type}
|
|
502
|
-
var node_path: String = str(track.get("nodePath", ""))
|
|
503
|
-
var property: String = str(track.get("property", ""))
|
|
504
|
-
var shape: Array = [track_type, node_path, property]
|
|
505
|
-
|
|
506
|
-
var found: Array = _animation_at(scene_path, player_node_path, animation_name)
|
|
507
|
-
if found[1] == null:
|
|
508
|
-
return found[2]
|
|
509
|
-
var scene: GdhSceneFile = found[0]
|
|
510
|
-
var player: AnimationPlayer = found[1]
|
|
511
|
-
var anim: Animation = player.get_animation(StringName(animation_name))
|
|
512
|
-
var homes: Array = _homes(
|
|
513
|
-
scene, [[_library_of(player, animation_name), "animation library"], [anim, "animation"]]
|
|
514
|
-
)
|
|
515
|
-
if not homes[0]:
|
|
516
|
-
return scene.refuse(str(homes[1]))
|
|
517
|
-
var matching: Array[int] = []
|
|
518
|
-
var held: PackedStringArray = []
|
|
519
|
-
for index: int in anim.get_track_count():
|
|
520
|
-
var of: Array = _track_shape(anim, index)
|
|
521
|
-
if of == shape:
|
|
522
|
-
matching.append(index)
|
|
523
|
-
var _listed: bool = held.append("%s (%s)" % [str(anim.track_get_path(index)), of[0]])
|
|
524
|
-
if matching.is_empty():
|
|
525
|
-
var said: String = (
|
|
526
|
-
"%s has no %s track on %s"
|
|
527
|
-
% [
|
|
528
|
-
animation_name,
|
|
529
|
-
track_type,
|
|
530
|
-
node_path if property.is_empty() else "%s:%s" % [node_path, property]
|
|
531
|
-
]
|
|
532
|
-
)
|
|
533
|
-
if held.is_empty():
|
|
534
|
-
return scene.refuse("%s; it has no tracks." % said)
|
|
535
|
-
return scene.refuse("%s; its tracks are %s." % [said, ", ".join(held)])
|
|
536
|
-
var keys: int = 0
|
|
537
|
-
matching.reverse()
|
|
538
|
-
for index: int in matching:
|
|
539
|
-
keys += anim.track_get_key_count(index)
|
|
540
|
-
anim.remove_track(index)
|
|
541
|
-
|
|
542
|
-
var at: String = scene.path_of(player)
|
|
543
|
-
var left: int = anim.get_track_count()
|
|
544
|
-
var check: Callable = func(saved: Node) -> String:
|
|
545
|
-
var copy: AnimationPlayer = saved.get_node_or_null(at) as AnimationPlayer
|
|
546
|
-
if copy == null or not copy.has_animation(StringName(animation_name)):
|
|
547
|
-
return "%s would not be in the scene when it loads." % animation_name
|
|
548
|
-
if copy.get_animation(StringName(animation_name)).get_track_count() != left:
|
|
549
|
-
return "the track would still be in %s when the scene loads." % animation_name
|
|
550
|
-
return ""
|
|
551
|
-
var removed: Dictionary = {"type": track_type, "nodePath": node_path}
|
|
552
|
-
if not property.is_empty():
|
|
553
|
-
removed["property"] = property
|
|
554
|
-
var answer: Dictionary = {
|
|
555
|
-
"ok": true,
|
|
556
|
-
"animationName": animation_name,
|
|
557
|
-
"removed": removed,
|
|
558
|
-
"tracksRemoved": matching.size(),
|
|
559
|
-
"keysRemoved": keys,
|
|
560
|
-
"tracks": left,
|
|
561
|
-
}
|
|
562
|
-
var files: Dictionary = homes[1]
|
|
563
|
-
return _finish(scene, check, files, answer)
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
## Track [param index] of [param anim] as [type, nodePath, property], named as read names them.
|
|
567
|
-
static func _track_shape(anim: Animation, index: int) -> Array:
|
|
568
|
-
var path: NodePath = anim.track_get_path(index)
|
|
569
|
-
return [
|
|
570
|
-
GdhAnimationReads.TRACK_TYPE_NAMES.get(anim.track_get_type(index), str(anim.track_get_type(index))),
|
|
571
|
-
str(path.get_concatenated_names()),
|
|
572
|
-
str(path.get_concatenated_subnames()),
|
|
573
|
-
]
|
|
342
|
+
for forbidden: String in [":", ",", "["]:
|
|
343
|
+
if animation_name.contains(forbidden):
|
|
344
|
+
return "An animation's name cannot hold : , or [, and %s does." % animation_name
|
|
345
|
+
for part: String in animation_name.split("/"):
|
|
346
|
+
if part.strip_edges().is_empty() and animation_name.contains("/"):
|
|
347
|
+
return "%s leaves the library's or the animation's name empty." % animation_name
|
|
348
|
+
return ""
|
|
574
349
|
|
|
575
350
|
|
|
576
351
|
## The keys of [param anim], called [param animation_name], that lie past its end, where they never
|
|
@@ -585,6 +360,12 @@ static func _past_length(anim: Animation, animation_name: String) -> String:
|
|
|
585
360
|
var _added: bool = past.append(
|
|
586
361
|
"%s at %s s" % [str(anim.track_get_path(track)), _seconds(time)]
|
|
587
362
|
)
|
|
363
|
+
return _past_sentence(anim, animation_name, past)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
## The pastLength sentence for [param past], keys of [param anim] each named by its track's path and
|
|
367
|
+
## time, or "" for none.
|
|
368
|
+
static func _past_sentence(anim: Animation, animation_name: String, past: PackedStringArray) -> String:
|
|
588
369
|
if past.is_empty():
|
|
589
370
|
return ""
|
|
590
371
|
return (
|
|
@@ -610,57 +391,6 @@ static func _seconds(seconds: float) -> String:
|
|
|
610
391
|
return str(int(rounded)) if rounded == floorf(rounded) else str(rounded)
|
|
611
392
|
|
|
612
393
|
|
|
613
|
-
## The keyframes of [param track], as [true, list] or [false, why]. Each needs its own time: one
|
|
614
|
-
## that was not a dictionary was skipped, and one without a time went in at 0, where the next such
|
|
615
|
-
## key replaced it, and the answer said nothing of either.
|
|
616
|
-
func _keyframes(track: Dictionary) -> Array:
|
|
617
|
-
var given: Variant = track.get("keyframes", [])
|
|
618
|
-
if not given is Array:
|
|
619
|
-
return [false, "track.keyframes must be a list."]
|
|
620
|
-
var keyframes: Array = given
|
|
621
|
-
var times: Dictionary = {}
|
|
622
|
-
for index: int in keyframes.size():
|
|
623
|
-
if not keyframes[index] is Dictionary:
|
|
624
|
-
return [false, "keyframes[%d] is not an object with a time." % index]
|
|
625
|
-
var keyframe: Dictionary = keyframes[index]
|
|
626
|
-
var time: Variant = keyframe.get("time")
|
|
627
|
-
if not (time is float or time is int):
|
|
628
|
-
return [false, "keyframes[%d] has no time." % index]
|
|
629
|
-
var seconds: float = GdhRead.as_float(time)
|
|
630
|
-
if times.has(seconds):
|
|
631
|
-
return [
|
|
632
|
-
false, "keyframes[%d] and [%d] are both at %s seconds." % [times[seconds], index, seconds]
|
|
633
|
-
]
|
|
634
|
-
times[seconds] = index
|
|
635
|
-
return [true, keyframes]
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
## Each keyframe's value read as the property [param prop_name] on [param target] takes it, as
|
|
639
|
-
## [true, values] or [false, why]. Read without the property, a string "10" keyed on Label.text
|
|
640
|
-
## was stored as the number 10, and a Vector2 given as [1, 2] as a list.
|
|
641
|
-
func _key_values(target: Node, prop_name: String, keyframes: Array) -> Array:
|
|
642
|
-
var base: String = prop_name.get_slice(":", 0)
|
|
643
|
-
var declared: Dictionary = _properties.declaration(target, base)
|
|
644
|
-
if declared.is_empty():
|
|
645
|
-
return [false, GdhPropertyValues.no_property(target, base)]
|
|
646
|
-
var values: Array = []
|
|
647
|
-
for index: int in keyframes.size():
|
|
648
|
-
var keyframe: Dictionary = keyframes[index]
|
|
649
|
-
var raw: Variant = keyframe.get("value")
|
|
650
|
-
if base != prop_name:
|
|
651
|
-
# A subname such as position:x keys one component, which is a number.
|
|
652
|
-
var component: Variant = _properties.parse(raw, TYPE_FLOAT)
|
|
653
|
-
if not component is float:
|
|
654
|
-
return [false, "keyframes[%d].value keys %s, which is a number." % [index, prop_name]]
|
|
655
|
-
values.append(component)
|
|
656
|
-
continue
|
|
657
|
-
var value: Array = _properties.value_for(target, declared, raw)
|
|
658
|
-
if not value[0]:
|
|
659
|
-
return [false, "keyframes[%d]: %s" % [index, value[1]]]
|
|
660
|
-
values.append(value[1])
|
|
661
|
-
return [true, values]
|
|
662
|
-
|
|
663
|
-
|
|
664
394
|
# =============================================================================
|
|
665
395
|
# set_animation, rename_animation, remove_animation
|
|
666
396
|
# =============================================================================
|
|
@@ -803,7 +533,7 @@ func rename_animation(args: Dictionary) -> Dictionary:
|
|
|
803
533
|
if player.has_animation(StringName(new_full)):
|
|
804
534
|
return scene.refuse("%s already has an animation %s." % [player_node_path, new_full])
|
|
805
535
|
var library: AnimationLibrary = _library_of(player, animation_name)
|
|
806
|
-
var states: Array =
|
|
536
|
+
var states: Array = GdhAnimationReads.states_playing(scene.root, player, animation_name)
|
|
807
537
|
var held: Array = [[library, "animation library"]]
|
|
808
538
|
for state: Array in states:
|
|
809
539
|
held.append([state[0], "state playing %s" % animation_name])
|
|
@@ -817,6 +547,9 @@ func rename_animation(args: Dictionary) -> Dictionary:
|
|
|
817
547
|
library.rename_animation(StringName(animation_name.trim_prefix(prefix)), StringName(new_name))
|
|
818
548
|
if not player.has_animation(StringName(new_full)):
|
|
819
549
|
return scene.refuse("The library did not take the name %s." % new_full)
|
|
550
|
+
# As the editor's rename sets it: the library's key alone left the file's resource_name, which
|
|
551
|
+
# the inspector shows, on the old name.
|
|
552
|
+
player.get_animation(StringName(new_full)).resource_name = new_name
|
|
820
553
|
var answer: Dictionary = {"ok": true, "animationName": new_full, "was": animation_name}
|
|
821
554
|
if autoplayed:
|
|
822
555
|
answer["autoplay"] = new_full
|
|
@@ -868,7 +601,7 @@ func remove_animation(args: Dictionary) -> Dictionary:
|
|
|
868
601
|
player.autoplay = ""
|
|
869
602
|
answer["autoplayCleared"] = true
|
|
870
603
|
var orphaned: Array = []
|
|
871
|
-
for state: Array in
|
|
604
|
+
for state: Array in GdhAnimationReads.states_playing(scene.root, player, animation_name):
|
|
872
605
|
orphaned.append(state[1])
|
|
873
606
|
if not orphaned.is_empty():
|
|
874
607
|
answer["statesPlayingNothing"] = orphaned
|
|
@@ -881,49 +614,3 @@ func remove_animation(args: Dictionary) -> Dictionary:
|
|
|
881
614
|
return ""
|
|
882
615
|
var files: Dictionary = homes[1]
|
|
883
616
|
return _finish(scene, check, files, answer)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
## The states of [param scene]'s AnimationTrees that play [param animation_name] from [param player],
|
|
887
|
-
## as [[node, {animTreePath, state}]], a state named by the path of the nodes leading to it.
|
|
888
|
-
func _states_playing(scene: GdhSceneFile, player: AnimationPlayer, animation_name: String) -> Array:
|
|
889
|
-
var found: Array = []
|
|
890
|
-
var trees: Array[Node] = scene.root.find_children("*", "AnimationTree", true, false)
|
|
891
|
-
if scene.root is AnimationTree:
|
|
892
|
-
trees.append(scene.root)
|
|
893
|
-
for node: Node in trees:
|
|
894
|
-
var tree: AnimationTree = node
|
|
895
|
-
if tree.tree_root == null or tree.get_node_or_null(tree.anim_player) != player:
|
|
896
|
-
continue
|
|
897
|
-
var tree_path: String = str(scene.root.get_path_to(tree))
|
|
898
|
-
_collect_playing(tree.tree_root, "", tree_path, animation_name, found)
|
|
899
|
-
return found
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
static func _collect_playing(
|
|
903
|
-
node: AnimationNode, at: String, tree_path: String, animation_name: String, found: Array
|
|
904
|
-
) -> void:
|
|
905
|
-
if node is AnimationNodeAnimation:
|
|
906
|
-
var plays: AnimationNodeAnimation = node
|
|
907
|
-
if plays.animation == StringName(animation_name):
|
|
908
|
-
found.append([plays, {"animTreePath": tree_path, "state": "." if at.is_empty() else at}])
|
|
909
|
-
return
|
|
910
|
-
var children: Dictionary = {}
|
|
911
|
-
if node is AnimationNodeStateMachine:
|
|
912
|
-
var machine: AnimationNodeStateMachine = node
|
|
913
|
-
for state: StringName in machine.get_node_list():
|
|
914
|
-
children[str(state)] = machine.get_node(state)
|
|
915
|
-
elif node is AnimationNodeBlendTree:
|
|
916
|
-
var blend: AnimationNodeBlendTree = node
|
|
917
|
-
for input: StringName in blend.get_node_list():
|
|
918
|
-
children[str(input)] = blend.get_node(input)
|
|
919
|
-
elif node is AnimationNodeBlendSpace1D:
|
|
920
|
-
var line: AnimationNodeBlendSpace1D = node
|
|
921
|
-
for point: int in line.get_blend_point_count():
|
|
922
|
-
children[str(point)] = line.get_blend_point_node(point)
|
|
923
|
-
elif node is AnimationNodeBlendSpace2D:
|
|
924
|
-
var plane: AnimationNodeBlendSpace2D = node
|
|
925
|
-
for point: int in plane.get_blend_point_count():
|
|
926
|
-
children[str(point)] = plane.get_blend_point_node(point)
|
|
927
|
-
for step: String in children:
|
|
928
|
-
var child: AnimationNode = children[step]
|
|
929
|
-
_collect_playing(child, step if at.is_empty() else at + "/" + step, tree_path, animation_name, found)
|