gdharness 1.3.9 → 1.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/build/cli.js +4740 -3212
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +102 -18
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
  6. package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
  7. package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
  8. package/build/godot/addons/gdharness_editor/reading.gd +51 -1
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +77 -140
  10. package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
  11. package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +11 -22
  13. package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
  14. package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
  15. package/build/godot/addons/gdharness_editor/tool_executor.gd +28 -5
  16. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +39 -20
  17. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -362
  18. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
  19. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
  20. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
  21. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
  22. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
  23. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +147 -492
  24. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
  25. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -3
  26. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
  27. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +79 -5
  28. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
  29. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +183 -37
  30. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
  31. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
  32. package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
  33. package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
  34. package/build/godot/addons/gdharness_runtime/reading.gd +51 -1
  35. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
  36. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +185 -30
  37. package/build/godot/addons/gdharness_runtime/runtime_input.gd +86 -79
  38. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
  39. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
  40. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +309 -22
  41. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +163 -20
  42. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
  43. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +5 -2
  44. package/build/godot/addons/gdharness_runtime/runtime_values.gd +146 -5
  45. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
  46. package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
  47. package/build/godot/addons/gdharness_runtime/serialisation.gd +11 -22
  48. package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
  49. package/build/godot/operations/animation_reads.gd +115 -4
  50. package/build/godot/operations/audio_buses.gd +93 -7
  51. package/build/godot/operations/classdb_queries.gd +119 -7
  52. package/build/godot/operations/declared_values.gd +272 -0
  53. package/build/godot/operations/dependencies.gd +154 -41
  54. package/build/godot/operations/editor_tools.gd +22 -14
  55. package/build/godot/operations/export_presets.gd +602 -4
  56. package/build/godot/operations/file_reads.gd +102 -18
  57. package/build/godot/operations/gdscript_authoring.gd +11 -1
  58. package/build/godot/operations/godot_operations.gd +16 -2
  59. package/build/godot/operations/import_pipeline.gd +20 -221
  60. package/build/godot/operations/input_actions.gd +108 -47
  61. package/build/godot/operations/project_config.gd +83 -69
  62. package/build/godot/operations/project_diagnostics.gd +4 -0
  63. package/build/godot/operations/project_validation.gd +266 -0
  64. package/build/godot/operations/property_paths.gd +89 -16
  65. package/build/godot/operations/property_values.gd +180 -121
  66. package/build/godot/operations/reading.gd +51 -1
  67. package/build/godot/operations/scene_connections.gd +222 -0
  68. package/build/godot/operations/serialisation.gd +11 -22
  69. package/build/godot/operations/setting_reads.gd +121 -0
  70. package/build/godot/operations/value_shapes.gd +128 -1
  71. package/build/godot/tests/listed.gd +73 -13
  72. package/build/keeper.js +1371 -706
  73. 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 = GdhSceneFile.save_keeping_uid(resource, str(file))
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]: the
172
- ## default one, made when the player has none, or the one named before a slash, as the player
173
- ## names them ("moves/walk").
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("", made)
183
+ var adding: Error = player.add_animation_library(StringName(library), made)
184
184
  if adding != OK:
185
- return [null, "Failed to create the default AnimationLibrary: " + error_string(adding)]
186
- return [made, ""]
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
- return _read_in(scene, GdhAnimationReads.states(anim_tree, state_machine_path))
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
- # add_animation_track
321
- # =============================================================================
322
- func add_animation_track(args: Dictionary) -> Dictionary:
323
- var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
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
- "No node at %s from the player's root node, %s, which is what the track path is read "
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
- var keyframes: Array = keyed[1]
372
- var built: Array = _add_track(anim, track, target, target_path, keyframes)
373
- if not str(built[1]).is_empty():
374
- return scene.refuse(str(built[1]))
375
- var track_idx: int = built[0]
376
-
377
- var at: String = scene.path_of(player)
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 = _states_playing(scene, player, animation_name)
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 _states_playing(scene, player, animation_name):
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)