gdharness 1.3.8 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +4668 -3229
- package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
- package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +115 -18
- package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
- package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
- package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
- package/build/godot/addons/gdharness_editor/reading.gd +112 -6
- package/build/godot/addons/gdharness_editor/scene_file.gd +117 -174
- package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
- package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +19 -29
- package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
- package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +39 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +263 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -414
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +162 -483
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +8 -4
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +151 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +190 -38
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
- package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +112 -6
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +188 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +107 -104
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +320 -27
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +165 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +31 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +250 -10
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +19 -29
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
- package/build/godot/operations/animation_reads.gd +115 -4
- package/build/godot/operations/audio_buses.gd +93 -7
- package/build/godot/operations/classdb_queries.gd +119 -7
- package/build/godot/operations/declared_values.gd +272 -0
- package/build/godot/operations/dependencies.gd +154 -41
- package/build/godot/operations/editor_tools.gd +29 -13
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +115 -18
- package/build/godot/operations/gdscript_analysis.gd +14 -0
- package/build/godot/operations/gdscript_authoring.gd +12 -2
- package/build/godot/operations/godot_operations.gd +18 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +127 -51
- package/build/godot/operations/project_config.gd +113 -68
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +266 -0
- package/build/godot/operations/property_paths.gd +89 -16
- package/build/godot/operations/property_values.gd +180 -121
- package/build/godot/operations/reading.gd +112 -6
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +19 -29
- package/build/godot/operations/setting_reads.gd +121 -0
- package/build/godot/operations/value_shapes.gd +128 -1
- package/build/godot/tests/listed.gd +20 -13
- package/build/keeper.js +1402 -707
- package/package.json +1 -1
|
@@ -9,11 +9,9 @@ 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
|
-
## How far right of the rightmost state a new one goes in the editor's graph, wider than a state's
|
|
15
|
-
## box there.
|
|
16
|
-
const STATE_SPACING: float = 200.0
|
|
17
15
|
const LOOP_MODES: Dictionary = {
|
|
18
16
|
"none": Animation.LOOP_NONE, "linear": Animation.LOOP_LINEAR, "pingpong": Animation.LOOP_PINGPONG
|
|
19
17
|
}
|
|
@@ -102,7 +100,7 @@ func _finish(scene: GdhSceneFile, check: Callable, homes: Dictionary, answer: Di
|
|
|
102
100
|
return written
|
|
103
101
|
for file: Variant in homes:
|
|
104
102
|
var resource: Resource = homes[file]
|
|
105
|
-
var saving: Error =
|
|
103
|
+
var saving: Error = GdhSceneUids.save_keeping_uid(resource, str(file))
|
|
106
104
|
if saving != OK:
|
|
107
105
|
return {"ok": false, "error": "Failed to save %s: %s" % [file, error_string(saving)]}
|
|
108
106
|
if Engine.is_editor_hint():
|
|
@@ -171,22 +169,24 @@ func _homes(scene: GdhSceneFile, held: Array) -> Array:
|
|
|
171
169
|
return [true, homes]
|
|
172
170
|
|
|
173
171
|
|
|
174
|
-
## The library a new animation called [param animation_name] goes in, as [library, why not]:
|
|
175
|
-
## default one,
|
|
176
|
-
##
|
|
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.
|
|
177
176
|
func _library_for(player: AnimationPlayer, animation_name: String) -> Array:
|
|
178
177
|
var library: String = ""
|
|
179
178
|
if animation_name.contains("/"):
|
|
180
179
|
library = animation_name.get_slice("/", 0)
|
|
181
180
|
if player.has_animation_library(StringName(library)):
|
|
182
|
-
return [player.get_animation_library(StringName(library)), ""]
|
|
183
|
-
if not library.is_empty():
|
|
184
|
-
return [null, "%s has no animation library named %s." % [player.name, library]]
|
|
181
|
+
return [player.get_animation_library(StringName(library)), "", false]
|
|
185
182
|
var made: AnimationLibrary = AnimationLibrary.new()
|
|
186
|
-
var adding: Error = player.add_animation_library(
|
|
183
|
+
var adding: Error = player.add_animation_library(StringName(library), made)
|
|
187
184
|
if adding != OK:
|
|
188
|
-
|
|
189
|
-
|
|
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]
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
## The library [param animation_name] is in on [param player].
|
|
@@ -259,7 +259,9 @@ func read_animation_states(args: Dictionary) -> Dictionary:
|
|
|
259
259
|
if opened[1] == null:
|
|
260
260
|
return opened[2]
|
|
261
261
|
var anim_tree: AnimationTree = opened[1]
|
|
262
|
-
|
|
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))
|
|
263
265
|
|
|
264
266
|
|
|
265
267
|
# =============================================================================
|
|
@@ -275,6 +277,9 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
275
277
|
return {"ok": false, "error": "Missing scenePath"}
|
|
276
278
|
if animation_name.strip_edges().is_empty():
|
|
277
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}
|
|
278
283
|
var loop_modes: Dictionary = LOOP_MODES
|
|
279
284
|
if not loop_modes.has(loop_mode_name):
|
|
280
285
|
return {"ok": false, "error": "Unsupported loopMode: " + loop_mode_name}
|
|
@@ -302,6 +307,8 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
302
307
|
anim.loop_mode = GdhRead.as_int(loop_modes[loop_mode_name]) as Animation.LoopMode
|
|
303
308
|
anim.step = GdhRead.as_float(args.get("step", 0.1), 0.1)
|
|
304
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
|
|
305
312
|
var add_err: Error = anim_lib.add_animation(StringName(called), anim)
|
|
306
313
|
if add_err != OK:
|
|
307
314
|
return scene.refuse("Failed to add animation: " + error_string(add_err))
|
|
@@ -315,161 +322,30 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
315
322
|
var answer: Dictionary = {
|
|
316
323
|
"ok": true, "animationName": animation_name, "length": anim.length, "loopMode": loop_mode_name
|
|
317
324
|
}
|
|
325
|
+
if library[2]:
|
|
326
|
+
answer["libraryMade"] = animation_name.get_slice("/", 0) if animation_name.contains("/") else ""
|
|
318
327
|
var files: Dictionary = homes[1]
|
|
319
328
|
return _finish(scene, check, files, answer)
|
|
320
329
|
|
|
321
330
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
328
|
-
var animation_name: String = str(args.get("animationName", ""))
|
|
329
|
-
var given_track: Variant = args.get("track", {})
|
|
330
|
-
|
|
331
|
-
if scene_path.strip_edges() == "res://":
|
|
332
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
333
|
-
if animation_name.strip_edges().is_empty():
|
|
334
|
-
return {"ok": false, "error": "Missing animationName"}
|
|
335
|
-
var track: Dictionary = given_track if given_track is Dictionary else {}
|
|
336
|
-
if track.is_empty():
|
|
337
|
-
return {"ok": false, "error": "Missing track"}
|
|
338
|
-
var keyed: Array = _keyframes(track)
|
|
339
|
-
if not keyed[0]:
|
|
340
|
-
return {"ok": false, "error": keyed[1]}
|
|
341
|
-
|
|
342
|
-
# Asked of the player, which knows every library: looking only in the default one refused
|
|
343
|
-
# "moves/walk" as missing.
|
|
344
|
-
var opened: Array = _animation_at(scene_path, player_node_path, animation_name)
|
|
345
|
-
var scene: GdhSceneFile = opened[0]
|
|
346
|
-
if opened[1] == null:
|
|
347
|
-
return opened[2]
|
|
348
|
-
var player: AnimationPlayer = opened[1]
|
|
349
|
-
var anim: Animation = player.get_animation(StringName(animation_name))
|
|
350
|
-
var homes: Array = _homes(
|
|
351
|
-
scene, [[_library_of(player, animation_name), "animation library"], [anim, "animation"]]
|
|
352
|
-
)
|
|
353
|
-
if not homes[0]:
|
|
354
|
-
return scene.refuse(str(homes[1]))
|
|
355
|
-
|
|
356
|
-
var target_path: String = str(track.get("nodePath", ""))
|
|
357
|
-
if target_path.is_empty():
|
|
358
|
-
target_path = "."
|
|
359
|
-
var target: Node = _animated(player, target_path)
|
|
360
|
-
if target == null:
|
|
361
|
-
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 (
|
|
362
336
|
(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
+ "against."
|
|
366
|
-
)
|
|
367
|
-
% [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."
|
|
368
339
|
)
|
|
340
|
+
% animation_name
|
|
369
341
|
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
var keys: int = anim.track_get_key_count(track_idx)
|
|
378
|
-
var check: Callable = func(saved: Node) -> String:
|
|
379
|
-
var copy: AnimationPlayer = saved.get_node_or_null(at) as AnimationPlayer
|
|
380
|
-
if copy == null or not copy.has_animation(StringName(animation_name)):
|
|
381
|
-
return "%s would not be in the scene when it loads." % animation_name
|
|
382
|
-
var loaded: Animation = copy.get_animation(StringName(animation_name))
|
|
383
|
-
if loaded.get_track_count() <= track_idx or loaded.track_get_key_count(track_idx) != keys:
|
|
384
|
-
return "the track would not be in %s when the scene loads." % animation_name
|
|
385
|
-
return ""
|
|
386
|
-
var answer: Dictionary = {
|
|
387
|
-
"ok": true,
|
|
388
|
-
"trackType": str(track.get("type", "")),
|
|
389
|
-
"trackIndex": track_idx,
|
|
390
|
-
"keys": keys,
|
|
391
|
-
"keysAdded": keyframes.size(),
|
|
392
|
-
}
|
|
393
|
-
if built[3]:
|
|
394
|
-
answer["existingTrack"] = true
|
|
395
|
-
if built[2] != null:
|
|
396
|
-
answer["methodFound"] = built[2]
|
|
397
|
-
var past: String = _past_length(anim, animation_name)
|
|
398
|
-
if not past.is_empty():
|
|
399
|
-
answer["pastLength"] = past
|
|
400
|
-
var files: Dictionary = homes[1]
|
|
401
|
-
return _finish(scene, check, files, answer)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
## Adds [param track] to [param anim] with [param keyframes], as [index, why not, methodFound,
|
|
405
|
-
## existing]: methodFound is null for a property track, and existing is whether the keys went into
|
|
406
|
-
## a track the animation already had for that path.
|
|
407
|
-
##
|
|
408
|
-
## Into that track rather than a second one beside it, as the editor keys a property it already
|
|
409
|
-
## animates: two tracks for one path both play, the later overriding the earlier, and a key meant
|
|
410
|
-
## to extend the motion was split off where reading the animation showed two halves.
|
|
411
|
-
func _add_track(
|
|
412
|
-
anim: Animation, track: Dictionary, target: Node, target_path: String, keyframes: Array
|
|
413
|
-
) -> Array:
|
|
414
|
-
var track_type: String = str(track.get("type", ""))
|
|
415
|
-
var keys: Array = []
|
|
416
|
-
var method_found: Variant = null
|
|
417
|
-
var kind: Animation.TrackType = Animation.TYPE_VALUE
|
|
418
|
-
var path: NodePath = NodePath(target_path)
|
|
419
|
-
if track_type == "property":
|
|
420
|
-
var prop_name: String = str(track.get("property", ""))
|
|
421
|
-
if prop_name.is_empty():
|
|
422
|
-
return [-1, "track.property is required for property track", null, false]
|
|
423
|
-
var values: Array = _key_values(target, prop_name, keyframes)
|
|
424
|
-
if not values[0]:
|
|
425
|
-
return [-1, values[1], null, false]
|
|
426
|
-
keys = values[1]
|
|
427
|
-
path = NodePath(target_path + ":" + prop_name)
|
|
428
|
-
elif track_type == "method":
|
|
429
|
-
var method_name: String = str(track.get("method", ""))
|
|
430
|
-
if method_name.is_empty():
|
|
431
|
-
return [-1, "track.method is required for method track", null, false]
|
|
432
|
-
for index: int in keyframes.size():
|
|
433
|
-
var keyframe: Dictionary = keyframes[index]
|
|
434
|
-
var given: Variant = keyframe.get("args", [])
|
|
435
|
-
if not given is Array:
|
|
436
|
-
return [-1, "keyframes[%d].args must be a list." % index, null, false]
|
|
437
|
-
var arguments: Array = given
|
|
438
|
-
keys.append({"method": method_name, "args": _parse_method_args(arguments)})
|
|
439
|
-
method_found = target.has_method(method_name)
|
|
440
|
-
kind = Animation.TYPE_METHOD
|
|
441
|
-
else:
|
|
442
|
-
return [-1, "Unsupported track.type: " + track_type, null, false]
|
|
443
|
-
|
|
444
|
-
var track_idx: int = anim.find_track(path, kind)
|
|
445
|
-
var existing: bool = track_idx >= 0
|
|
446
|
-
if existing:
|
|
447
|
-
# Checked before any key goes in, since track_insert_key replaces a key at the same time
|
|
448
|
-
# without a word, and a refusal partway would leave the editor's scene half changed.
|
|
449
|
-
for index: int in keyframes.size():
|
|
450
|
-
var keyframe: Dictionary = keyframes[index]
|
|
451
|
-
var time: float = GdhRead.as_float(keyframe["time"])
|
|
452
|
-
if anim.track_find_key(track_idx, time, Animation.FIND_MODE_APPROX) >= 0:
|
|
453
|
-
return [
|
|
454
|
-
-1,
|
|
455
|
-
(
|
|
456
|
-
(
|
|
457
|
-
"Track %d already animates %s and has a key at %s s, which keyframes[%d] would "
|
|
458
|
-
+ "replace. Nothing was written."
|
|
459
|
-
)
|
|
460
|
-
% [track_idx, str(path), _seconds(time), index]
|
|
461
|
-
),
|
|
462
|
-
null,
|
|
463
|
-
false
|
|
464
|
-
]
|
|
465
|
-
else:
|
|
466
|
-
track_idx = anim.add_track(kind)
|
|
467
|
-
anim.track_set_path(track_idx, path)
|
|
468
|
-
for index: int in keyframes.size():
|
|
469
|
-
var keyframe: Dictionary = keyframes[index]
|
|
470
|
-
if anim.track_insert_key(track_idx, GdhRead.as_float(keyframe["time"]), keys[index]) < 0:
|
|
471
|
-
return [-1, "Keyframe %d could not be inserted." % index, null, false]
|
|
472
|
-
return [track_idx, "", method_found, existing]
|
|
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 ""
|
|
473
349
|
|
|
474
350
|
|
|
475
351
|
## The keys of [param anim], called [param animation_name], that lie past its end, where they never
|
|
@@ -484,6 +360,12 @@ static func _past_length(anim: Animation, animation_name: String) -> String:
|
|
|
484
360
|
var _added: bool = past.append(
|
|
485
361
|
"%s at %s s" % [str(anim.track_get_path(track)), _seconds(time)]
|
|
486
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:
|
|
487
369
|
if past.is_empty():
|
|
488
370
|
return ""
|
|
489
371
|
return (
|
|
@@ -509,57 +391,6 @@ static func _seconds(seconds: float) -> String:
|
|
|
509
391
|
return str(int(rounded)) if rounded == floorf(rounded) else str(rounded)
|
|
510
392
|
|
|
511
393
|
|
|
512
|
-
## The keyframes of [param track], as [true, list] or [false, why]. Each needs its own time: one
|
|
513
|
-
## that was not a dictionary was skipped, and one without a time went in at 0, where the next such
|
|
514
|
-
## key replaced it, and the answer said nothing of either.
|
|
515
|
-
func _keyframes(track: Dictionary) -> Array:
|
|
516
|
-
var given: Variant = track.get("keyframes", [])
|
|
517
|
-
if not given is Array:
|
|
518
|
-
return [false, "track.keyframes must be a list."]
|
|
519
|
-
var keyframes: Array = given
|
|
520
|
-
var times: Dictionary = {}
|
|
521
|
-
for index: int in keyframes.size():
|
|
522
|
-
if not keyframes[index] is Dictionary:
|
|
523
|
-
return [false, "keyframes[%d] is not an object with a time." % index]
|
|
524
|
-
var keyframe: Dictionary = keyframes[index]
|
|
525
|
-
var time: Variant = keyframe.get("time")
|
|
526
|
-
if not (time is float or time is int):
|
|
527
|
-
return [false, "keyframes[%d] has no time." % index]
|
|
528
|
-
var seconds: float = GdhRead.as_float(time)
|
|
529
|
-
if times.has(seconds):
|
|
530
|
-
return [
|
|
531
|
-
false, "keyframes[%d] and [%d] are both at %s seconds." % [times[seconds], index, seconds]
|
|
532
|
-
]
|
|
533
|
-
times[seconds] = index
|
|
534
|
-
return [true, keyframes]
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
## Each keyframe's value read as the property [param prop_name] on [param target] takes it, as
|
|
538
|
-
## [true, values] or [false, why]. Read without the property, a string "10" keyed on Label.text
|
|
539
|
-
## was stored as the number 10, and a Vector2 given as [1, 2] as a list.
|
|
540
|
-
func _key_values(target: Node, prop_name: String, keyframes: Array) -> Array:
|
|
541
|
-
var base: String = prop_name.get_slice(":", 0)
|
|
542
|
-
var declared: Dictionary = _properties.declaration(target, base)
|
|
543
|
-
if declared.is_empty():
|
|
544
|
-
return [false, GdhPropertyValues.no_property(target, base)]
|
|
545
|
-
var values: Array = []
|
|
546
|
-
for index: int in keyframes.size():
|
|
547
|
-
var keyframe: Dictionary = keyframes[index]
|
|
548
|
-
var raw: Variant = keyframe.get("value")
|
|
549
|
-
if base != prop_name:
|
|
550
|
-
# A subname such as position:x keys one component, which is a number.
|
|
551
|
-
var component: Variant = _properties.parse(raw, TYPE_FLOAT)
|
|
552
|
-
if not component is float:
|
|
553
|
-
return [false, "keyframes[%d].value keys %s, which is a number." % [index, prop_name]]
|
|
554
|
-
values.append(component)
|
|
555
|
-
continue
|
|
556
|
-
var value: Array = _properties.value_for(target, declared, raw)
|
|
557
|
-
if not value[0]:
|
|
558
|
-
return [false, "keyframes[%d]: %s" % [index, value[1]]]
|
|
559
|
-
values.append(value[1])
|
|
560
|
-
return [true, values]
|
|
561
|
-
|
|
562
|
-
|
|
563
394
|
# =============================================================================
|
|
564
395
|
# set_animation, rename_animation, remove_animation
|
|
565
396
|
# =============================================================================
|
|
@@ -702,7 +533,7 @@ func rename_animation(args: Dictionary) -> Dictionary:
|
|
|
702
533
|
if player.has_animation(StringName(new_full)):
|
|
703
534
|
return scene.refuse("%s already has an animation %s." % [player_node_path, new_full])
|
|
704
535
|
var library: AnimationLibrary = _library_of(player, animation_name)
|
|
705
|
-
var states: Array =
|
|
536
|
+
var states: Array = GdhAnimationReads.states_playing(scene.root, player, animation_name)
|
|
706
537
|
var held: Array = [[library, "animation library"]]
|
|
707
538
|
for state: Array in states:
|
|
708
539
|
held.append([state[0], "state playing %s" % animation_name])
|
|
@@ -716,6 +547,9 @@ func rename_animation(args: Dictionary) -> Dictionary:
|
|
|
716
547
|
library.rename_animation(StringName(animation_name.trim_prefix(prefix)), StringName(new_name))
|
|
717
548
|
if not player.has_animation(StringName(new_full)):
|
|
718
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
|
|
719
553
|
var answer: Dictionary = {"ok": true, "animationName": new_full, "was": animation_name}
|
|
720
554
|
if autoplayed:
|
|
721
555
|
answer["autoplay"] = new_full
|
|
@@ -767,7 +601,7 @@ func remove_animation(args: Dictionary) -> Dictionary:
|
|
|
767
601
|
player.autoplay = ""
|
|
768
602
|
answer["autoplayCleared"] = true
|
|
769
603
|
var orphaned: Array = []
|
|
770
|
-
for state: Array in
|
|
604
|
+
for state: Array in GdhAnimationReads.states_playing(scene.root, player, animation_name):
|
|
771
605
|
orphaned.append(state[1])
|
|
772
606
|
if not orphaned.is_empty():
|
|
773
607
|
answer["statesPlayingNothing"] = orphaned
|
|
@@ -780,202 +614,3 @@ func remove_animation(args: Dictionary) -> Dictionary:
|
|
|
780
614
|
return ""
|
|
781
615
|
var files: Dictionary = homes[1]
|
|
782
616
|
return _finish(scene, check, files, answer)
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
## The states of [param scene]'s AnimationTrees that play [param animation_name] from [param player],
|
|
786
|
-
## as [[node, {animTreePath, state}]], a state named by the path of the nodes leading to it.
|
|
787
|
-
func _states_playing(scene: GdhSceneFile, player: AnimationPlayer, animation_name: String) -> Array:
|
|
788
|
-
var found: Array = []
|
|
789
|
-
var trees: Array[Node] = scene.root.find_children("*", "AnimationTree", true, false)
|
|
790
|
-
if scene.root is AnimationTree:
|
|
791
|
-
trees.append(scene.root)
|
|
792
|
-
for node: Node in trees:
|
|
793
|
-
var tree: AnimationTree = node
|
|
794
|
-
if tree.tree_root == null or tree.get_node_or_null(tree.anim_player) != player:
|
|
795
|
-
continue
|
|
796
|
-
var tree_path: String = str(scene.root.get_path_to(tree))
|
|
797
|
-
_collect_playing(tree.tree_root, "", tree_path, animation_name, found)
|
|
798
|
-
return found
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
static func _collect_playing(
|
|
802
|
-
node: AnimationNode, at: String, tree_path: String, animation_name: String, found: Array
|
|
803
|
-
) -> void:
|
|
804
|
-
if node is AnimationNodeAnimation:
|
|
805
|
-
var plays: AnimationNodeAnimation = node
|
|
806
|
-
if plays.animation == StringName(animation_name):
|
|
807
|
-
found.append([plays, {"animTreePath": tree_path, "state": "." if at.is_empty() else at}])
|
|
808
|
-
return
|
|
809
|
-
var children: Dictionary = {}
|
|
810
|
-
if node is AnimationNodeStateMachine:
|
|
811
|
-
var machine: AnimationNodeStateMachine = node
|
|
812
|
-
for state: StringName in machine.get_node_list():
|
|
813
|
-
children[str(state)] = machine.get_node(state)
|
|
814
|
-
elif node is AnimationNodeBlendTree:
|
|
815
|
-
var blend: AnimationNodeBlendTree = node
|
|
816
|
-
for input: StringName in blend.get_node_list():
|
|
817
|
-
children[str(input)] = blend.get_node(input)
|
|
818
|
-
elif node is AnimationNodeBlendSpace1D:
|
|
819
|
-
var line: AnimationNodeBlendSpace1D = node
|
|
820
|
-
for point: int in line.get_blend_point_count():
|
|
821
|
-
children[str(point)] = line.get_blend_point_node(point)
|
|
822
|
-
elif node is AnimationNodeBlendSpace2D:
|
|
823
|
-
var plane: AnimationNodeBlendSpace2D = node
|
|
824
|
-
for point: int in plane.get_blend_point_count():
|
|
825
|
-
children[str(point)] = plane.get_blend_point_node(point)
|
|
826
|
-
for step: String in children:
|
|
827
|
-
var child: AnimationNode = children[step]
|
|
828
|
-
_collect_playing(child, step if at.is_empty() else at + "/" + step, tree_path, animation_name, found)
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
# =============================================================================
|
|
832
|
-
# add_animation_state
|
|
833
|
-
# =============================================================================
|
|
834
|
-
func add_animation_state(args: Dictionary) -> Dictionary:
|
|
835
|
-
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
836
|
-
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
837
|
-
var state_name: String = str(args.get("stateName", ""))
|
|
838
|
-
var animation_name: String = str(args.get("animationName", ""))
|
|
839
|
-
var state_machine_path: String = str(args.get("stateMachinePath", ""))
|
|
840
|
-
|
|
841
|
-
if scene_path.strip_edges() == "res://":
|
|
842
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
843
|
-
for required: Array in [
|
|
844
|
-
[anim_tree_path, "animTreePath"], [state_name, "stateName"], [animation_name, "animationName"]
|
|
845
|
-
]:
|
|
846
|
-
if str(required[0]).is_empty():
|
|
847
|
-
return {"ok": false, "error": "Missing " + str(required[1])}
|
|
848
|
-
|
|
849
|
-
var opened: Array = _open_at(scene_path, anim_tree_path, "AnimationTree")
|
|
850
|
-
var scene: GdhSceneFile = opened[0]
|
|
851
|
-
if opened[1] == null:
|
|
852
|
-
return opened[2]
|
|
853
|
-
var anim_tree: AnimationTree = opened[1]
|
|
854
|
-
var machine: Array = _machine(scene, anim_tree, state_machine_path)
|
|
855
|
-
if machine[0] == null:
|
|
856
|
-
return scene.refuse(str(machine[2]))
|
|
857
|
-
var sm: AnimationNodeStateMachine = machine[0]
|
|
858
|
-
# add_node fails with nothing but a line in the editor's log on a name in use, and Start and End
|
|
859
|
-
# are in use in every state machine.
|
|
860
|
-
if sm.has_node(StringName(state_name)):
|
|
861
|
-
return scene.refuse("The state machine already has a state named %s." % state_name)
|
|
862
|
-
var player: AnimationPlayer = anim_tree.get_node_or_null(anim_tree.anim_player) as AnimationPlayer
|
|
863
|
-
var found: bool = player != null and player.has_animation(StringName(animation_name))
|
|
864
|
-
|
|
865
|
-
var anim_node: AnimationNodeAnimation = AnimationNodeAnimation.new()
|
|
866
|
-
anim_node.animation = StringName(animation_name)
|
|
867
|
-
# Beside the rightmost state: add_node puts a state at the origin, where the editor's graph
|
|
868
|
-
# draws it over whatever is already there, Start included.
|
|
869
|
-
var rightmost: Vector2 = Vector2.ZERO
|
|
870
|
-
for state: StringName in sm.get_node_list():
|
|
871
|
-
var held: Vector2 = sm.get_node_position(state)
|
|
872
|
-
if held.x > rightmost.x:
|
|
873
|
-
rightmost = held
|
|
874
|
-
var position: Vector2 = rightmost + Vector2(STATE_SPACING, 0)
|
|
875
|
-
sm.add_node(StringName(state_name), anim_node, position)
|
|
876
|
-
if not sm.has_node(StringName(state_name)):
|
|
877
|
-
return scene.refuse("The state machine did not take the state %s." % state_name)
|
|
878
|
-
|
|
879
|
-
var answer: Dictionary = {
|
|
880
|
-
"ok": true,
|
|
881
|
-
"stateName": state_name,
|
|
882
|
-
"animationName": animation_name,
|
|
883
|
-
# False when the tree has no player to look the animation up in, or its player has none of
|
|
884
|
-
# that name.
|
|
885
|
-
"animationFound": found,
|
|
886
|
-
"position": GdhSerialisation.new().serialize_value(position),
|
|
887
|
-
}
|
|
888
|
-
# Written all the same, as the editor writes one: a state machine is often laid out before its
|
|
889
|
-
# animations are made, and refusing it stopped a tree being built in that order.
|
|
890
|
-
if player != null and not found:
|
|
891
|
-
answer["animationMissing"] = _missing_animation(scene, player, animation_name)
|
|
892
|
-
var files: Dictionary = machine[1]
|
|
893
|
-
return _finish(scene, Callable(), files, answer)
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
## Why the state playing [param animation_name] plays nothing yet. The player is named by its path
|
|
897
|
-
## from the scene root, as playerNodePath takes it; anim_player is relative to the tree ("../Anim"),
|
|
898
|
-
## which no argument takes.
|
|
899
|
-
static func _missing_animation(
|
|
900
|
-
scene: GdhSceneFile, player: AnimationPlayer, animation_name: String
|
|
901
|
-
) -> String:
|
|
902
|
-
var said: String = (
|
|
903
|
-
"The AnimationPlayer at %s has no animation %s yet, so the state plays nothing until it has one."
|
|
904
|
-
% [str(scene.root.get_path_to(player)), animation_name]
|
|
905
|
-
)
|
|
906
|
-
var close: PackedStringArray = GdhRead.nearest(animation_name, player.get_animation_list())
|
|
907
|
-
if close.is_empty():
|
|
908
|
-
return said
|
|
909
|
-
return (
|
|
910
|
-
said + " The nearest %s %s." % ["it has is" if close.size() == 1 else "it has are", ", ".join(close)]
|
|
911
|
-
)
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
# =============================================================================
|
|
915
|
-
# connect_animation_states
|
|
916
|
-
# =============================================================================
|
|
917
|
-
func connect_animation_states(args: Dictionary) -> Dictionary:
|
|
918
|
-
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
919
|
-
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
920
|
-
var from_state: String = str(args.get("fromState", ""))
|
|
921
|
-
var to_state: String = str(args.get("toState", ""))
|
|
922
|
-
var transition_type: String = str(args.get("transitionType", "immediate"))
|
|
923
|
-
var state_machine_path: String = str(args.get("stateMachinePath", ""))
|
|
924
|
-
var advance_condition: String = str(args.get("advanceCondition", ""))
|
|
925
|
-
|
|
926
|
-
if scene_path.strip_edges() == "res://":
|
|
927
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
928
|
-
if anim_tree_path.is_empty():
|
|
929
|
-
return {"ok": false, "error": "Missing animTreePath"}
|
|
930
|
-
if from_state.is_empty() or to_state.is_empty():
|
|
931
|
-
return {"ok": false, "error": "Missing fromState or toState"}
|
|
932
|
-
var modes: Dictionary = {
|
|
933
|
-
"sync": AnimationNodeStateMachineTransition.SWITCH_MODE_SYNC,
|
|
934
|
-
"at_end": AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END,
|
|
935
|
-
"immediate": AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE,
|
|
936
|
-
}
|
|
937
|
-
if not modes.has(transition_type):
|
|
938
|
-
return {"ok": false, "error": "Unsupported transitionType: " + transition_type}
|
|
939
|
-
var transition: AnimationNodeStateMachineTransition = AnimationNodeStateMachineTransition.new()
|
|
940
|
-
transition.switch_mode = (
|
|
941
|
-
GdhRead.as_int(modes[transition_type]) as AnimationNodeStateMachineTransition.SwitchMode
|
|
942
|
-
)
|
|
943
|
-
if not advance_condition.is_empty():
|
|
944
|
-
transition.advance_condition = StringName(advance_condition)
|
|
945
|
-
|
|
946
|
-
var opened: Array = _open_at(scene_path, anim_tree_path, "AnimationTree")
|
|
947
|
-
var scene: GdhSceneFile = opened[0]
|
|
948
|
-
if opened[1] == null:
|
|
949
|
-
return opened[2]
|
|
950
|
-
var anim_tree: AnimationTree = opened[1]
|
|
951
|
-
var machine: Array = _machine(scene, anim_tree, state_machine_path)
|
|
952
|
-
if machine[0] == null:
|
|
953
|
-
return scene.refuse(str(machine[2]))
|
|
954
|
-
var sm: AnimationNodeStateMachine = machine[0]
|
|
955
|
-
var problem: String = _transition_problem(sm, from_state, to_state)
|
|
956
|
-
if not problem.is_empty():
|
|
957
|
-
return scene.refuse(problem)
|
|
958
|
-
|
|
959
|
-
sm.add_transition(StringName(from_state), StringName(to_state), transition)
|
|
960
|
-
if not sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
961
|
-
return scene.refuse("The state machine did not take the transition.")
|
|
962
|
-
var files: Dictionary = machine[1]
|
|
963
|
-
return _finish(scene, Callable(), files, {"ok": true, "from": from_state, "to": to_state})
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
## Why [param sm] cannot take a transition from [param from_state] to [param to_state], or "".
|
|
967
|
-
## Each is a condition add_transition fails on with only a line in the editor's log, and the
|
|
968
|
-
## transition was answered as made.
|
|
969
|
-
func _transition_problem(sm: AnimationNodeStateMachine, from_state: String, to_state: String) -> String:
|
|
970
|
-
for state: String in [from_state, to_state]:
|
|
971
|
-
if not sm.has_node(StringName(state)):
|
|
972
|
-
return "The state machine has no state named %s." % state
|
|
973
|
-
if from_state == to_state:
|
|
974
|
-
return "A transition needs two different states."
|
|
975
|
-
if from_state == "End":
|
|
976
|
-
return "No transition can leave End."
|
|
977
|
-
if to_state == "Start":
|
|
978
|
-
return "No transition can arrive at Start."
|
|
979
|
-
if sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
980
|
-
return "There is already a transition from %s to %s." % [from_state, to_state]
|
|
981
|
-
return ""
|