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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## Project settings read the same way headless and from the open editor, because the two are one
|
|
4
|
+
## question asked of two places: a field one of them left out read as the editor and the file
|
|
5
|
+
## disagreeing about something they agree about.
|
|
6
|
+
|
|
7
|
+
const GdhRead = preload("reading.gd")
|
|
8
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## One setting: whether the engine knows it, its value, whether that is the engine's default, and
|
|
12
|
+
## the feature overrides that replace it in some builds.
|
|
13
|
+
static func one(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
14
|
+
var result: Dictionary = {
|
|
15
|
+
"setting_path": setting_path, "exists": ProjectSettings.has_setting(setting_path)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if result["exists"]:
|
|
19
|
+
var value: Variant = ProjectSettings.get_setting(setting_path)
|
|
20
|
+
result["value"] = values.serialize_value(value)
|
|
21
|
+
# A setting the engine registers exists whether or not the project sets it, so exists did
|
|
22
|
+
# not say whether project.godot has a line for it. At its default it has none: the engine
|
|
23
|
+
# drops such a key when it saves.
|
|
24
|
+
var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
25
|
+
if default_value != null:
|
|
26
|
+
result["at_default"] = value == default_value
|
|
27
|
+
else:
|
|
28
|
+
result["value"] = null
|
|
29
|
+
result["message"] = "Setting does not exist"
|
|
30
|
+
|
|
31
|
+
# An override exists only while project.godot sets it, so one the file does not set read as a
|
|
32
|
+
# setting nobody had heard of, when a build with that feature reads its base setting instead.
|
|
33
|
+
var base: String = override_base(setting_path)
|
|
34
|
+
if not base.is_empty():
|
|
35
|
+
result["override_of"] = base
|
|
36
|
+
if not result["exists"] and ProjectSettings.has_setting(base):
|
|
37
|
+
result["holds"] = values.serialize_value(ProjectSettings.get_setting(base))
|
|
38
|
+
result["message"] = overridden_by_none(setting_path, values)
|
|
39
|
+
|
|
40
|
+
# The value under this name is not the one every build reads: `name.windows` replaces it on
|
|
41
|
+
# Windows, `name.editor` in the editor. Read alone, a caller saw the plain value and not the one
|
|
42
|
+
# their game showed.
|
|
43
|
+
var overrides: Dictionary = overrides_of(setting_path, values)
|
|
44
|
+
if not overrides.is_empty():
|
|
45
|
+
result["overrides"] = overrides
|
|
46
|
+
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Every setting whose name starts with [param prefix], with the type the engine registers for
|
|
51
|
+
## each.
|
|
52
|
+
##
|
|
53
|
+
## The type is here because it is the half a name hides. A setting sitting among a family of levels
|
|
54
|
+
## can be a bool, and a caller that assumes otherwise writes a level over it and sees nothing go
|
|
55
|
+
## wrong: 2 is as true as true is.
|
|
56
|
+
static func under(prefix: String, values: GdhSerialisation) -> Dictionary:
|
|
57
|
+
var found: Array[Dictionary] = []
|
|
58
|
+
for property: Dictionary in ProjectSettings.get_property_list():
|
|
59
|
+
var name: String = str(property.get("name", ""))
|
|
60
|
+
if not name.begins_with(prefix):
|
|
61
|
+
continue
|
|
62
|
+
var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
|
|
63
|
+
var value: Variant = ProjectSettings.get_setting(name)
|
|
64
|
+
var described: Dictionary = {
|
|
65
|
+
"setting": name,
|
|
66
|
+
"type": type_string(kind),
|
|
67
|
+
"value": values.serialize_value(value),
|
|
68
|
+
}
|
|
69
|
+
# As a single get says it, so a family read can be told apart into what the project sets
|
|
70
|
+
# and the engine's defaults: "input/" is ninety actions the engine ships and the project's few.
|
|
71
|
+
var default_value: Variant = ProjectSettings.property_get_revert(name)
|
|
72
|
+
if default_value != null:
|
|
73
|
+
described["at_default"] = value == default_value
|
|
74
|
+
var base: String = override_base(name)
|
|
75
|
+
if not base.is_empty():
|
|
76
|
+
described["override_of"] = base
|
|
77
|
+
found.append(described)
|
|
78
|
+
|
|
79
|
+
found.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a["setting"] < b["setting"])
|
|
80
|
+
return {"prefix": prefix, "count": found.size(), "settings": found}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## The setting [param setting_path] overrides for builds with one feature, as
|
|
84
|
+
## `application/run/max_fps.mobile` does `application/run/max_fps`, or "" for a plain setting.
|
|
85
|
+
static func override_base(setting_path: String) -> String:
|
|
86
|
+
var dot: int = setting_path.find(".", setting_path.rfind("/") + 1)
|
|
87
|
+
return "" if dot == -1 else setting_path.substr(0, dot)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## The overrides project.godot sets for [param setting_path], by feature.
|
|
91
|
+
static func overrides_of(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
92
|
+
var overrides: Dictionary = {}
|
|
93
|
+
for info: Dictionary in ProjectSettings.get_property_list():
|
|
94
|
+
var name: String = str(info.get("name", ""))
|
|
95
|
+
if name.begins_with(setting_path + "."):
|
|
96
|
+
overrides[name.substr(setting_path.length() + 1)] = values.serialize_value(
|
|
97
|
+
ProjectSettings.get_setting(name)
|
|
98
|
+
)
|
|
99
|
+
return overrides
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## What a build with the feature of [param setting_path], an override project.godot does not set,
|
|
103
|
+
## reads in its place.
|
|
104
|
+
static func overridden_by_none(setting_path: String, values: GdhSerialisation) -> String:
|
|
105
|
+
var base: String = override_base(setting_path)
|
|
106
|
+
var feature: String = setting_path.substr(base.length() + 1)
|
|
107
|
+
var said: String = (
|
|
108
|
+
(
|
|
109
|
+
"project.godot sets no %s override of %s, so a build with the %s feature reads %s, "
|
|
110
|
+
+ "the value under holds"
|
|
111
|
+
)
|
|
112
|
+
% [feature, base, feature, base]
|
|
113
|
+
)
|
|
114
|
+
var others: Array[String] = []
|
|
115
|
+
for name: String in overrides_of(base, values).keys():
|
|
116
|
+
if name != feature:
|
|
117
|
+
others.append(name)
|
|
118
|
+
if others.is_empty():
|
|
119
|
+
return said + "."
|
|
120
|
+
others.sort()
|
|
121
|
+
return said + (", unless it also has a feature with an override: %s." % ", ".join(others))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://c1ncxrof35y6e
|
|
@@ -5,10 +5,14 @@ extends Node
|
|
|
5
5
|
## preloaded rather than looked up on disk at runtime, so a missing one fails to parse here
|
|
6
6
|
## instead of leaving a command silently unanswered.
|
|
7
7
|
|
|
8
|
+
const GdhSceneFile = preload("scene_file.gd")
|
|
8
9
|
const GdhSceneTools = preload("tools/scene_tools.gd")
|
|
9
10
|
const GdhSignalTools = preload("tools/signal_tools.gd")
|
|
10
11
|
const GdhResourceTools = preload("tools/resource_tools.gd")
|
|
12
|
+
const GdhTilesetTools = preload("tools/tileset_tools.gd")
|
|
13
|
+
const GdhAnimationStates = preload("tools/animation_states.gd")
|
|
11
14
|
const GdhAnimationTools = preload("tools/animation_tools.gd")
|
|
15
|
+
const GdhAnimationTracks = preload("tools/animation_tracks.gd")
|
|
12
16
|
const GdhPlayTools = preload("tools/play_tools.gd")
|
|
13
17
|
const GdhClassTools = preload("tools/class_tools.gd")
|
|
14
18
|
const GdhSettingsTools = preload("tools/settings_tools.gd")
|
|
@@ -20,7 +24,10 @@ var _editor_plugin: EditorPlugin = null
|
|
|
20
24
|
var _scene_tools: GdhSceneTools = null
|
|
21
25
|
var _signal_tools: GdhSignalTools = null
|
|
22
26
|
var _resource_tools: GdhResourceTools = null
|
|
27
|
+
var _tileset_tools: GdhTilesetTools = null
|
|
28
|
+
var _animation_states: GdhAnimationStates = null
|
|
23
29
|
var _animation_tools: GdhAnimationTools = null
|
|
30
|
+
var _animation_tracks: GdhAnimationTracks = null
|
|
24
31
|
var _play_tools: GdhPlayTools = null
|
|
25
32
|
var _class_tools: GdhClassTools = null
|
|
26
33
|
var _settings_tools: GdhSettingsTools = null
|
|
@@ -36,7 +43,10 @@ func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
|
36
43
|
_init_tools()
|
|
37
44
|
_scene_tools.set_editor_plugin(plugin)
|
|
38
45
|
_resource_tools.set_editor_plugin(plugin)
|
|
46
|
+
_tileset_tools.set_editor_plugin(plugin)
|
|
39
47
|
_animation_tools.set_editor_plugin(plugin)
|
|
48
|
+
_animation_states.set_editor_plugin(plugin)
|
|
49
|
+
_animation_tracks.set_editor_plugin(plugin)
|
|
40
50
|
_play_tools.set_editor_plugin(plugin)
|
|
41
51
|
_class_tools.set_editor_plugin(plugin)
|
|
42
52
|
_filesystem_tools.set_editor_plugin(plugin)
|
|
@@ -60,10 +70,22 @@ func _init_tools() -> void:
|
|
|
60
70
|
_resource_tools.name = "ResourceTools"
|
|
61
71
|
add_child(_resource_tools)
|
|
62
72
|
|
|
73
|
+
_tileset_tools = GdhTilesetTools.new()
|
|
74
|
+
_tileset_tools.name = "TilesetTools"
|
|
75
|
+
add_child(_tileset_tools)
|
|
76
|
+
|
|
63
77
|
_animation_tools = GdhAnimationTools.new()
|
|
64
78
|
_animation_tools.name = "AnimationTools"
|
|
65
79
|
add_child(_animation_tools)
|
|
66
80
|
|
|
81
|
+
_animation_states = GdhAnimationStates.new()
|
|
82
|
+
_animation_states.name = "AnimationStates"
|
|
83
|
+
add_child(_animation_states)
|
|
84
|
+
|
|
85
|
+
_animation_tracks = GdhAnimationTracks.new()
|
|
86
|
+
_animation_tracks.name = "AnimationTracks"
|
|
87
|
+
add_child(_animation_tracks)
|
|
88
|
+
|
|
67
89
|
_play_tools = GdhPlayTools.new()
|
|
68
90
|
_play_tools.name = "PlayTools"
|
|
69
91
|
add_child(_play_tools)
|
|
@@ -94,6 +116,7 @@ func _init_tools() -> void:
|
|
|
94
116
|
"reparent_node": [_scene_tools, "reparent_node"],
|
|
95
117
|
"set_node_properties": [_scene_tools, "set_node_properties"],
|
|
96
118
|
"get_node_properties": [_scene_tools, "get_node_properties"],
|
|
119
|
+
"bake_navigation": [_scene_tools, "bake_navigation"],
|
|
97
120
|
"save_scene": [_scene_tools, "save_scene"],
|
|
98
121
|
"connect_signal": [_signal_tools, "connect_signal"],
|
|
99
122
|
"disconnect_signal": [_signal_tools, "disconnect_signal"],
|
|
@@ -104,6 +127,7 @@ func _init_tools() -> void:
|
|
|
104
127
|
"files_moved": [_filesystem_tools, "files_moved"],
|
|
105
128
|
"global_classes": [_class_tools, "global_classes"],
|
|
106
129
|
"reload_script": [_class_tools, "reload_script"],
|
|
130
|
+
"scripts_behind": [_class_tools, "scripts_behind"],
|
|
107
131
|
# Settings, for a caller that asked the editor rather than the file on disk
|
|
108
132
|
"get_project_setting": [_settings_tools, "get_project_setting"],
|
|
109
133
|
"adopt_project_settings": [_settings_tools, "adopt_project_settings"],
|
|
@@ -113,10 +137,11 @@ func _init_tools() -> void:
|
|
|
113
137
|
"modify_resource": [_resource_tools, "modify_resource"],
|
|
114
138
|
"read_resource": [_resource_tools, "read_resource"],
|
|
115
139
|
"create_shader": [_resource_tools, "create_shader"],
|
|
116
|
-
"create_tileset": [
|
|
117
|
-
"set_tilemap_cells": [
|
|
140
|
+
"create_tileset": [_tileset_tools, "create_tileset"],
|
|
141
|
+
"set_tilemap_cells": [_tileset_tools, "set_tilemap_cells"],
|
|
118
142
|
"set_theme_color": [_resource_tools, "set_theme_color"],
|
|
119
143
|
"set_theme_font_size": [_resource_tools, "set_theme_font_size"],
|
|
144
|
+
"remove_theme_item": [_resource_tools, "remove_theme_item"],
|
|
120
145
|
"play_scene": [_play_tools, "play_scene"],
|
|
121
146
|
"restart_editor": [_play_tools, "restart_editor"],
|
|
122
147
|
"quit_editor": [_play_tools, "quit_editor"],
|
|
@@ -130,12 +155,15 @@ func _init_tools() -> void:
|
|
|
130
155
|
"read_animations": [_animation_tools, "read_animations"],
|
|
131
156
|
"read_animation_states": [_animation_tools, "read_animation_states"],
|
|
132
157
|
"create_animation": [_animation_tools, "create_animation"],
|
|
133
|
-
"add_animation_track": [
|
|
158
|
+
"add_animation_track": [_animation_tracks, "add_animation_track"],
|
|
159
|
+
"remove_animation_track": [_animation_tracks, "remove_animation_track"],
|
|
134
160
|
"set_animation": [_animation_tools, "set_animation"],
|
|
135
161
|
"rename_animation": [_animation_tools, "rename_animation"],
|
|
136
162
|
"remove_animation": [_animation_tools, "remove_animation"],
|
|
137
|
-
"add_animation_state": [
|
|
138
|
-
"connect_animation_states": [
|
|
163
|
+
"add_animation_state": [_animation_states, "add_animation_state"],
|
|
164
|
+
"connect_animation_states": [_animation_states, "connect_animation_states"],
|
|
165
|
+
"remove_animation_state": [_animation_states, "remove_animation_state"],
|
|
166
|
+
"disconnect_animation_states": [_animation_states, "disconnect_animation_states"],
|
|
139
167
|
}
|
|
140
168
|
|
|
141
169
|
|
|
@@ -150,8 +178,13 @@ func execute_tool(tool_name: String, args: Dictionary) -> Dictionary:
|
|
|
150
178
|
if not node.has_method(method):
|
|
151
179
|
return {"ok": false, "error": "Tool method not found: %s.%s" % [node.name, method]}
|
|
152
180
|
|
|
181
|
+
GdhSceneFile.also_saved.clear()
|
|
153
182
|
var result: Variant = node.call(method, args)
|
|
154
183
|
if result is Dictionary:
|
|
155
|
-
|
|
184
|
+
var answered: Dictionary = result
|
|
185
|
+
if not GdhSceneFile.also_saved.is_empty():
|
|
186
|
+
answered["alsoSaved"] = GdhSceneFile.also_saved.duplicate(true)
|
|
187
|
+
GdhSceneFile.also_saved.clear()
|
|
188
|
+
return answered
|
|
156
189
|
|
|
157
190
|
return {"ok": false, "error": "Invalid tool result from: " + tool_name}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends "animation_tools.gd"
|
|
3
|
+
|
|
4
|
+
## The states and transitions of an AnimationTree's state machines, beside the players and tracks
|
|
5
|
+
## the script it extends edits, whose helpers it shares.
|
|
6
|
+
|
|
7
|
+
## How far right of the rightmost state a new one goes in the editor's graph, wider than a state's
|
|
8
|
+
## box there.
|
|
9
|
+
const STATE_SPACING: float = 200.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# add_animation_state
|
|
14
|
+
# =============================================================================
|
|
15
|
+
func add_animation_state(args: Dictionary) -> Dictionary:
|
|
16
|
+
var state_name: String = str(args.get("stateName", ""))
|
|
17
|
+
var animation_name: String = str(args.get("animationName", ""))
|
|
18
|
+
if state_name.is_empty():
|
|
19
|
+
return {"ok": false, "error": "Missing stateName"}
|
|
20
|
+
if animation_name.is_empty():
|
|
21
|
+
return {"ok": false, "error": "Missing animationName"}
|
|
22
|
+
var machine: Array = _machine_at(args)
|
|
23
|
+
if machine[1] == null:
|
|
24
|
+
return machine[3]
|
|
25
|
+
var scene: GdhSceneFile = machine[0]
|
|
26
|
+
var sm: AnimationNodeStateMachine = machine[1]
|
|
27
|
+
var anim_tree: AnimationTree = machine[4]
|
|
28
|
+
# add_node fails with nothing but a line in the editor's log on a name in use, and Start and End
|
|
29
|
+
# are in use in every state machine.
|
|
30
|
+
if sm.has_node(StringName(state_name)):
|
|
31
|
+
return scene.refuse("The state machine already has a state named %s." % state_name)
|
|
32
|
+
var player: AnimationPlayer = anim_tree.get_node_or_null(anim_tree.anim_player) as AnimationPlayer
|
|
33
|
+
var found: bool = player != null and player.has_animation(StringName(animation_name))
|
|
34
|
+
|
|
35
|
+
var anim_node: AnimationNodeAnimation = AnimationNodeAnimation.new()
|
|
36
|
+
anim_node.animation = StringName(animation_name)
|
|
37
|
+
# Beside the rightmost state: add_node puts a state at the origin, where the editor's graph
|
|
38
|
+
# draws it over whatever is already there, Start included.
|
|
39
|
+
var rightmost: Vector2 = Vector2.ZERO
|
|
40
|
+
for state: StringName in sm.get_node_list():
|
|
41
|
+
var held: Vector2 = sm.get_node_position(state)
|
|
42
|
+
if held.x > rightmost.x:
|
|
43
|
+
rightmost = held
|
|
44
|
+
var position: Vector2 = rightmost + Vector2(STATE_SPACING, 0)
|
|
45
|
+
sm.add_node(StringName(state_name), anim_node, position)
|
|
46
|
+
if not sm.has_node(StringName(state_name)):
|
|
47
|
+
return scene.refuse("The state machine did not take the state %s." % state_name)
|
|
48
|
+
|
|
49
|
+
var answer: Dictionary = {
|
|
50
|
+
"ok": true,
|
|
51
|
+
"stateName": state_name,
|
|
52
|
+
"animationName": animation_name,
|
|
53
|
+
# False when the tree has no player to look the animation up in, or its player has none of
|
|
54
|
+
# that name.
|
|
55
|
+
"animationFound": found,
|
|
56
|
+
"position": GdhSerialisation.new().serialize_value(position),
|
|
57
|
+
}
|
|
58
|
+
# Written all the same, as the editor writes one: a state machine is often laid out before its
|
|
59
|
+
# animations are made, and refusing it stopped a tree being built in that order.
|
|
60
|
+
if player != null and not found:
|
|
61
|
+
answer["animationMissing"] = GdhAnimationReads.missing_animation(
|
|
62
|
+
player, scene.path_of(player), animation_name
|
|
63
|
+
)
|
|
64
|
+
var files: Dictionary = machine[2]
|
|
65
|
+
return _finish(scene, Callable(), files, answer)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# connect_animation_states
|
|
70
|
+
# =============================================================================
|
|
71
|
+
func connect_animation_states(args: Dictionary) -> Dictionary:
|
|
72
|
+
var from_state: String = str(args.get("fromState", ""))
|
|
73
|
+
var to_state: String = str(args.get("toState", ""))
|
|
74
|
+
var transition_type: String = str(args.get("transitionType", "immediate"))
|
|
75
|
+
var advance_condition: String = str(args.get("advanceCondition", ""))
|
|
76
|
+
# Auto where it is the only mode that does what was asked: a condition is checked only on an auto
|
|
77
|
+
# transition, and a machine leaves Start only through one. Written as the default enabled, both
|
|
78
|
+
# did nothing, and the machine sat in Start with its conditions true.
|
|
79
|
+
var auto: bool = not advance_condition.is_empty() or from_state == "Start"
|
|
80
|
+
var advance_mode: String = str(args.get("advanceMode", "auto" if auto else "enabled"))
|
|
81
|
+
|
|
82
|
+
if from_state.is_empty() or to_state.is_empty():
|
|
83
|
+
return {"ok": false, "error": "Missing fromState or toState"}
|
|
84
|
+
var advances: Dictionary = {
|
|
85
|
+
"auto": AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO,
|
|
86
|
+
"enabled": AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED,
|
|
87
|
+
"disabled": AnimationNodeStateMachineTransition.ADVANCE_MODE_DISABLED,
|
|
88
|
+
}
|
|
89
|
+
if not advances.has(advance_mode):
|
|
90
|
+
return {
|
|
91
|
+
"ok": false,
|
|
92
|
+
"error": "Unsupported advanceMode: %s; it is auto, enabled or disabled" % advance_mode
|
|
93
|
+
}
|
|
94
|
+
if not advance_condition.is_empty() and advance_mode != "auto":
|
|
95
|
+
return {
|
|
96
|
+
"ok": false,
|
|
97
|
+
"error":
|
|
98
|
+
(
|
|
99
|
+
(
|
|
100
|
+
"advanceCondition %s would never advance a transition whose advanceMode is %s: the engine"
|
|
101
|
+
+ " checks a condition only on an auto one. Leave advanceMode out, or give auto"
|
|
102
|
+
)
|
|
103
|
+
% [advance_condition, advance_mode]
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
var modes: Dictionary = {
|
|
107
|
+
"sync": AnimationNodeStateMachineTransition.SWITCH_MODE_SYNC,
|
|
108
|
+
"at_end": AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END,
|
|
109
|
+
"immediate": AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE,
|
|
110
|
+
}
|
|
111
|
+
if not modes.has(transition_type):
|
|
112
|
+
return {"ok": false, "error": "Unsupported transitionType: " + transition_type}
|
|
113
|
+
var transition: AnimationNodeStateMachineTransition = AnimationNodeStateMachineTransition.new()
|
|
114
|
+
transition.switch_mode = (
|
|
115
|
+
GdhRead.as_int(modes[transition_type]) as AnimationNodeStateMachineTransition.SwitchMode
|
|
116
|
+
)
|
|
117
|
+
transition.advance_mode = (
|
|
118
|
+
GdhRead.as_int(advances[advance_mode]) as AnimationNodeStateMachineTransition.AdvanceMode
|
|
119
|
+
)
|
|
120
|
+
if not advance_condition.is_empty():
|
|
121
|
+
transition.advance_condition = StringName(advance_condition)
|
|
122
|
+
|
|
123
|
+
var found: Array = _machine_at(args)
|
|
124
|
+
if found[1] == null:
|
|
125
|
+
return found[3]
|
|
126
|
+
var scene: GdhSceneFile = found[0]
|
|
127
|
+
var sm: AnimationNodeStateMachine = found[1]
|
|
128
|
+
var problem: String = _transition_problem(sm, from_state, to_state)
|
|
129
|
+
if not problem.is_empty():
|
|
130
|
+
return scene.refuse(problem)
|
|
131
|
+
|
|
132
|
+
sm.add_transition(StringName(from_state), StringName(to_state), transition)
|
|
133
|
+
if not sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
134
|
+
return scene.refuse("The state machine did not take the transition.")
|
|
135
|
+
var files: Dictionary = found[2]
|
|
136
|
+
return _finish(
|
|
137
|
+
scene,
|
|
138
|
+
Callable(),
|
|
139
|
+
files,
|
|
140
|
+
{"ok": true, "from": from_state, "to": to_state, "advanceMode": advance_mode}
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## Why [param sm] cannot take a transition from [param from_state] to [param to_state], or "".
|
|
145
|
+
## Each is a condition add_transition fails on with only a line in the editor's log, and the
|
|
146
|
+
## transition was answered as made.
|
|
147
|
+
func _transition_problem(sm: AnimationNodeStateMachine, from_state: String, to_state: String) -> String:
|
|
148
|
+
for state: String in [from_state, to_state]:
|
|
149
|
+
if not sm.has_node(StringName(state)):
|
|
150
|
+
return _no_state(sm, state)
|
|
151
|
+
if from_state == to_state:
|
|
152
|
+
return "A transition needs two different states."
|
|
153
|
+
if from_state == "End":
|
|
154
|
+
return "No transition can leave End."
|
|
155
|
+
if to_state == "Start":
|
|
156
|
+
return "No transition can arrive at Start."
|
|
157
|
+
if sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
158
|
+
return "There is already a transition from %s to %s." % [from_state, to_state]
|
|
159
|
+
return ""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## The refusal for [param state_name], which [param sm] has no state of: with the nearest names it
|
|
163
|
+
## has, or else every state, since a machine has a handful.
|
|
164
|
+
static func _no_state(sm: AnimationNodeStateMachine, state_name: String) -> String:
|
|
165
|
+
var said: String = "The state machine has no state named %s" % state_name
|
|
166
|
+
var states: PackedStringArray = []
|
|
167
|
+
for state: StringName in sm.get_node_list():
|
|
168
|
+
var _listed: bool = states.append(str(state))
|
|
169
|
+
var close: PackedStringArray = GdhRead.nearest(state_name, states)
|
|
170
|
+
if not close.is_empty():
|
|
171
|
+
return (
|
|
172
|
+
"%s; the nearest it has %s %s." % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
173
|
+
)
|
|
174
|
+
return "%s; its states are %s." % [said, ", ".join(states)]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
## The state machine [param args] names, as [scene, machine, files, refusal, tree]: the machine is
|
|
178
|
+
## null and the refusal says why when the scene, the tree or the machine is not there.
|
|
179
|
+
func _machine_at(args: Dictionary) -> Array:
|
|
180
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
181
|
+
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
182
|
+
if scene_path.strip_edges() == "res://":
|
|
183
|
+
return [null, null, {}, {"ok": false, "error": "Missing scenePath"}]
|
|
184
|
+
if anim_tree_path.is_empty():
|
|
185
|
+
return [null, null, {}, {"ok": false, "error": "Missing animTreePath"}]
|
|
186
|
+
var opened: Array = _open_at(scene_path, anim_tree_path, "AnimationTree")
|
|
187
|
+
var scene: GdhSceneFile = opened[0]
|
|
188
|
+
if opened[1] == null:
|
|
189
|
+
return [scene, null, {}, opened[2]]
|
|
190
|
+
var anim_tree: AnimationTree = opened[1]
|
|
191
|
+
var machine: Array = _machine(scene, anim_tree, str(args.get("stateMachinePath", "")))
|
|
192
|
+
if machine[0] == null:
|
|
193
|
+
return [scene, null, {}, scene.refuse(str(machine[2]))]
|
|
194
|
+
return [scene, machine[0], machine[1], {}, anim_tree]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# =============================================================================
|
|
198
|
+
# remove_animation_state, disconnect_animation_states
|
|
199
|
+
# =============================================================================
|
|
200
|
+
## Takes a state out of its state machine with every transition leaving or reaching it, which the
|
|
201
|
+
## engine drops with the state, and names those under transitionsRemoved. Start and End are part of
|
|
202
|
+
## every state machine.
|
|
203
|
+
func remove_animation_state(args: Dictionary) -> Dictionary:
|
|
204
|
+
var state_name: String = str(args.get("stateName", ""))
|
|
205
|
+
if state_name.is_empty():
|
|
206
|
+
return {"ok": false, "error": "Missing stateName"}
|
|
207
|
+
var found: Array = _machine_at(args)
|
|
208
|
+
if found[1] == null:
|
|
209
|
+
return found[3]
|
|
210
|
+
var scene: GdhSceneFile = found[0]
|
|
211
|
+
var sm: AnimationNodeStateMachine = found[1]
|
|
212
|
+
if state_name in ["Start", "End"]:
|
|
213
|
+
return scene.refuse("%s is part of every state machine, so it cannot be removed." % state_name)
|
|
214
|
+
if not sm.has_node(StringName(state_name)):
|
|
215
|
+
return scene.refuse(_no_state(sm, state_name))
|
|
216
|
+
var dropped: Array = []
|
|
217
|
+
for index: int in sm.get_transition_count():
|
|
218
|
+
var from: String = str(sm.get_transition_from(index))
|
|
219
|
+
var to: String = str(sm.get_transition_to(index))
|
|
220
|
+
if state_name in [from, to]:
|
|
221
|
+
dropped.append({"from": from, "to": to})
|
|
222
|
+
sm.remove_node(StringName(state_name))
|
|
223
|
+
if sm.has_node(StringName(state_name)):
|
|
224
|
+
return scene.refuse("The state machine did not let go of %s." % state_name)
|
|
225
|
+
for index: int in sm.get_transition_count():
|
|
226
|
+
if state_name in [str(sm.get_transition_from(index)), str(sm.get_transition_to(index))]:
|
|
227
|
+
return scene.refuse(
|
|
228
|
+
"The state machine kept a transition of %s after the state went." % state_name
|
|
229
|
+
)
|
|
230
|
+
var files: Dictionary = found[2]
|
|
231
|
+
return _finish(
|
|
232
|
+
scene, Callable(), files, {"ok": true, "removed": state_name, "transitionsRemoved": dropped}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
## Takes out the transition from one state to another.
|
|
237
|
+
func disconnect_animation_states(args: Dictionary) -> Dictionary:
|
|
238
|
+
var from_state: String = str(args.get("fromState", ""))
|
|
239
|
+
var to_state: String = str(args.get("toState", ""))
|
|
240
|
+
if from_state.is_empty() or to_state.is_empty():
|
|
241
|
+
return {"ok": false, "error": "Missing fromState or toState"}
|
|
242
|
+
var found: Array = _machine_at(args)
|
|
243
|
+
if found[1] == null:
|
|
244
|
+
return found[3]
|
|
245
|
+
var scene: GdhSceneFile = found[0]
|
|
246
|
+
var sm: AnimationNodeStateMachine = found[1]
|
|
247
|
+
for state: String in [from_state, to_state]:
|
|
248
|
+
if not sm.has_node(StringName(state)):
|
|
249
|
+
return scene.refuse(_no_state(sm, state))
|
|
250
|
+
if not sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
251
|
+
var leaving: PackedStringArray = []
|
|
252
|
+
for index: int in sm.get_transition_count():
|
|
253
|
+
if str(sm.get_transition_from(index)) == from_state:
|
|
254
|
+
var _listed: bool = leaving.append(str(sm.get_transition_to(index)))
|
|
255
|
+
var said: String = "There is no transition from %s to %s" % [from_state, to_state]
|
|
256
|
+
if leaving.is_empty():
|
|
257
|
+
return scene.refuse("%s; none leaves %s." % [said, from_state])
|
|
258
|
+
return scene.refuse("%s; the ones leaving %s go to %s." % [said, from_state, ", ".join(leaving)])
|
|
259
|
+
sm.remove_transition(StringName(from_state), StringName(to_state))
|
|
260
|
+
if sm.has_transition(StringName(from_state), StringName(to_state)):
|
|
261
|
+
return scene.refuse("The state machine did not let go of the transition.")
|
|
262
|
+
var files: Dictionary = found[2]
|
|
263
|
+
return _finish(scene, Callable(), files, {"ok": true, "removed": {"from": from_state, "to": to_state}})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://bu6foylc4333x
|