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
|
@@ -5,6 +5,7 @@ extends RefCounted
|
|
|
5
5
|
## stores, which runs no script of the scene's, so a read answers the same either way.
|
|
6
6
|
|
|
7
7
|
const GdhFileReads = preload("file_reads.gd")
|
|
8
|
+
const GdhRead = preload("reading.gd")
|
|
8
9
|
const GdhSerialisation = preload("serialisation.gd")
|
|
9
10
|
|
|
10
11
|
const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
@@ -28,6 +29,11 @@ const SWITCH_MODE_NAMES: Dictionary[int, String] = {
|
|
|
28
29
|
AnimationNodeStateMachineTransition.SWITCH_MODE_SYNC: "sync",
|
|
29
30
|
AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END: "at_end",
|
|
30
31
|
}
|
|
32
|
+
const ADVANCE_MODE_NAMES: Dictionary[int, String] = {
|
|
33
|
+
AnimationNodeStateMachineTransition.ADVANCE_MODE_DISABLED: "disabled",
|
|
34
|
+
AnimationNodeStateMachineTransition.ADVANCE_MODE_ENABLED: "enabled",
|
|
35
|
+
AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO: "auto",
|
|
36
|
+
}
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
## [method animations] of the AnimationPlayer at [param player_path] in the file [param scene_path].
|
|
@@ -47,8 +53,21 @@ static func scene_states(scene_path: String, tree_path: String, state_machine_pa
|
|
|
47
53
|
if made[0] == null:
|
|
48
54
|
return made[1]
|
|
49
55
|
var tree: AnimationTree = made[0]
|
|
50
|
-
|
|
56
|
+
# The player the tree plays from, read from the file the same way, since a bare tree has no
|
|
57
|
+
# siblings to find it among.
|
|
58
|
+
# A path above the root names no node the file has, and finds none, as the editor finds none.
|
|
59
|
+
var player: AnimationPlayer = null
|
|
60
|
+
var player_path: String = ""
|
|
61
|
+
if not str(tree.anim_player).is_empty():
|
|
62
|
+
player_path = (tree_path + "/" + str(tree.anim_player)).simplify_path()
|
|
63
|
+
if player_path.is_empty():
|
|
64
|
+
player_path = "."
|
|
65
|
+
var found: Array = _bare(scene_path, player_path, "AnimationPlayer")
|
|
66
|
+
player = found[0]
|
|
67
|
+
var answer: Dictionary = states(tree, state_machine_path, player, player_path)
|
|
51
68
|
tree.free()
|
|
69
|
+
if player != null:
|
|
70
|
+
player.free()
|
|
52
71
|
return answer
|
|
53
72
|
|
|
54
73
|
|
|
@@ -200,8 +219,28 @@ static func no_machine(tree: AnimationTree, state_machine_path: String) -> Strin
|
|
|
200
219
|
return "No state machine at " + state_machine_path
|
|
201
220
|
|
|
202
221
|
|
|
222
|
+
## Why a state playing [param animation_name] from [param player], at [param player_path] from the
|
|
223
|
+
## scene root as playerNodePath takes it, plays nothing yet. anim_player is relative to the tree
|
|
224
|
+
## ("../Anim"), which no argument takes.
|
|
225
|
+
static func missing_animation(player: AnimationPlayer, player_path: String, animation_name: String) -> String:
|
|
226
|
+
var said: String = (
|
|
227
|
+
"The AnimationPlayer at %s has no animation %s yet, so the state plays nothing until it has one."
|
|
228
|
+
% [player_path, animation_name]
|
|
229
|
+
)
|
|
230
|
+
var close: PackedStringArray = GdhRead.nearest(animation_name, player.get_animation_list())
|
|
231
|
+
if close.is_empty():
|
|
232
|
+
return said
|
|
233
|
+
return (
|
|
234
|
+
said + " The nearest %s %s." % ["it has is" if close.size() == 1 else "it has are", ", ".join(close)]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
203
238
|
## The states and transitions of the state machine at [param state_machine_path] in [param tree].
|
|
204
|
-
|
|
239
|
+
## A state playing an animation says whether [param player], at [param player_path], has it, as
|
|
240
|
+
## add_state answers: a state naming one it lacks plays nothing, and says so only in a log.
|
|
241
|
+
static func states(
|
|
242
|
+
tree: AnimationTree, state_machine_path: String, player: AnimationPlayer, player_path: String
|
|
243
|
+
) -> Dictionary:
|
|
205
244
|
var sm: AnimationNodeStateMachine = state_machine(tree, state_machine_path)
|
|
206
245
|
if sm == null:
|
|
207
246
|
return {"ok": false, "error": no_machine(tree, state_machine_path)}
|
|
@@ -212,6 +251,10 @@ static func states(tree: AnimationTree, state_machine_path: String) -> Dictionar
|
|
|
212
251
|
if node is AnimationNodeAnimation:
|
|
213
252
|
var plays: AnimationNodeAnimation = node
|
|
214
253
|
entry["animationName"] = str(plays.animation)
|
|
254
|
+
var found: bool = player != null and player.has_animation(plays.animation)
|
|
255
|
+
entry["animationFound"] = found
|
|
256
|
+
if player != null and not found:
|
|
257
|
+
entry["animationMissing"] = missing_animation(player, player_path, str(plays.animation))
|
|
215
258
|
listed.append(entry)
|
|
216
259
|
var transitions: Array[Dictionary] = []
|
|
217
260
|
for index: int in sm.get_transition_count():
|
|
@@ -220,8 +263,76 @@ static func states(tree: AnimationTree, state_machine_path: String) -> Dictionar
|
|
|
220
263
|
"from": str(sm.get_transition_from(index)),
|
|
221
264
|
"to": str(sm.get_transition_to(index)),
|
|
222
265
|
"transitionType": SWITCH_MODE_NAMES.get(transition.switch_mode, str(transition.switch_mode)),
|
|
266
|
+
"advanceMode": ADVANCE_MODE_NAMES.get(transition.advance_mode, str(transition.advance_mode)),
|
|
223
267
|
}
|
|
224
|
-
|
|
225
|
-
|
|
268
|
+
var condition: String = str(transition.advance_condition)
|
|
269
|
+
if not condition.is_empty():
|
|
270
|
+
shaped["advanceCondition"] = condition
|
|
271
|
+
# A condition is checked only on an auto transition, and the default is enabled: a machine
|
|
272
|
+
# with its conditions set true stayed where it was, and nothing said why.
|
|
273
|
+
if not condition.is_empty() and transition.advance_mode != transition.ADVANCE_MODE_AUTO:
|
|
274
|
+
shaped["conditionNote"] = (
|
|
275
|
+
(
|
|
276
|
+
"advanceMode is %s, so %s never advances it: the engine checks a condition only on an"
|
|
277
|
+
+ " auto transition%s. disconnect_states and connect_states again with the condition"
|
|
278
|
+
+ " write it as auto"
|
|
279
|
+
)
|
|
280
|
+
% [
|
|
281
|
+
shaped["advanceMode"],
|
|
282
|
+
condition,
|
|
283
|
+
(
|
|
284
|
+
", and travel() still takes this one"
|
|
285
|
+
if transition.advance_mode == transition.ADVANCE_MODE_ENABLED
|
|
286
|
+
else ""
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
)
|
|
226
290
|
transitions.append(shaped)
|
|
227
291
|
return {"ok": true, "states": listed, "transitions": transitions}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
## The states of the AnimationTrees under [param root] that play [param animation_name] from
|
|
295
|
+
## [param player], as [[node, {animTreePath, state}]], a state named by the path of the nodes leading
|
|
296
|
+
## to it.
|
|
297
|
+
static func states_playing(root: Node, player: AnimationPlayer, animation_name: String) -> Array:
|
|
298
|
+
var found: Array = []
|
|
299
|
+
var trees: Array[Node] = root.find_children("*", "AnimationTree", true, false)
|
|
300
|
+
if root is AnimationTree:
|
|
301
|
+
trees.append(root)
|
|
302
|
+
for node: Node in trees:
|
|
303
|
+
var tree: AnimationTree = node
|
|
304
|
+
if tree.tree_root == null or tree.get_node_or_null(tree.anim_player) != player:
|
|
305
|
+
continue
|
|
306
|
+
var tree_path: String = str(root.get_path_to(tree))
|
|
307
|
+
_collect_playing(tree.tree_root, "", tree_path, animation_name, found)
|
|
308
|
+
return found
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
static func _collect_playing(
|
|
312
|
+
node: AnimationNode, at: String, tree_path: String, animation_name: String, found: Array
|
|
313
|
+
) -> void:
|
|
314
|
+
if node is AnimationNodeAnimation:
|
|
315
|
+
var plays: AnimationNodeAnimation = node
|
|
316
|
+
if plays.animation == StringName(animation_name):
|
|
317
|
+
found.append([plays, {"animTreePath": tree_path, "state": "." if at.is_empty() else at}])
|
|
318
|
+
return
|
|
319
|
+
var children: Dictionary = {}
|
|
320
|
+
if node is AnimationNodeStateMachine:
|
|
321
|
+
var machine: AnimationNodeStateMachine = node
|
|
322
|
+
for state: StringName in machine.get_node_list():
|
|
323
|
+
children[str(state)] = machine.get_node(state)
|
|
324
|
+
elif node is AnimationNodeBlendTree:
|
|
325
|
+
var blend: AnimationNodeBlendTree = node
|
|
326
|
+
for input: StringName in blend.get_node_list():
|
|
327
|
+
children[str(input)] = blend.get_node(input)
|
|
328
|
+
elif node is AnimationNodeBlendSpace1D:
|
|
329
|
+
var line: AnimationNodeBlendSpace1D = node
|
|
330
|
+
for point: int in line.get_blend_point_count():
|
|
331
|
+
children[str(point)] = line.get_blend_point_node(point)
|
|
332
|
+
elif node is AnimationNodeBlendSpace2D:
|
|
333
|
+
var plane: AnimationNodeBlendSpace2D = node
|
|
334
|
+
for point: int in plane.get_blend_point_count():
|
|
335
|
+
children[str(point)] = plane.get_blend_point_node(point)
|
|
336
|
+
for step: String in children:
|
|
337
|
+
var child: AnimationNode = children[step]
|
|
338
|
+
_collect_playing(child, step if at.is_empty() else at + "/" + step, tree_path, animation_name, found)
|
|
@@ -19,7 +19,7 @@ func create_audio_bus(params: Dictionary) -> Dictionary:
|
|
|
19
19
|
return _log.failure("bus_name is required")
|
|
20
20
|
var parent_idx: int = GdhRead.as_int(params.get("parent_bus_index", 0))
|
|
21
21
|
if parent_idx < 0 or parent_idx >= AudioServer.bus_count:
|
|
22
|
-
return _log.failure(
|
|
22
|
+
return _log.failure(_no_index(parent_idx))
|
|
23
23
|
# Sends are by name, so a second bus with one would leave every send to it meaning either.
|
|
24
24
|
var existing: int = AudioServer.get_bus_index(bus_name)
|
|
25
25
|
if existing != -1:
|
|
@@ -50,10 +50,7 @@ func remove_audio_bus(params: Dictionary) -> Dictionary:
|
|
|
50
50
|
return _log.failure("bus_name is required")
|
|
51
51
|
var index: int = AudioServer.get_bus_index(bus_name)
|
|
52
52
|
if index == -1:
|
|
53
|
-
|
|
54
|
-
for i: int in range(AudioServer.bus_count):
|
|
55
|
-
names.append(AudioServer.get_bus_name(i))
|
|
56
|
-
return _log.failure("No bus named %s. The buses are %s." % [bus_name, ", ".join(names)])
|
|
53
|
+
return _log.failure("No bus named %s. The buses are %s." % [bus_name, ", ".join(_names())])
|
|
57
54
|
if index == 0:
|
|
58
55
|
return _log.failure("%s is the bus every other ends in, so it cannot be removed." % bus_name)
|
|
59
56
|
|
|
@@ -105,7 +102,7 @@ func set_audio_bus_effect(params: Dictionary) -> Dictionary:
|
|
|
105
102
|
var effect_type: String = str(params.get("effect_type", ""))
|
|
106
103
|
var enabled: bool = GdhRead.as_bool(params.get("enabled", true), true)
|
|
107
104
|
if bus_idx < 0 or bus_idx >= AudioServer.bus_count:
|
|
108
|
-
return _log.failure(
|
|
105
|
+
return _log.failure(_no_index(bus_idx))
|
|
109
106
|
|
|
110
107
|
var effect: AudioEffect = _effect_named(effect_type)
|
|
111
108
|
if effect == null:
|
|
@@ -153,7 +150,7 @@ func set_audio_bus_volume(params: Dictionary) -> Dictionary:
|
|
|
153
150
|
var bus_idx: int = GdhRead.as_int(params.get("bus_index", 0))
|
|
154
151
|
var volume_db: float = GdhRead.as_float(params.get("volume_db", 0.0))
|
|
155
152
|
if bus_idx < 0 or bus_idx >= AudioServer.bus_count:
|
|
156
|
-
return _log.failure(
|
|
153
|
+
return _log.failure(_no_index(bus_idx))
|
|
157
154
|
|
|
158
155
|
AudioServer.set_bus_volume_db(bus_idx, volume_db)
|
|
159
156
|
|
|
@@ -163,6 +160,95 @@ func set_audio_bus_volume(params: Dictionary) -> Dictionary:
|
|
|
163
160
|
return {"bus": _bus(bus_idx), "layout": layout}
|
|
164
161
|
|
|
165
162
|
|
|
163
|
+
## Renames a bus, and every send to it with it: the engine renames the one bus and leaves a bus that
|
|
164
|
+
## sent to it sending to a name nothing has, which it reads as Master. The scenes naming it are the
|
|
165
|
+
## server's to rewrite, since they are files rather than anything this process holds.
|
|
166
|
+
func rename_audio_bus(params: Dictionary) -> Dictionary:
|
|
167
|
+
var bus_name: String = str(params.get("bus_name", ""))
|
|
168
|
+
var new_name: String = str(params.get("new_bus_name", ""))
|
|
169
|
+
if bus_name.is_empty() or new_name.is_empty():
|
|
170
|
+
return _log.failure("bus_name and new_bus_name are required")
|
|
171
|
+
var index: int = AudioServer.get_bus_index(bus_name)
|
|
172
|
+
if index == -1:
|
|
173
|
+
return _log.failure("No bus named %s. The buses are %s." % [bus_name, ", ".join(_names())])
|
|
174
|
+
if index == 0:
|
|
175
|
+
return _log.failure(
|
|
176
|
+
"%s is the bus every other ends in, which the editor keeps under its name." % bus_name
|
|
177
|
+
)
|
|
178
|
+
var existing: int = AudioServer.get_bus_index(new_name)
|
|
179
|
+
if existing != -1:
|
|
180
|
+
return _log.failure("A bus named " + new_name + " already exists, at index " + str(existing))
|
|
181
|
+
AudioServer.set_bus_name(index, new_name)
|
|
182
|
+
var resent: Array[String] = []
|
|
183
|
+
for i: int in range(AudioServer.bus_count):
|
|
184
|
+
if AudioServer.get_bus_send(i) == bus_name:
|
|
185
|
+
AudioServer.set_bus_send(i, new_name)
|
|
186
|
+
resent.append(AudioServer.get_bus_name(i))
|
|
187
|
+
|
|
188
|
+
var layout: String = _save_layout()
|
|
189
|
+
if layout.is_empty():
|
|
190
|
+
return {}
|
|
191
|
+
var answer: Dictionary = {
|
|
192
|
+
"renamed": {"from": bus_name, "to": new_name}, "bus": _bus(index), "layout": layout
|
|
193
|
+
}
|
|
194
|
+
if not resent.is_empty():
|
|
195
|
+
answer["resent"] = resent
|
|
196
|
+
return answer
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
## Points a bus's send at another bus. Only one before it: the editor offers no other, and the
|
|
200
|
+
## engine takes any name, a later bus, the bus itself or one that does not exist, without a word.
|
|
201
|
+
func set_audio_bus_send(params: Dictionary) -> Dictionary:
|
|
202
|
+
var bus_idx: int = GdhRead.as_int(params.get("bus_index", 0))
|
|
203
|
+
var send: String = str(params.get("send_to", ""))
|
|
204
|
+
if bus_idx < 0 or bus_idx >= AudioServer.bus_count:
|
|
205
|
+
return _log.failure(_no_index(bus_idx))
|
|
206
|
+
var bus_name: String = AudioServer.get_bus_name(bus_idx)
|
|
207
|
+
if bus_idx == 0:
|
|
208
|
+
return _log.failure("%s sends nowhere: it is the bus every other ends in." % bus_name)
|
|
209
|
+
var target: int = AudioServer.get_bus_index(send)
|
|
210
|
+
if target == -1:
|
|
211
|
+
return _log.failure("No bus named %s. The buses are %s." % [send, ", ".join(_names())])
|
|
212
|
+
if target == bus_idx:
|
|
213
|
+
return _log.failure(
|
|
214
|
+
"%s cannot send to itself; it can send to %s." % [bus_name, ", ".join(_names().slice(0, bus_idx))]
|
|
215
|
+
)
|
|
216
|
+
if target > bus_idx:
|
|
217
|
+
return (
|
|
218
|
+
_log
|
|
219
|
+
. failure(
|
|
220
|
+
(
|
|
221
|
+
(
|
|
222
|
+
"%s is bus %d and %s is bus %d: a bus sends only to one before it, which keeps sound from "
|
|
223
|
+
+ "going round in a loop, so %s can send to %s."
|
|
224
|
+
)
|
|
225
|
+
% [bus_name, bus_idx, send, target, bus_name, ", ".join(_names().slice(0, bus_idx))]
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
AudioServer.set_bus_send(bus_idx, send)
|
|
230
|
+
|
|
231
|
+
var layout: String = _save_layout()
|
|
232
|
+
if layout.is_empty():
|
|
233
|
+
return {}
|
|
234
|
+
return {"bus": _bus(bus_idx), "layout": layout}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
func _names() -> Array[String]:
|
|
238
|
+
var names: Array[String] = []
|
|
239
|
+
for i: int in range(AudioServer.bus_count):
|
|
240
|
+
names.append(AudioServer.get_bus_name(i))
|
|
241
|
+
return names
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## The refusal for a bus index nothing is at, with each bus at its index, so the caller has one.
|
|
245
|
+
func _no_index(index: int) -> String:
|
|
246
|
+
var held: PackedStringArray = []
|
|
247
|
+
for i: int in range(AudioServer.bus_count):
|
|
248
|
+
var _added: bool = held.append("%d %s" % [i, AudioServer.get_bus_name(i)])
|
|
249
|
+
return "No bus at index %d; the buses are %s." % [index, ", ".join(held)]
|
|
250
|
+
|
|
251
|
+
|
|
166
252
|
func _bus(index: int) -> Dictionary:
|
|
167
253
|
var effects: Array[Dictionary] = []
|
|
168
254
|
for e: int in range(AudioServer.get_bus_effect_count(index)):
|
|
@@ -93,6 +93,19 @@ func query_classes(params: Dictionary) -> Dictionary:
|
|
|
93
93
|
|
|
94
94
|
filtered_classes.append(class_name_str)
|
|
95
95
|
|
|
96
|
+
var declared: Dictionary = _declared_classes(params)
|
|
97
|
+
var own: Array[Dictionary] = []
|
|
98
|
+
for name: String in _sorted_names(declared):
|
|
99
|
+
if instantiable_only and not _instantiable(name, declared):
|
|
100
|
+
continue
|
|
101
|
+
if not filter.is_empty() and not name.to_lower().contains(filter.to_lower()):
|
|
102
|
+
continue
|
|
103
|
+
if not bases.is_empty():
|
|
104
|
+
var engine_base: String = _engine_base_of(name, declared)
|
|
105
|
+
if engine_base.is_empty() or not _under_any(engine_base, bases):
|
|
106
|
+
continue
|
|
107
|
+
own.append(_project_entry(name, declared))
|
|
108
|
+
|
|
96
109
|
_log.info(
|
|
97
110
|
"Found " + str(filtered_classes.size()) + " classes (out of " + str(all_classes.size()) + " total)"
|
|
98
111
|
)
|
|
@@ -103,7 +116,8 @@ func query_classes(params: Dictionary) -> Dictionary:
|
|
|
103
116
|
"filter": filter,
|
|
104
117
|
"category": category,
|
|
105
118
|
"instantiable_only": instantiable_only,
|
|
106
|
-
"classes": filtered_classes
|
|
119
|
+
"classes": filtered_classes,
|
|
120
|
+
"project_classes": own,
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
|
|
@@ -194,6 +208,8 @@ static func _names(members: Array[Dictionary]) -> Array[String]:
|
|
|
194
208
|
return names
|
|
195
209
|
|
|
196
210
|
|
|
211
|
+
## What the project's scripts declare on disk, class to its path, what it extends and whether it is
|
|
212
|
+
## abstract, as the server read them.
|
|
197
213
|
static func _declared_classes(params: Dictionary) -> Dictionary:
|
|
198
214
|
var given: Variant = params.get("project_classes", {})
|
|
199
215
|
if given is Dictionary:
|
|
@@ -202,12 +218,75 @@ static func _declared_classes(params: Dictionary) -> Dictionary:
|
|
|
202
218
|
return {}
|
|
203
219
|
|
|
204
220
|
|
|
221
|
+
static func _entry_of(name: String, declared: Dictionary) -> Dictionary:
|
|
222
|
+
var entry: Variant = declared.get(name, {})
|
|
223
|
+
if entry is Dictionary:
|
|
224
|
+
var fields: Dictionary = entry
|
|
225
|
+
return fields
|
|
226
|
+
return {}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
static func _sorted_names(declared: Dictionary) -> Array[String]:
|
|
230
|
+
var names: Array[String] = []
|
|
231
|
+
for name: Variant in declared.keys():
|
|
232
|
+
names.append(str(name))
|
|
233
|
+
names.sort()
|
|
234
|
+
return names
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
## [param name]'s ancestors, nearest first: the project classes it extends, then the engine's. A
|
|
238
|
+
## base that is neither ends the line, as it is written.
|
|
239
|
+
static func _ancestors_of(name: String, declared: Dictionary) -> Array[String]:
|
|
240
|
+
var ancestors: Array[String] = []
|
|
241
|
+
var current: String = name
|
|
242
|
+
while declared.has(current):
|
|
243
|
+
var base: String = str(_entry_of(current, declared).get("extends", ""))
|
|
244
|
+
if base.is_empty() or base == name or ancestors.has(base):
|
|
245
|
+
return ancestors
|
|
246
|
+
ancestors.append(base)
|
|
247
|
+
current = base
|
|
248
|
+
while ClassDB.class_exists(current):
|
|
249
|
+
var parent: String = ClassDB.get_parent_class(current)
|
|
250
|
+
if parent.is_empty():
|
|
251
|
+
break
|
|
252
|
+
ancestors.append(parent)
|
|
253
|
+
current = parent
|
|
254
|
+
return ancestors
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
## The engine class [param name] is built on, or empty when its line reaches none.
|
|
258
|
+
static func _engine_base_of(name: String, declared: Dictionary) -> String:
|
|
259
|
+
if not declared.has(name):
|
|
260
|
+
return name if ClassDB.class_exists(name) else ""
|
|
261
|
+
for each: String in _ancestors_of(name, declared):
|
|
262
|
+
if ClassDB.class_exists(each):
|
|
263
|
+
return each
|
|
264
|
+
return ""
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
static func _instantiable(name: String, declared: Dictionary) -> bool:
|
|
268
|
+
if GdhRead.as_bool(_entry_of(name, declared).get("abstract", false)):
|
|
269
|
+
return false
|
|
270
|
+
var engine_base: String = _engine_base_of(name, declared)
|
|
271
|
+
return not engine_base.is_empty() and ClassDB.can_instantiate(engine_base)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
static func _project_entry(name: String, declared: Dictionary) -> Dictionary:
|
|
275
|
+
var entry: Dictionary = _entry_of(name, declared)
|
|
276
|
+
return {
|
|
277
|
+
"name": name,
|
|
278
|
+
"path": str(entry.get("path", "")),
|
|
279
|
+
"extends": str(entry.get("extends", "")),
|
|
280
|
+
"abstract": GdhRead.as_bool(entry.get("abstract", false)),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
205
284
|
## Why [param wanted] has no answer here. A class_name a project script declares is not in ClassDB,
|
|
206
285
|
## and "Class not found" for one read as though the project had no such class. [param declared] is
|
|
207
286
|
## what the project's scripts declare on disk, class to path, rather than the engine's list, which is
|
|
208
287
|
## the class cache: that lags the scripts, and a class written a moment ago was refused as declared
|
|
209
288
|
## nowhere.
|
|
210
|
-
func _not_found(wanted: String, declared: Dictionary) -> Dictionary:
|
|
289
|
+
func _not_found(wanted: String, declared: Dictionary, answers_own: bool = false) -> Dictionary:
|
|
211
290
|
if declared.has(wanted):
|
|
212
291
|
return _log.failure(
|
|
213
292
|
(
|
|
@@ -216,7 +295,7 @@ func _not_found(wanted: String, declared: Dictionary) -> Dictionary:
|
|
|
216
295
|
+ "the engine's ClassDB only. script_info structure on that script, with "
|
|
217
296
|
+ "includeInherited, answers what it declares and what it inherits."
|
|
218
297
|
)
|
|
219
|
-
% [wanted, str(declared
|
|
298
|
+
% [wanted, str(_entry_of(wanted, declared).get("path", ""))]
|
|
220
299
|
)
|
|
221
300
|
)
|
|
222
301
|
# The usual miss is a spelling, one letter off a class the engine has or one the project declares.
|
|
@@ -245,13 +324,18 @@ func _not_found(wanted: String, declared: Dictionary) -> Dictionary:
|
|
|
245
324
|
)
|
|
246
325
|
if not own.is_empty():
|
|
247
326
|
offered += (
|
|
248
|
-
" The nearest %s a project script declares %s %s
|
|
327
|
+
" The nearest %s a project script declares %s %s%s."
|
|
249
328
|
% [
|
|
250
329
|
"class" if own.size() == 1 else "classes",
|
|
251
330
|
"is" if own.size() == 1 else "are",
|
|
252
331
|
", ".join(own),
|
|
332
|
+
"" if answers_own else ", which script_info structure reads",
|
|
253
333
|
]
|
|
254
334
|
)
|
|
335
|
+
if answers_own:
|
|
336
|
+
return _log.failure(
|
|
337
|
+
"Class not found: %s.%s Neither the engine nor a project script declares it." % [wanted, offered]
|
|
338
|
+
)
|
|
255
339
|
return _log.failure(
|
|
256
340
|
(
|
|
257
341
|
(
|
|
@@ -340,8 +424,34 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
340
424
|
|
|
341
425
|
_log.info("Inspecting inheritance for: " + class_name_str)
|
|
342
426
|
|
|
343
|
-
|
|
344
|
-
|
|
427
|
+
var declared: Dictionary = _declared_classes(params)
|
|
428
|
+
var own_class: bool = declared.has(class_name_str)
|
|
429
|
+
if not own_class and not ClassDB.class_exists(class_name_str):
|
|
430
|
+
return _not_found(class_name_str, declared, true)
|
|
431
|
+
|
|
432
|
+
# A class a project script declares can stand only under another or under an engine class, so
|
|
433
|
+
# both answers carry the project's own beneath the class asked about.
|
|
434
|
+
var project_children: Array[Dictionary] = []
|
|
435
|
+
var project_descendants: Array[Dictionary] = []
|
|
436
|
+
for name: String in _sorted_names(declared):
|
|
437
|
+
var line: Array[String] = _ancestors_of(name, declared)
|
|
438
|
+
if name == class_name_str or not line.has(class_name_str):
|
|
439
|
+
continue
|
|
440
|
+
project_descendants.append(_project_entry(name, declared))
|
|
441
|
+
if line[0] == class_name_str:
|
|
442
|
+
project_children.append(_project_entry(name, declared))
|
|
443
|
+
if own_class:
|
|
444
|
+
var own_line: Array[String] = _ancestors_of(class_name_str, declared)
|
|
445
|
+
return {
|
|
446
|
+
"class_name": class_name_str,
|
|
447
|
+
"declared_in": str(_entry_of(class_name_str, declared).get("path", "")),
|
|
448
|
+
"parent_class": "" if own_line.is_empty() else own_line[0],
|
|
449
|
+
"ancestors": own_line,
|
|
450
|
+
"abstract": GdhRead.as_bool(_entry_of(class_name_str, declared).get("abstract", false)),
|
|
451
|
+
"can_instantiate": _instantiable(class_name_str, declared),
|
|
452
|
+
"project_children": project_children,
|
|
453
|
+
"project_descendants": project_descendants,
|
|
454
|
+
}
|
|
345
455
|
|
|
346
456
|
var ancestors: Array[String] = []
|
|
347
457
|
var current: String = class_name_str
|
|
@@ -386,7 +496,9 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
386
496
|
"direct_children": direct_children,
|
|
387
497
|
"all_descendants_count": all_descendants.size(),
|
|
388
498
|
"all_descendants": all_descendants,
|
|
389
|
-
"can_instantiate": ClassDB.can_instantiate(class_name_str)
|
|
499
|
+
"can_instantiate": ClassDB.can_instantiate(class_name_str),
|
|
500
|
+
"project_children": project_children,
|
|
501
|
+
"project_descendants": project_descendants,
|
|
390
502
|
}
|
|
391
503
|
|
|
392
504
|
|