gdharness 1.3.59 → 1.3.61
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/README.md +20 -20
- package/build/cli.js +2264 -1706
- package/build/godot/addons/auto_reload/auto_reload.gd +3 -3
- package/build/godot/addons/gdharness_editor/bridge_client.gd +4 -4
- package/build/godot/addons/gdharness_editor/declared_values.gd +13 -13
- package/build/godot/addons/gdharness_editor/file_reads.gd +6 -6
- package/build/godot/addons/gdharness_editor/headless_check.gd +9 -6
- package/build/godot/addons/gdharness_editor/property_paths.gd +3 -3
- package/build/godot/addons/gdharness_editor/property_values.gd +18 -11
- package/build/godot/addons/gdharness_editor/reading.gd +22 -18
- package/build/godot/addons/gdharness_editor/scene_file.gd +5 -5
- package/build/godot/addons/gdharness_editor/serialisation.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +5 -5
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +6 -6
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +9 -9
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +5 -5
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +5 -5
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +1 -1
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +5 -5
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +10 -10
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +1 -1
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +5 -5
- package/build/godot/addons/gdharness_runtime/declared_values.gd +13 -13
- package/build/godot/addons/gdharness_runtime/reading.gd +22 -18
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +89 -18
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +5 -4
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +3 -4
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +1 -1
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +5 -5
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +13 -13
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +5 -5
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +39 -39
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +5 -5
- package/build/godot/addons/gdharness_runtime/serialisation.gd +4 -4
- package/build/godot/addons/gdharness_tests/bench.gd +1 -1
- package/build/godot/addons/gdharness_tests/share.gd +6 -6
- package/build/godot/addons/gdharness_tests/suites.gd +1 -1
- package/build/godot/operations/audio_buses.gd +4 -4
- package/build/godot/operations/class_cache.gd +3 -3
- package/build/godot/operations/classdb_queries.gd +1 -1
- package/build/godot/operations/declared_values.gd +13 -13
- package/build/godot/operations/dependencies.gd +6 -6
- package/build/godot/operations/export_presets.gd +7 -7
- package/build/godot/operations/file_reads.gd +6 -6
- package/build/godot/operations/gdscript_analysis.gd +6 -6
- package/build/godot/operations/gdscript_authoring.gd +11 -11
- package/build/godot/operations/godot_operations.gd +1 -1
- package/build/godot/operations/import_pipeline.gd +12 -12
- package/build/godot/operations/input_actions.gd +3 -3
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +2 -2
- package/build/godot/operations/project_config.gd +11 -11
- package/build/godot/operations/project_validation.gd +1 -1
- package/build/godot/operations/property_paths.gd +3 -3
- package/build/godot/operations/property_values.gd +18 -11
- package/build/godot/operations/reading.gd +22 -18
- package/build/godot/operations/resource_files.gd +1 -1
- package/build/godot/operations/script_check.gd +2 -2
- package/build/godot/operations/serialisation.gd +4 -4
- package/build/godot/tests/listed.gd +28 -10
- package/build/keeper.js +511 -161
- package/package.json +1 -1
|
@@ -3,12 +3,12 @@ extends EditorPlugin
|
|
|
3
3
|
## Reloads the open scene and the scripts it carries when they change on disk, without the
|
|
4
4
|
## editor's confirmation popup. Every edit made through gdharness lands on disk, and an editor
|
|
5
5
|
## that keeps showing the old file is how a change reads as one that never happened. The
|
|
6
|
-
## files are polled once a second
|
|
6
|
+
## files are polled once a second. The editor's own watcher only fires on window focus.
|
|
7
7
|
|
|
8
8
|
const CHECK_INTERVAL_SECONDS: float = 1.0
|
|
9
9
|
## How long after a file's modification time its contents are compared as well. The time is in
|
|
10
10
|
## whole seconds, so a second write inside the second of the first leaves it as it was and changes
|
|
11
|
-
## only the contents
|
|
11
|
+
## only the contents. Past that second any write moves the time.
|
|
12
12
|
const SAME_SECOND_SECONDS: int = 5
|
|
13
13
|
|
|
14
14
|
var _timer: Timer
|
|
@@ -130,7 +130,7 @@ func _reload_scene(path: String) -> void:
|
|
|
130
130
|
(
|
|
131
131
|
(
|
|
132
132
|
"gdharness: %s changed on disk while the editor holds unsaved changes to it, "
|
|
133
|
-
+ "so it was not reloaded
|
|
133
|
+
+ "so it was not reloaded. Saving it writes the editor's copy over the file"
|
|
134
134
|
)
|
|
135
135
|
% path
|
|
136
136
|
)
|
|
@@ -17,7 +17,7 @@ const DIGEST_MARKER: String = "res://addons/gdharness_editor/.gdharness-digest"
|
|
|
17
17
|
## step with `announcementPath` in src/bridge-announce.ts.
|
|
18
18
|
const ANNOUNCEMENT: String = "res://.godot/gdharness-bridge.json"
|
|
19
19
|
const ANNOUNCE_PROTOCOL: int = 1
|
|
20
|
-
## Where each editor of this project says which ports it serves
|
|
20
|
+
## Where each editor of this project says which ports it serves. See [method _ports_record].
|
|
21
21
|
const EDITORS_RECORDED: String = "res://.godot/gdharness-editors"
|
|
22
22
|
const EDITORS_RECORDED_PROTOCOL: int = 1
|
|
23
23
|
const RECONNECT_DELAY: float = 3.0
|
|
@@ -172,7 +172,7 @@ func _process(_delta: float) -> void:
|
|
|
172
172
|
## The file saying which ports this editor serves, named by its process id, so `gdharness lsp` can
|
|
173
173
|
## find its language server when a server moved it off the default. One for each process rather
|
|
174
174
|
## than one for the project, because a gate's `--import` loads this addon too, and its plugin is
|
|
175
|
-
## shown none of the options that would say so
|
|
175
|
+
## shown none of the options that would say so. The reader tells the two apart by command line.
|
|
176
176
|
## Kept in step with `editorRecordsIn` in src/lsp-bridge.ts.
|
|
177
177
|
static func _ports_record() -> String:
|
|
178
178
|
return ProjectSettings.globalize_path(EDITORS_RECORDED).path_join(
|
|
@@ -265,7 +265,7 @@ func _resolve_server_url(explicit_url: String) -> String:
|
|
|
265
265
|
if raw != "":
|
|
266
266
|
if raw.is_valid_int() and int(raw) >= 1 and int(raw) <= 65535:
|
|
267
267
|
return "ws://127.0.0.1:%d/godot" % int(raw)
|
|
268
|
-
push_error("GDHARNESS_BRIDGE_PORT is %s, not a port
|
|
268
|
+
push_error("GDHARNESS_BRIDGE_PORT is %s, not a port, so %s is used" % [raw, DEFAULT_URL])
|
|
269
269
|
|
|
270
270
|
return DEFAULT_URL
|
|
271
271
|
|
|
@@ -411,7 +411,7 @@ static func _asked_for(variable: String) -> int:
|
|
|
411
411
|
## Turns breakpoint syncing on, so a debug session opening does not clear the editor's breakpoints.
|
|
412
412
|
##
|
|
413
413
|
## With the setting off, Godot's adapter answers `initialize` by clearing every breakpoint in the
|
|
414
|
-
## script editor, open and cached, and telling every session so
|
|
414
|
+
## script editor, open and cached, and telling every session so. With it on, it tells the new
|
|
415
415
|
## session what is set and clears nothing. A server opens a session for its first breakpoint or
|
|
416
416
|
## its first stack read, so off meant a harness session took the user's breakpoints away on its
|
|
417
417
|
## first debug call, silently. The adapter reads the setting again the moment it changes, so this
|
|
@@ -288,7 +288,7 @@ static func outside(target: Object, declared: Dictionary, value: Variant, shown:
|
|
|
288
288
|
return ""
|
|
289
289
|
return (
|
|
290
290
|
(
|
|
291
|
-
"%s is none of the names %s lists: %s, which the inspector holds it to
|
|
291
|
+
"%s is none of the names %s lists: %s, which the inspector holds it to. The engine "
|
|
292
292
|
+ "stores it as given."
|
|
293
293
|
)
|
|
294
294
|
% [shown, property, ", ".join(names)]
|
|
@@ -311,7 +311,7 @@ static func outside(target: Object, declared: Dictionary, value: Variant, shown:
|
|
|
311
311
|
if flags.is_empty() or stray == 0:
|
|
312
312
|
return ""
|
|
313
313
|
return (
|
|
314
|
-
"%d holds bits no flag of %s has (%d): its flags are %s
|
|
314
|
+
"%d holds bits no flag of %s has (%d): its flags are %s. The engine stores it as given."
|
|
315
315
|
% [whole, property, stray, _listed(flags)]
|
|
316
316
|
)
|
|
317
317
|
if hint != PROPERTY_HINT_RANGE:
|
|
@@ -327,7 +327,7 @@ static func outside(target: Object, declared: Dictionary, value: Variant, shown:
|
|
|
327
327
|
return ""
|
|
328
328
|
return (
|
|
329
329
|
(
|
|
330
|
-
"%s is outside the range %s declares, %s to %s, which the inspector holds it to
|
|
330
|
+
"%s is outside the range %s declares, %s to %s, which the inspector holds it to. The "
|
|
331
331
|
+ "engine stores it as given."
|
|
332
332
|
)
|
|
333
333
|
% [shown, property, _bound(low), _bound(high)]
|
|
@@ -544,7 +544,7 @@ static func not_held(
|
|
|
544
544
|
return (
|
|
545
545
|
(
|
|
546
546
|
"%s.bus takes the name of one of the project's audio buses, and %s is none of "
|
|
547
|
-
+ "them, so it would play on %s
|
|
547
|
+
+ "them, so it would play on %s. %s. project_info include audio_buses lists them, "
|
|
548
548
|
+ "and project_settings add_audio_bus adds one. Nothing was written."
|
|
549
549
|
)
|
|
550
550
|
% [
|
|
@@ -552,21 +552,21 @@ static func not_held(
|
|
|
552
552
|
given[1],
|
|
553
553
|
AudioServer.get_bus_name(0),
|
|
554
554
|
(
|
|
555
|
-
"
|
|
555
|
+
"The buses are " + ", ".join(names)
|
|
556
556
|
if nearest.is_empty()
|
|
557
|
-
else "
|
|
557
|
+
else "The nearest " + nearest
|
|
558
558
|
),
|
|
559
559
|
]
|
|
560
560
|
)
|
|
561
561
|
return (
|
|
562
|
-
"%s: it takes one of the names it lists, and %s is none of them
|
|
562
|
+
"%s: it takes one of the names it lists, and %s is none of them. %s. Nothing was written."
|
|
563
563
|
% [
|
|
564
564
|
said,
|
|
565
565
|
given[1],
|
|
566
566
|
(
|
|
567
|
-
"
|
|
567
|
+
"It lists " + ", ".join(names)
|
|
568
568
|
if nearest.is_empty()
|
|
569
|
-
else "
|
|
569
|
+
else "The nearest it lists " + nearest
|
|
570
570
|
),
|
|
571
571
|
]
|
|
572
572
|
)
|
|
@@ -593,7 +593,7 @@ static func held_by_children(node: Node, property: String) -> String:
|
|
|
593
593
|
|
|
594
594
|
## A sentence for each node path in [param given], by property, that names no node from
|
|
595
595
|
## [param node]. A path is written whatever it names, as the inspector writes one, since the node
|
|
596
|
-
## may be added next
|
|
596
|
+
## may be added next. A joint whose node_a named nothing then joined nothing, and no answer said so.
|
|
597
597
|
static func naming_nothing(node: Node, given: Dictionary) -> Dictionary:
|
|
598
598
|
var said: Dictionary = {}
|
|
599
599
|
for property: String in given:
|
|
@@ -610,7 +610,7 @@ static func naming_nothing(node: Node, given: Dictionary) -> Dictionary:
|
|
|
610
610
|
said[property] = (
|
|
611
611
|
(
|
|
612
612
|
"%s names no node from %s. It is written, as the editor writes one, since that "
|
|
613
|
-
+ "node may be added next
|
|
613
|
+
+ "node may be added next. Until it is, %s finds nothing."
|
|
614
614
|
)
|
|
615
615
|
% [str(named), node.name, property]
|
|
616
616
|
)
|
|
@@ -624,7 +624,7 @@ static func no_uniform(material: ShaderMaterial, property: String) -> String:
|
|
|
624
624
|
var uniform: String = property.trim_prefix("shader_parameter/")
|
|
625
625
|
if material.shader == null:
|
|
626
626
|
return (
|
|
627
|
-
"The ShaderMaterial has no shader, so it has no parameter %s yet
|
|
627
|
+
"The ShaderMaterial has no shader, so it has no parameter %s yet. Set shader first."
|
|
628
628
|
% uniform
|
|
629
629
|
)
|
|
630
630
|
var shader: String = material.shader.resource_path
|
|
@@ -634,5 +634,5 @@ static func no_uniform(material: ShaderMaterial, property: String) -> String:
|
|
|
634
634
|
uniforms.append(str(entry["name"]))
|
|
635
635
|
var said: String = "%s declares no uniform %s, which %s would set" % [named, uniform, property]
|
|
636
636
|
if uniforms.is_empty():
|
|
637
|
-
return said + "
|
|
637
|
+
return said + ". It declares no uniforms."
|
|
638
638
|
return GdhRead.offered(said, uniform, uniforms) + "."
|
|
@@ -4,7 +4,7 @@ extends RefCounted
|
|
|
4
4
|
##
|
|
5
5
|
## A scene is read off its SceneState rather than instantiated. Instantiating outside the editor
|
|
6
6
|
## runs every script's member initialisers and _init (measured on 4.7.2, with or without -e), so a
|
|
7
|
-
## read with no editor would have run the game's code on every node
|
|
7
|
+
## read with no editor would have run the game's code on every node. The editor gets placeholders
|
|
8
8
|
## and runs none. The state holds everything the file says, and the scenes it instances theirs,
|
|
9
9
|
## without making a node. Loading the file still does what loading does in the game: static
|
|
10
10
|
## initialisers run, and a resource with a script is made with it.
|
|
@@ -53,7 +53,7 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
## The node at [param node_path] in [param scene_path] as [method scene_node] answers it, when it is
|
|
56
|
-
## a [param kind]
|
|
56
|
+
## a [param kind]. Otherwise a refusal saying what the scene has there and where its nodes of that
|
|
57
57
|
## kind are, so the caller has the path to pass. The file is read once, because every load prints
|
|
58
58
|
## the engine's warnings about it again.
|
|
59
59
|
static func scene_node_of_kind(scene_path: String, node_path: String, kind: String) -> Dictionary:
|
|
@@ -143,7 +143,7 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
143
143
|
## file's own entries for it applied over it. Answers a refusal, or "".
|
|
144
144
|
##
|
|
145
145
|
## [param within] is the files being laid, outermost first, so a scene that instances one of them
|
|
146
|
-
## is refused rather than laid forever
|
|
146
|
+
## is refused rather than laid forever. The engine refuses to load such a scene, so this is only
|
|
147
147
|
## reached through a state built some other way. [param loaded] is the scene [param state] is read
|
|
148
148
|
## from, when the caller has it loaded already.
|
|
149
149
|
static func _lay(
|
|
@@ -200,7 +200,7 @@ static func _lay(
|
|
|
200
200
|
if prefix == "." and loaded == null:
|
|
201
201
|
nodes[path]["inheritedFrom"] = state.get_path()
|
|
202
202
|
# The name is the state's even for an entry laid already, since an inherited scene names its
|
|
203
|
-
# root itself
|
|
203
|
+
# root itself. A root instanced inside another scene is renamed by that scene once laid.
|
|
204
204
|
if not recovered:
|
|
205
205
|
nodes[path]["name"] = str(state.get_node_name(index))
|
|
206
206
|
if fresh:
|
|
@@ -229,7 +229,7 @@ static func _lay(
|
|
|
229
229
|
if loaded != null and not set_here.has(named):
|
|
230
230
|
set_here.append(named)
|
|
231
231
|
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
232
|
-
# instances a write here sets
|
|
232
|
+
# instances a write here sets. A file instanced below makes its own owner's editable.
|
|
233
233
|
if prefix == ".":
|
|
234
234
|
for editable: String in _editable_in(state.get_path(), loaded):
|
|
235
235
|
var at: String = _joined(prefix, editable)
|
|
@@ -558,7 +558,7 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
558
558
|
var serialiser: GdhSerialisation = GdhSerialisation.new()
|
|
559
559
|
var properties: Dictionary = {}
|
|
560
560
|
# A headless engine runs a script that is not a tool as the file loads, so what its _init and
|
|
561
|
-
# setters gave other properties read as values the file sets
|
|
561
|
+
# setters gave other properties read as values the file sets. The file's own lines say which it
|
|
562
562
|
# does. The editor runs no such script.
|
|
563
563
|
var untooled: bool = script != null and not script.is_tool() and not Engine.is_editor_hint()
|
|
564
564
|
var from_file: bool = (
|
|
@@ -80,7 +80,7 @@ static func kept_by_engine(object: Object, name: String, wanted: Variant, held:
|
|
|
80
80
|
|
|
81
81
|
## The stored properties of [param object] that are not in [param names] and no longer hold what
|
|
82
82
|
## [param before] recorded. One the object did not have before, which a script set on it declares,
|
|
83
|
-
## counts once it holds anything but its default
|
|
83
|
+
## counts once it holds anything but its default. One it no longer has went with the old script.
|
|
84
84
|
static func differing(object: Object, before: Dictionary, names: Dictionary) -> Array[String]:
|
|
85
85
|
var changed: Array[String] = []
|
|
86
86
|
var now: Dictionary = stored(object)
|
|
@@ -111,7 +111,7 @@ static func default_of(object: Object, name: String) -> Variant:
|
|
|
111
111
|
## What the declaration of [param name] in [param script] or a script it extends initialises it to,
|
|
112
112
|
## when that is a value written out, such as [code]PackedStringArray()[/code], or null. The engine
|
|
113
113
|
## reports a default only for an initializer it folded into a constant, and it folds no constructor
|
|
114
|
-
## call, so it gives none for such a property
|
|
114
|
+
## call, so it gives none for such a property. The saver then writes it whatever it holds, and the
|
|
115
115
|
## line read as a change some script had made.
|
|
116
116
|
static func _initialised(script: Script, name: String, type: int) -> Variant:
|
|
117
117
|
var declaration: RegEx = RegEx.create_from_string(
|
|
@@ -157,7 +157,7 @@ static func reached(object: Object, property: String) -> Dictionary:
|
|
|
157
157
|
return ids
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
## Adds what [param more] spares to [param into], both as [method reached] answers them
|
|
160
|
+
## Adds what [param more] spares to [param into], both as [method reached] answers them. True for an
|
|
161
161
|
## id spares the whole resource, as one a tool rebuilt.
|
|
162
162
|
static func spare(into: Dictionary, more: Dictionary) -> void:
|
|
163
163
|
for id: Variant in more:
|
|
@@ -507,7 +507,7 @@ static func setter_ran(object: Object, property: String, refused: String) -> Str
|
|
|
507
507
|
(
|
|
508
508
|
(
|
|
509
509
|
"%s %s is not a tool script, so its setter ran in the headless engine writing this "
|
|
510
|
-
+ "as it runs in the game
|
|
510
|
+
+ "as it runs in the game. An editor runs none and would have saved the value "
|
|
511
511
|
+ "given. "
|
|
512
512
|
)
|
|
513
513
|
% [refused, named]
|
|
@@ -539,7 +539,7 @@ static func move_script_set(answer: Dictionary, names: Array, owner: String) ->
|
|
|
539
539
|
(
|
|
540
540
|
"%s's script is not a tool, so it ran in the headless engine that wrote the file, and "
|
|
541
541
|
+ "set %s as it did. The file does not hold %s, as a save from the editor, where the "
|
|
542
|
-
+ "script does not run, would not
|
|
542
|
+
+ "script does not run, would not. The script sets %s again when it runs in the game."
|
|
543
543
|
)
|
|
544
544
|
% [owner, ", ".join(listed), pronoun, pronoun]
|
|
545
545
|
)
|
|
@@ -547,6 +547,9 @@ static func move_script_set(answer: Dictionary, names: Array, owner: String) ->
|
|
|
547
547
|
|
|
548
548
|
## Why [param name] on [param what] cannot be saved as the file held it.
|
|
549
549
|
static func unasked(what: String, name: String, why: String = "") -> String:
|
|
550
|
+
var told: String = why.trim_suffix(".")
|
|
551
|
+
if not told.is_empty():
|
|
552
|
+
told = ". " + told.left(1).to_upper() + told.substr(1)
|
|
550
553
|
return (
|
|
551
554
|
(
|
|
552
555
|
(
|
|
@@ -554,7 +557,7 @@ static func unasked(what: String, name: String, why: String = "") -> String:
|
|
|
554
557
|
+ "ask for it: its script ran when a headless engine loaded or set it, as it does "
|
|
555
558
|
+ "in the game and not in an editor%s. "
|
|
556
559
|
)
|
|
557
|
-
% [what, name,
|
|
560
|
+
% [what, name, told]
|
|
558
561
|
)
|
|
559
562
|
+ REMEDY
|
|
560
563
|
)
|
|
@@ -75,7 +75,7 @@ static func _stepped(target: Object, name: String, at: int) -> Array:
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
## The refusal for [param name], which [param target] does not declare, when a step of it indexes a
|
|
78
|
-
## list [param target] holds and names no resource in it
|
|
78
|
+
## list [param target] holds and names no resource in it, or "".
|
|
79
79
|
static func unlisted(target: Object, name: String) -> String:
|
|
80
80
|
var at: int = name.find("/")
|
|
81
81
|
while at > 0:
|
|
@@ -163,7 +163,7 @@ static func held_at(target: Object, property: String) -> Resource:
|
|
|
163
163
|
## only once the element exists, so a TileSet with no physics layer had no
|
|
164
164
|
## physics_layer_0/collision_layer to write. A list the engine declares as an array grows through
|
|
165
165
|
## the method named after its elements, add_physics_layer for physics_layer_, given the indices of
|
|
166
|
-
## the lists around it
|
|
166
|
+
## the lists around it. A list counted by a property, as a tile's polygons are by polygons_count,
|
|
167
167
|
## grows by raising the count.
|
|
168
168
|
static func grow(target: Object, name: String, called: String) -> String:
|
|
169
169
|
var steps: PackedStringArray = name.split("/")
|
|
@@ -255,7 +255,7 @@ static func class_called(target: Object) -> String:
|
|
|
255
255
|
|
|
256
256
|
|
|
257
257
|
## The refusal for [param name], which [param target] does not declare, when it runs through a
|
|
258
|
-
## resource saved in a file other than [param home], the one [param target] is saved in
|
|
258
|
+
## resource saved in a file other than [param home], the one [param target] is saved in, or "". The
|
|
259
259
|
## write belongs in that file, since this one would be saved and that one left as it was: through a
|
|
260
260
|
## TileMapLayer's tile_set kept in a file of its own, or through the mesh of an instance's editable
|
|
261
261
|
## child, which is built into the scene the instance comes from.
|
|
@@ -20,7 +20,7 @@ const GdhRead = preload("reading.gd")
|
|
|
20
20
|
const GdhSame = preload("same_values.gd")
|
|
21
21
|
const GdhSerialisation = preload("serialisation.gd")
|
|
22
22
|
const GdhShapes = preload("value_shapes.gd")
|
|
23
|
-
# The data types a Theme names its items by, as in
|
|
23
|
+
# The data types a Theme names its items by, as in `Button/colors/font_color`, and what each holds.
|
|
24
24
|
const THEME_ITEMS: Dictionary[String, Array] = {
|
|
25
25
|
"colors": [TYPE_COLOR, ""],
|
|
26
26
|
"constants": [TYPE_INT, ""],
|
|
@@ -40,6 +40,7 @@ const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_
|
|
|
40
40
|
## Control's layout_mode values, which the engine does not expose as constants.
|
|
41
41
|
const LAYOUT_MODE_POSITION: int = 0
|
|
42
42
|
const LAYOUT_MODE_ANCHORS: int = 1
|
|
43
|
+
const LAYOUT_MODE_UNCONTROLLED: int = 3
|
|
43
44
|
## What a container sets on each child it lays out, beside the anchors and offsets.
|
|
44
45
|
const PLACED_BY_A_CONTAINER: Array[String] = ["position", "size", "global_position"]
|
|
45
46
|
|
|
@@ -74,7 +75,7 @@ var _names_no_node: Dictionary = {}
|
|
|
74
75
|
##
|
|
75
76
|
## Three separate questions, asked in order, because a caller may say what it means in three
|
|
76
77
|
## ways: a dictionary that names its own type, a dictionary shaped like the type the property
|
|
77
|
-
## declares, or a bare array positional for a vector. Each is its own function
|
|
78
|
+
## declares, or a bare array positional for a vector. Each is its own function. Asking all three
|
|
78
79
|
## in one was twenty-four exits deep and impossible to follow.
|
|
79
80
|
##
|
|
80
81
|
## [param elements] is what a typed Array or Dictionary holds, as [method GdhDeclared.elements_of]
|
|
@@ -215,8 +216,8 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
215
216
|
var written_at: Dictionary[String, int] = {}
|
|
216
217
|
var writes: int = 0
|
|
217
218
|
# Which other property's write last changed each one already written, by name. A Gradient keeps
|
|
218
|
-
# one offset for each colour, so offsets given after colors cut them back, and the refusal
|
|
219
|
-
# only that the engine had changed the colours.
|
|
219
|
+
# one offset for each colour, so offsets given after `colors` cut them back, and the refusal
|
|
220
|
+
# said only that the engine had changed the colours.
|
|
220
221
|
var undone_by: Dictionary[String, String] = {}
|
|
221
222
|
# A refusal of one of several leaves the others unwritten too, which a refusal naming only the
|
|
222
223
|
# one it refused left a caller to guess.
|
|
@@ -349,7 +350,7 @@ func _note_undone(
|
|
|
349
350
|
## sizes its children and the scene keeps none of that for them, so their anchors, offsets, position
|
|
350
351
|
## and size are refused with what a child does set. A control in Position mode ignores an anchor
|
|
351
352
|
## preset, and the inspector offers anchors only in Anchors mode, so writing one puts it there
|
|
352
|
-
## first, as picking a preset in the editor does
|
|
353
|
+
## first, as picking a preset in the editor does, and alsoChanged then names layout_mode.
|
|
353
354
|
func _laid_out(control: Control, name: String) -> String:
|
|
354
355
|
var base: String = name.get_slice(":", 0)
|
|
355
356
|
var anchoring: bool = base == "anchors_preset" or base.begins_with("anchor_")
|
|
@@ -366,8 +367,14 @@ func _laid_out(control: Control, name: String) -> String:
|
|
|
366
367
|
)
|
|
367
368
|
% [control.name, parent.name, parent.get_class(), base]
|
|
368
369
|
)
|
|
369
|
-
|
|
370
|
+
var mode: int = GdhRead.as_int(control.get("layout_mode"))
|
|
371
|
+
if anchoring and mode == LAYOUT_MODE_POSITION:
|
|
370
372
|
control.set("layout_mode", LAYOUT_MODE_ANCHORS)
|
|
373
|
+
# A scene's root reads as Uncontrolled but keeps the Position mode it was made with until it
|
|
374
|
+
# enters a tree, and the engine ignores a preset in that stored mode, so full rect read back as
|
|
375
|
+
# top left on every root a headless edit wrote.
|
|
376
|
+
elif anchoring and mode == LAYOUT_MODE_UNCONTROLLED:
|
|
377
|
+
control.set("layout_mode", LAYOUT_MODE_UNCONTROLLED)
|
|
371
378
|
return ""
|
|
372
379
|
|
|
373
380
|
|
|
@@ -447,7 +454,7 @@ func _write(target: Object, key: String, value: Variant) -> void:
|
|
|
447
454
|
|
|
448
455
|
|
|
449
456
|
## The declaration the last step of [param path] would have, typed as the component it reaches, so
|
|
450
|
-
## a fraction for an integer vector's axis is refused as one for an int property is
|
|
457
|
+
## a fraction for an integer vector's axis is refused as one for an int property is, or
|
|
451
458
|
## {"refused": why} for the first step that is not there.
|
|
452
459
|
func _component_slot(target: Object, path: String) -> Dictionary:
|
|
453
460
|
var steps: PackedStringArray = path.split(":")
|
|
@@ -487,7 +494,7 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
487
494
|
return {
|
|
488
495
|
"refused":
|
|
489
496
|
(
|
|
490
|
-
"%s.%s is %s, so it has no %s
|
|
497
|
+
"%s.%s is %s, so it has no %s. It has %s."
|
|
491
498
|
% [owner, walked, sort, step, ", ".join(has)]
|
|
492
499
|
),
|
|
493
500
|
}
|
|
@@ -562,7 +569,7 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
562
569
|
|
|
563
570
|
|
|
564
571
|
## A property holding a Node is given the node the path leads to from [param target], as the
|
|
565
|
-
## editor's inspector gives it
|
|
572
|
+
## editor's inspector gives it. The scene is saved with the path. A res:// path could never name a
|
|
566
573
|
## node, so the remedy a caller is given is a node path.
|
|
567
574
|
func _node_reference(target: Object, property: String, raw: Variant) -> Array:
|
|
568
575
|
if raw == null:
|
|
@@ -739,7 +746,7 @@ func colour_from(raw: Variant, named: String) -> Array:
|
|
|
739
746
|
(
|
|
740
747
|
(
|
|
741
748
|
'%s is {"r": .., "g": .., "b": .., "a": ..} from 0 to 1, a optional, or an html '
|
|
742
|
-
+ 'code such as "#ff8000" or "#ff800080"
|
|
749
|
+
+ 'code such as "#ff8000" or "#ff800080". %s is neither.'
|
|
743
750
|
)
|
|
744
751
|
% [named, JSON.stringify(raw)]
|
|
745
752
|
),
|
|
@@ -891,7 +898,7 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
891
898
|
)
|
|
892
899
|
return [true, null]
|
|
893
900
|
# A plain Resource with a script or values of its own, as a read answers one built into a scene,
|
|
894
|
-
# is one to build
|
|
901
|
+
# is one to build. Taken as nothing, writing such a read back cleared the property.
|
|
895
902
|
if value.size() > 1:
|
|
896
903
|
return _parse_new_resource("Resource", value)
|
|
897
904
|
return [true, null]
|
|
@@ -139,7 +139,7 @@ static func from_the_root(said: String, node_path: String, root_name: String) ->
|
|
|
139
139
|
|
|
140
140
|
## [param said], the refusal for a path a scene does not have, with where the path leaves it: the
|
|
141
141
|
## last node of the path it has, [param reached] from the root, called [param root_name], when that
|
|
142
|
-
## is empty
|
|
142
|
+
## is empty, the name below it that is missing, and [param children], the names that node has, as
|
|
143
143
|
## after a node was moved or renamed. Names up to [constant NAMED_CHILDREN] of them.
|
|
144
144
|
static func missing_below(
|
|
145
145
|
said: String,
|
|
@@ -152,17 +152,21 @@ static func missing_below(
|
|
|
152
152
|
if children.size() > NAMED_CHILDREN:
|
|
153
153
|
shown += " and %d more" % (children.size() - NAMED_CHILDREN)
|
|
154
154
|
return (
|
|
155
|
-
"%s
|
|
155
|
+
"%s %s has no %s. %s."
|
|
156
156
|
% [
|
|
157
|
-
said,
|
|
158
|
-
"" if said.ends_with(".") else ".",
|
|
157
|
+
ended(said),
|
|
159
158
|
"The root, %s," % root_name if reached.is_empty() else "/".join(reached),
|
|
160
159
|
missing,
|
|
161
|
-
"
|
|
160
|
+
"Its children are " + shown if not children.is_empty() else "It has no children",
|
|
162
161
|
]
|
|
163
162
|
)
|
|
164
163
|
|
|
165
164
|
|
|
165
|
+
## [param said] with a full stop at its end, so another sentence can follow it.
|
|
166
|
+
static func ended(said: String) -> String:
|
|
167
|
+
return said if said.ends_with(".") else said + "."
|
|
168
|
+
|
|
169
|
+
|
|
166
170
|
## [param said], the refusal for a [param kind] the scene does not have at [param node_path], with
|
|
167
171
|
## what is there when the scene has a node there, [param found] its class, and [param holders], the
|
|
168
172
|
## paths of the nodes in the scene that are one, so the caller has the path to pass. Names up to
|
|
@@ -180,7 +184,7 @@ static func not_of_kind(
|
|
|
180
184
|
return (
|
|
181
185
|
told
|
|
182
186
|
+ (
|
|
183
|
-
" The scene holds no %s
|
|
187
|
+
" The scene holds no %s. scene_node add with nodeType %s puts one in it."
|
|
184
188
|
% [kind, kind]
|
|
185
189
|
)
|
|
186
190
|
)
|
|
@@ -222,17 +226,17 @@ static func offered(said: String, word: String, names: Array) -> String:
|
|
|
222
226
|
var every: PackedStringArray = PackedStringArray(names)
|
|
223
227
|
var close: PackedStringArray = nearest(word, every)
|
|
224
228
|
if close.is_empty():
|
|
225
|
-
return "%s
|
|
229
|
+
return "%s It has %s" % [ended(said), ", ".join(every)]
|
|
226
230
|
return (
|
|
227
|
-
"%s
|
|
228
|
-
% [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
231
|
+
"%s The nearest it has %s %s"
|
|
232
|
+
% [ended(said), "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
229
233
|
)
|
|
230
234
|
|
|
231
235
|
|
|
232
236
|
## [param said], the refusal for [param property], which [param target] does not declare, with the
|
|
233
237
|
## names it declares nearest it when any are, as a misspelt one is usually one of them, or else the
|
|
234
238
|
## variables its script declares. The engine's own are not listed when none is near, since an object
|
|
235
|
-
## has a hundred, while a script's are the few a caller most likely meant
|
|
239
|
+
## has a hundred, while a script's are the few a caller most likely meant. An object with no script
|
|
236
240
|
## is told where they are listed instead, since the refusal alone left nothing to try.
|
|
237
241
|
static func near_property(said: String, target: Object, property: String) -> String:
|
|
238
242
|
var headings: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
@@ -243,7 +247,7 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
243
247
|
if usage & PROPERTY_USAGE_ARRAY:
|
|
244
248
|
var listed: String = _list_called(target, declared, property)
|
|
245
249
|
if not listed.is_empty():
|
|
246
|
-
return "%s
|
|
250
|
+
return "%s %s" % [ended(said), listed]
|
|
247
251
|
if not usage & headings:
|
|
248
252
|
names.append(str(declared.get("name", "")))
|
|
249
253
|
if usage & PROPERTY_USAGE_SCRIPT_VARIABLE and not usage & headings:
|
|
@@ -251,8 +255,8 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
251
255
|
var close: PackedStringArray = nearest(property, PackedStringArray(names))
|
|
252
256
|
if not close.is_empty():
|
|
253
257
|
return (
|
|
254
|
-
"%s
|
|
255
|
-
% [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
258
|
+
"%s The nearest it has %s %s"
|
|
259
|
+
% [ended(said), "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
256
260
|
)
|
|
257
261
|
# With a script that declares nothing readable, as one that does not compile, the name is
|
|
258
262
|
# likelier the script's than the class's, so the class's list would point the wrong way.
|
|
@@ -261,17 +265,17 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
261
265
|
if scripted.is_empty():
|
|
262
266
|
return (
|
|
263
267
|
(
|
|
264
|
-
"%s
|
|
268
|
+
"%s editor_classes info with className %s and includeInherited true lists the "
|
|
265
269
|
+ "ones it has"
|
|
266
270
|
)
|
|
267
|
-
% [said, target.get_class()]
|
|
271
|
+
% [ended(said), target.get_class()]
|
|
268
272
|
)
|
|
269
273
|
scripted.sort()
|
|
270
|
-
return "%s
|
|
274
|
+
return "%s Its script declares %s" % [ended(said), ", ".join(PackedStringArray(scripted))]
|
|
271
275
|
|
|
272
276
|
|
|
273
277
|
## How [param target] keeps the list [param property] names, when that is the label of one of the
|
|
274
|
-
## engine's numbered lists, such as an OptionButton's Items
|
|
278
|
+
## engine's numbered lists, such as an OptionButton's Items. Empty when it is not. The engine has no
|
|
275
279
|
## property by that name, only a count and each entry's fields under a numbered prefix, so "items"
|
|
276
280
|
## was refused with nothing near it to offer.
|
|
277
281
|
static func _list_called(target: Object, count: Dictionary, property: String) -> String:
|
|
@@ -292,7 +296,7 @@ static func _list_called(target: Object, count: Dictionary, property: String) ->
|
|
|
292
296
|
)
|
|
293
297
|
return (
|
|
294
298
|
(
|
|
295
|
-
"
|
|
299
|
+
"It keeps its %s as %s, how many there are, and %s under %s for the first, %s for the "
|
|
296
300
|
+ "second and so on: give %s before the fields of the entries it makes"
|
|
297
301
|
)
|
|
298
302
|
% [parts[0].to_lower(), counted, each, first, parts[1] + "1/", counted]
|
|
@@ -599,7 +599,7 @@ func fixed(node: Node, deleting: bool = false) -> String:
|
|
|
599
599
|
if node.owner == null:
|
|
600
600
|
return foreign(node)
|
|
601
601
|
if node.owner != root:
|
|
602
|
-
# Editable children keep values here and no more
|
|
602
|
+
# Editable children keep values here and no more. The scene instanced may have the node from
|
|
603
603
|
# a scene it inherits, which is the one to edit.
|
|
604
604
|
var instanced: String = node.owner.scene_file_path
|
|
605
605
|
var packed: PackedScene = load(instanced) as PackedScene
|
|
@@ -624,7 +624,7 @@ func fixed(node: Node, deleting: bool = false) -> String:
|
|
|
624
624
|
|
|
625
625
|
|
|
626
626
|
## Each node the scenes [param state] inherits give it, by path, with the deepest file making it
|
|
627
|
-
## more than values
|
|
627
|
+
## more than values. A node [param state] makes itself is not in it.
|
|
628
628
|
static func makers(state: SceneState) -> Dictionary:
|
|
629
629
|
var made_by: Dictionary = {}
|
|
630
630
|
var at: SceneState = state.get_base_scene_state()
|
|
@@ -764,7 +764,7 @@ static func unfit_name(node_name: String) -> String:
|
|
|
764
764
|
if valid != node_name:
|
|
765
765
|
return (
|
|
766
766
|
(
|
|
767
|
-
"%s is not a name a node can have
|
|
767
|
+
"%s is not a name a node can have. Godot would save it as %s. The characters "
|
|
768
768
|
+ '. : @ / " %% cannot be in a node name.'
|
|
769
769
|
)
|
|
770
770
|
% [node_name, valid]
|
|
@@ -784,7 +784,7 @@ func unusable_name(parent: Node, child_name: String, moving: Node = null) -> Str
|
|
|
784
784
|
# The root by its name, since its path is ".", and ". already has a child" read as a typo.
|
|
785
785
|
var holder: String = "The root, %s," % parent.name if parent == root else path_of(parent)
|
|
786
786
|
return (
|
|
787
|
-
"%s already has a child named %s
|
|
787
|
+
"%s already has a child named %s. %s is free there."
|
|
788
788
|
% [holder, child_name, _free_name(parent, child_name)]
|
|
789
789
|
)
|
|
790
790
|
return ""
|
|
@@ -1210,7 +1210,7 @@ func _stray_text(node: Node, name: String) -> String:
|
|
|
1210
1210
|
(
|
|
1211
1211
|
(
|
|
1212
1212
|
"the script of %s did not start when a headless engine instanced %s, so the "
|
|
1213
|
-
+ "node would be saved without it
|
|
1213
|
+
+ "node would be saved without it. The engine's messages give its errors. "
|
|
1214
1214
|
)
|
|
1215
1215
|
% [at, path]
|
|
1216
1216
|
)
|
|
@@ -15,7 +15,7 @@ const GdhShapes = preload("value_shapes.gd")
|
|
|
15
15
|
const NUMBERS: Array[int] = [TYPE_BOOL, TYPE_INT, TYPE_FLOAT]
|
|
16
16
|
const TEXTS: Array[int] = [TYPE_STRING, TYPE_STRING_NAME, TYPE_NODE_PATH]
|
|
17
17
|
# 2^53. A JSON number is a double, and every whole number below this one in size has a double of
|
|
18
|
-
# its own
|
|
18
|
+
# its own. From here on two or more share each double, so one arriving tells nobody which was sent.
|
|
19
19
|
const EXACT: int = 9_007_199_254_740_992
|
|
20
20
|
# The types that cross as a plain JSON list and have to be built back into themselves. A list is
|
|
21
21
|
# what a caller can write by hand and what the serialiser answers with, and the parameter or
|
|
@@ -136,7 +136,7 @@ func serialize_value(value: Variant) -> Variant:
|
|
|
136
136
|
return value
|
|
137
137
|
var carried: Variant = call(serialiser, value)
|
|
138
138
|
# The fixed shapes carry their components as bare floats, so an infinite one in a Vector2 is
|
|
139
|
-
# tagged here, and a 32-bit one shortened
|
|
139
|
+
# tagged here, and a 32-bit one shortened. Containers reach their members through this already.
|
|
140
140
|
var type: int = typeof(value)
|
|
141
141
|
return (
|
|
142
142
|
carried
|
|
@@ -345,7 +345,7 @@ static func comparable(one: Variant, other: Variant) -> bool:
|
|
|
345
345
|
##
|
|
346
346
|
## A list of what is accepted rather than of what is refused, which is the way round that errs
|
|
347
347
|
## safely. The engine's own conversion table is not readable from GDScript, so either list is a
|
|
348
|
-
## guess at it
|
|
348
|
+
## guess at it. Guessing narrow costs a caller a refusal naming both types, and guessing wide costs
|
|
349
349
|
## them the damage above. A refusal is the cheaper mistake, and it is the one they can act on.
|
|
350
350
|
static func acceptable(value: Variant, type: int) -> bool:
|
|
351
351
|
if type == TYPE_NIL:
|
|
@@ -560,7 +560,7 @@ func _serialize_rid(value: RID) -> Dictionary:
|
|
|
560
560
|
|
|
561
561
|
# A whole number from 2^53 on as its digits, because JSON numbers are doubles on the server's side
|
|
562
562
|
# and on the engine's, and from there on neighbouring whole numbers share a double. A RefCounted's
|
|
563
|
-
# instance id is one, with its top bit set, and a resource uid another
|
|
563
|
+
# instance id is one, with its top bit set, and a resource uid another. Read as numbers they came
|
|
564
564
|
# back as a neighbouring id, which named nothing or something else.
|
|
565
565
|
func _serialize_int(value: int) -> Variant:
|
|
566
566
|
if value > -EXACT and value < EXACT:
|
|
@@ -93,7 +93,7 @@ func connect_animation_states(args: Dictionary) -> Dictionary:
|
|
|
93
93
|
if not advances.has(advance_mode):
|
|
94
94
|
return {
|
|
95
95
|
"ok": false,
|
|
96
|
-
"error": "Unsupported advanceMode: %s
|
|
96
|
+
"error": "Unsupported advanceMode: %s. It is auto, enabled or disabled" % advance_mode,
|
|
97
97
|
}
|
|
98
98
|
if not advance_condition.is_empty() and advance_mode != "auto":
|
|
99
99
|
return {
|
|
@@ -189,10 +189,10 @@ static func _no_state(sm: AnimationNodeStateMachine, state_name: String) -> Stri
|
|
|
189
189
|
var close: PackedStringArray = GdhRead.nearest(state_name, states)
|
|
190
190
|
if not close.is_empty():
|
|
191
191
|
return (
|
|
192
|
-
"%s
|
|
192
|
+
"%s. The nearest it has %s %s."
|
|
193
193
|
% [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
194
194
|
)
|
|
195
|
-
return "%s
|
|
195
|
+
return "%s. Its states are %s." % [said, ", ".join(states)]
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
## The state machine [param args] names, as [scene, machine, files, refusal, tree]: the machine is
|
|
@@ -276,9 +276,9 @@ func disconnect_animation_states(args: Dictionary) -> Dictionary:
|
|
|
276
276
|
var _listed: bool = leaving.append(str(sm.get_transition_to(index)))
|
|
277
277
|
var said: String = "There is no transition from %s to %s" % [from_state, to_state]
|
|
278
278
|
if leaving.is_empty():
|
|
279
|
-
return scene.refuse("%s
|
|
279
|
+
return scene.refuse("%s. None leaves %s." % [said, from_state])
|
|
280
280
|
return scene.refuse(
|
|
281
|
-
"%s
|
|
281
|
+
"%s. The ones leaving %s go to %s." % [said, from_state, ", ".join(leaving)]
|
|
282
282
|
)
|
|
283
283
|
sm.remove_transition(StringName(from_state), StringName(to_state))
|
|
284
284
|
if sm.has_transition(StringName(from_state), StringName(to_state)):
|