gdharness 1.3.47 → 1.3.48
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 +159 -39
- package/build/godot/addons/gdharness_editor/animation_reads.gd +20 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +6 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +19 -0
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +9 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +7 -0
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +1 -1
- package/build/godot/operations/animation_reads.gd +20 -0
- package/build/godot/operations/dependencies.gd +65 -9
- package/build/godot/operations/file_reads.gd +6 -0
- package/build/godot/operations/import_pipeline.gd +10 -2
- package/build/godot/operations/project_config.gd +35 -0
- package/build/godot/operations/resource_files.gd +1 -2
- package/build/godot/tests/listed.gd +25 -0
- package/build/keeper.js +1308 -1026
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -12111,7 +12111,8 @@ class EngineSlots {
|
|
|
12111
12111
|
changes = new FolderChanges(this.directory);
|
|
12112
12112
|
continue;
|
|
12113
12113
|
}
|
|
12114
|
-
|
|
12114
|
+
const sayAt = waiting !== undefined ? said + WAIT_SAID_MS : this.say !== undefined && this.held.size === 0 ? Math.max(began + COMMAND_WAIT_SAID_MS, this.saidAt + WAIT_SAID_MS) : Number.POSITIVE_INFINITY;
|
|
12115
|
+
await changes.next(Math.max(0, Math.min(SLOT_LOOK_MS, sayAt - Date.now())), SLOT_SETTLE_MS, signal);
|
|
12115
12116
|
}
|
|
12116
12117
|
} finally {
|
|
12117
12118
|
changes?.close();
|
|
@@ -12169,11 +12170,17 @@ class EngineSlots {
|
|
|
12169
12170
|
}
|
|
12170
12171
|
release(lock) {
|
|
12171
12172
|
this.held.delete(lock);
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12173
|
+
giveBackSlot(lock, this.holder.pid);
|
|
12174
|
+
}
|
|
12175
|
+
forget(lock) {
|
|
12176
|
+
this.held.delete(lock);
|
|
12177
|
+
}
|
|
12178
|
+
hand(lock, to) {
|
|
12179
|
+
const handed = handSlot(lock, this.holder.pid, to);
|
|
12180
|
+
if (handed) {
|
|
12181
|
+
this.held.delete(lock);
|
|
12182
|
+
}
|
|
12183
|
+
return handed;
|
|
12177
12184
|
}
|
|
12178
12185
|
made(path) {
|
|
12179
12186
|
let made;
|
|
@@ -12240,6 +12247,22 @@ class EngineSlots {
|
|
|
12240
12247
|
this.starts.set(pid, { started, readAt: Date.now() });
|
|
12241
12248
|
}
|
|
12242
12249
|
}
|
|
12250
|
+
function handSlot(lock, from, to) {
|
|
12251
|
+
let content;
|
|
12252
|
+
try {
|
|
12253
|
+
content = readWhole(lock);
|
|
12254
|
+
} catch {
|
|
12255
|
+
return false;
|
|
12256
|
+
}
|
|
12257
|
+
return content !== null && holderOf(content)?.pid === from && writeWhole(lock, JSON.stringify(to));
|
|
12258
|
+
}
|
|
12259
|
+
function giveBackSlot(lock, pid) {
|
|
12260
|
+
try {
|
|
12261
|
+
if (holderOf(readFileSync8(lock, "utf8"))?.pid === pid) {
|
|
12262
|
+
removeWhole(lock);
|
|
12263
|
+
}
|
|
12264
|
+
} catch {}
|
|
12265
|
+
}
|
|
12243
12266
|
function sayWaitsTo(say) {
|
|
12244
12267
|
saidTo = say;
|
|
12245
12268
|
}
|
|
@@ -12308,8 +12331,14 @@ async function slotForCall() {
|
|
|
12308
12331
|
waits.said ??= (waitedMs) => slotWaitedSaid(waitedMs, slots.count, slots.directory, heldBy);
|
|
12309
12332
|
}
|
|
12310
12333
|
return {
|
|
12334
|
+
lock,
|
|
12335
|
+
from: slots.holder.pid,
|
|
12311
12336
|
release: () => {
|
|
12312
12337
|
slots.release(lock);
|
|
12338
|
+
},
|
|
12339
|
+
handTo: (holder) => slots.hand(lock, holder),
|
|
12340
|
+
leave: () => {
|
|
12341
|
+
slots.forget(lock);
|
|
12313
12342
|
}
|
|
12314
12343
|
};
|
|
12315
12344
|
}
|
|
@@ -12848,7 +12877,7 @@ class GameLog {
|
|
|
12848
12877
|
return;
|
|
12849
12878
|
}
|
|
12850
12879
|
const owner = this.lastHeadline[source];
|
|
12851
|
-
if (owner !== null && /^\s/.test(line)) {
|
|
12880
|
+
if (owner !== null && /^\s/.test(line) && !GDUNIT_CASE.test(line)) {
|
|
12852
12881
|
owner.detail.push(line.trim());
|
|
12853
12882
|
owner.continues = false;
|
|
12854
12883
|
return;
|
|
@@ -12925,10 +12954,11 @@ function answersTo(entry, severity, contains) {
|
|
|
12925
12954
|
const needle = contains.toLowerCase();
|
|
12926
12955
|
return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
|
|
12927
12956
|
}
|
|
12928
|
-
var HEADLINE, CRASH, CRASH_FILLER, COLOUR_CODE, STREAMS, ADMITTED, LOCATION2, FRAME, LONGEST_CYCLE = 64;
|
|
12957
|
+
var HEADLINE, CRASH, GDUNIT_CASE, CRASH_FILLER, COLOUR_CODE, STREAMS, ADMITTED, LOCATION2, FRAME, LONGEST_CYCLE = 64;
|
|
12929
12958
|
var init_game_log = __esm(() => {
|
|
12930
12959
|
HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
12931
12960
|
CRASH = /^(?:CrashHandlerException|handle_crash): Program crashed with signal \d+/;
|
|
12961
|
+
GDUNIT_CASE = /^\s+res:\/\/.+?\.(?:gd|cs) > .+? (?:STARTED|PASSED|FAILED|ERROR|SKIPPED|FLAKY)(?:\s|$)/;
|
|
12932
12962
|
CRASH_FILLER = /^(?:=+|-- END OF .* BACKTRACE --|\[\d+\] .* - no debug info in .*)$/;
|
|
12933
12963
|
COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
12934
12964
|
STREAMS = ["stdout", "stderr", "transcript"];
|
|
@@ -14225,7 +14255,7 @@ async function operationOutcome(engine, operation, params, projectPath) {
|
|
|
14225
14255
|
let ran;
|
|
14226
14256
|
let answer;
|
|
14227
14257
|
try {
|
|
14228
|
-
ran = await runEngine(engine.godotPath, args);
|
|
14258
|
+
ran = await runEngine(engine.godotPath, args, { timeout: engine.ceiling ?? OPERATION_CEILING });
|
|
14229
14259
|
answer = answerIn(answerFile);
|
|
14230
14260
|
} finally {
|
|
14231
14261
|
discard(paramsDir);
|
|
@@ -14315,7 +14345,7 @@ function answerIn(file) {
|
|
|
14315
14345
|
};
|
|
14316
14346
|
}
|
|
14317
14347
|
}
|
|
14318
|
-
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", ASIDE = "[gdharness] aside", RESUMED = "[gdharness] resumed", MESSAGES_KEPT = 200, ERRORS_NAMED = 3, NO_BLENDER = "Blender path is invalid or not set", BLENDER_STOPPED = "the import pass stopped at a .blend file and imported nothing: the engine imports .blend files through Blender, which the editor setting filesystem/import/blender/blender_path names, and here it names none. Set it in an editor, or set the project setting filesystem/import/blender/enabled to false with project_settings set, which has the engine leave .blend files unimported.";
|
|
14348
|
+
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", ASIDE = "[gdharness] aside", RESUMED = "[gdharness] resumed", MESSAGES_KEPT = 200, OPERATION_CEILING, ERRORS_NAMED = 3, NO_BLENDER = "Blender path is invalid or not set", BLENDER_STOPPED = "the import pass stopped at a .blend file and imported nothing: the engine imports .blend files through Blender, which the editor setting filesystem/import/blender/blender_path names, and here it names none. Set it in an editor, or set the project setting filesystem/import/blender/enabled to false with project_settings set, which has the engine leave .blend files unimported.";
|
|
14319
14349
|
var init_headless = __esm(() => {
|
|
14320
14350
|
init_doc_cache();
|
|
14321
14351
|
init_engine_leaks();
|
|
@@ -14326,6 +14356,7 @@ var init_headless = __esm(() => {
|
|
|
14326
14356
|
init_reimport();
|
|
14327
14357
|
init_scratch();
|
|
14328
14358
|
init_script_origins();
|
|
14359
|
+
OPERATION_CEILING = { ms: 600000, said: "ten minutes" };
|
|
14329
14360
|
});
|
|
14330
14361
|
|
|
14331
14362
|
// src/update-check.ts
|
|
@@ -16593,13 +16624,25 @@ var init_tool_definitions = __esm(() => {
|
|
|
16593
16624
|
],
|
|
16594
16625
|
description: "add_track makes property and method tracks; remove_track takes any type read answers."
|
|
16595
16626
|
},
|
|
16596
|
-
nodePath: {
|
|
16627
|
+
nodePath: {
|
|
16628
|
+
type: "string",
|
|
16629
|
+
description: "Target node, relative to the AnimationPlayer's root_node, which is the player's parent unless the scene sets it."
|
|
16630
|
+
},
|
|
16597
16631
|
property: { type: "string", description: "For property tracks: the property to animate." },
|
|
16598
16632
|
update: {
|
|
16599
16633
|
type: "string",
|
|
16600
16634
|
enum: ["continuous", "discrete", "capture"],
|
|
16601
16635
|
description: "For property tracks: how the value moves between keys. continuous blends toward the next key, so a bool or an int changes halfway to it; discrete holds each key's value until the next; capture blends from the value the property holds when playing starts. Without it a new track is made as the editor makes one: as the RESET animation's track for the same property, otherwise continuous for a float, vector, colour or transform and discrete for anything else. The answer gives the track's update."
|
|
16602
16636
|
},
|
|
16637
|
+
interpolation: {
|
|
16638
|
+
type: "string",
|
|
16639
|
+
enum: ["nearest", "linear", "cubic", "linear_angle", "cubic_angle"],
|
|
16640
|
+
description: "For property tracks: how a continuous track blends between keys. Without it a new track is made linear, or as the RESET track for the same property is, and an existing track keeps its own."
|
|
16641
|
+
},
|
|
16642
|
+
loopWrap: {
|
|
16643
|
+
type: "boolean",
|
|
16644
|
+
description: "For property tracks: whether a looping animation blends its last key into its first. Without it a new track wraps, or as the RESET track for the same property does, and an existing track keeps its own."
|
|
16645
|
+
},
|
|
16603
16646
|
method: {
|
|
16604
16647
|
type: "string",
|
|
16605
16648
|
description: "For method tracks: the method to call. A method track holds every method key for its node, so a key calling another method joins the node's track, with existingTrack true. The answer's methodFound says whether the target node has it; a method it lacks is still added, while a nodePath that reaches no node is refused, since it may be written next, and until it exists each call the track makes fails. A key whose args the method cannot take (too few or too many, or an object where it takes a value or the reverse) is written too, and answers argumentsFit false with misfit saying why. remove_track: only the keys calling this method."
|
|
@@ -16957,7 +17000,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16957
17000
|
direction: {
|
|
16958
17001
|
type: "string",
|
|
16959
17002
|
enum: ["forward", "reverse"],
|
|
16960
|
-
description: "forward: what this resource loads,
|
|
17003
|
+
description: "forward: what this resource loads or names. Each file named by its path carries kind, as reverse words it: ext_resource, extends, preload, load, include, or path for a path only written in a string, which the file itself may never load. Each reference closing a cycle is marked circular, with the cycle under circular_references. A script's dependencies include every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. A file's dependencies are listed at its first entry, and again further down only where the depth cut that list short and more is left; every other entry for a file naming any carries dependencies_listed_above. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
16961
17004
|
},
|
|
16962
17005
|
depth: {
|
|
16963
17006
|
type: "integer",
|
|
@@ -16992,7 +17035,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16992
17035
|
options: {
|
|
16993
17036
|
type: "object",
|
|
16994
17037
|
ops: ["set_options"],
|
|
16995
|
-
description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads. One already holding the value given is answered under unchanged_options rather than updated_options, and when none changed nothing is written or reimported. changed gives each option written as {was, now}
|
|
17038
|
+
description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads. One already holding the value given is answered under unchanged_options rather than updated_options, and when none changed nothing is written or reimported. changed gives each option written as {was, now}.`,
|
|
16996
17039
|
additionalProperties: true
|
|
16997
17040
|
},
|
|
16998
17041
|
reimport: {
|
|
@@ -17032,7 +17075,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17032
17075
|
},
|
|
17033
17076
|
uid: {
|
|
17034
17077
|
effect: "read",
|
|
17035
|
-
summary: "the UID of one file and where it was read: a script's or shader's .uid file, an imported file's .import, or a scene's or resource's own header",
|
|
17078
|
+
summary: "the UID of one file and where it was read: a script's or shader's .uid file, an imported file's .import, or a scene's or resource's own header. A file with none is answered with uid null and what would give it one",
|
|
17036
17079
|
requires: ["resourcePath"]
|
|
17037
17080
|
},
|
|
17038
17081
|
refresh_uids: {
|
|
@@ -17079,7 +17122,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17079
17122
|
type: "string",
|
|
17080
17123
|
ops: ["set_preset"],
|
|
17081
17124
|
enum: ["all_resources", "scenes", "resources", "exclude"],
|
|
17082
|
-
description: "set_preset: what the preset exports: every resource, the scenes listed in files and what they use, the resources listed, or every resource but those listed."
|
|
17125
|
+
description: "set_preset: what the preset exports: every resource, the scenes listed in files and what they use, the resources listed, or every resource but those listed. What a scene or a resource uses is what the engine counts as its dependencies, so a file named only in a string, such as a scene path held in an exported property, or a script reached only by its class_name, is left out of the export, though project_dependencies lists both."
|
|
17083
17126
|
},
|
|
17084
17127
|
files: {
|
|
17085
17128
|
type: "array",
|
|
@@ -17214,7 +17257,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17214
17257
|
name: "scene_tree",
|
|
17215
17258
|
title: "Scene tree",
|
|
17216
17259
|
effect: "read",
|
|
17217
|
-
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). The root of a scene inheriting another names the scene it inherits under inherits, and every node that scene or one further down makes names its file under inheritedFrom; this file holds only the values it changes on them. A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
17260
|
+
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. type is the engine class, and a node whose script declares a class_name names it under scriptClass, as scene_node get does. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). The root of a scene inheriting another names the scene it inherits under inherits, and every node that scene or one further down makes names its file under inheritedFrom; this file holds only the values it changes on them. A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
17218
17261
|
parameters: {
|
|
17219
17262
|
projectPath: PROJECT_PATH,
|
|
17220
17263
|
scenePath: SCENE_PATH,
|
|
@@ -18225,7 +18268,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
18225
18268
|
},
|
|
18226
18269
|
requires: [],
|
|
18227
18270
|
operations: {
|
|
18228
|
-
tree: {
|
|
18271
|
+
tree: {
|
|
18272
|
+
effect: "read",
|
|
18273
|
+
summary: "the live scene tree, with current_scene giving the path of the scene the game is showing and the file it was loaded from, or null when it shows none",
|
|
18274
|
+
requires: []
|
|
18275
|
+
},
|
|
18229
18276
|
text: {
|
|
18230
18277
|
effect: "read",
|
|
18231
18278
|
summary: "every line of text under nodePath, the values in its fields included, in reading order, leaving out what is hidden and everything under it, and a control drawn wholly outside the viewport or scrolled out of a ScrollContainer, which the player cannot read. Every line reads as drawn: rich text without tags, text only as far as it is typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items and the titles of its separators read a line each. A line drawn as a copy under a node saying the same words, as an outline or a glow is, reads once, counted under copies",
|
|
@@ -18635,7 +18682,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18635
18682
|
includeHidden: {
|
|
18636
18683
|
type: "boolean",
|
|
18637
18684
|
ops: ["until"],
|
|
18638
|
-
description: "until, with says: count words on a hidden node too, and on one drawn outside the viewport or scrolled out of a ScrollContainer. Default false, since a wait for words is a wait for them to be shown, and a wait that runs out while such a node holds them says so under unseen_note: a button that exists hidden through a whole animation carries its words the whole time and satisfied the wait at once, and every wait on that screen fell back to counting frames."
|
|
18685
|
+
description: "until, with says: count words on a hidden node too, and on one drawn outside the viewport or scrolled out of a ScrollContainer. Default false, since a wait for words is a wait for them to be shown, and a wait that runs out while such a node holds them says so under unseen_note: a button that exists hidden through a whole animation carries its words the whole time and satisfied the wait at once, and every wait on that screen fell back to counting frames. What is drawn over a node is not looked at, so words under a dialog or a panel count either way."
|
|
18639
18686
|
},
|
|
18640
18687
|
timeoutMs: {
|
|
18641
18688
|
type: "integer",
|
|
@@ -18711,7 +18758,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18711
18758
|
},
|
|
18712
18759
|
step_out: {
|
|
18713
18760
|
effect: "additive",
|
|
18714
|
-
summary: "run to the end of the current function and stop at the next line run at its caller's depth: in its caller, or, when the call was the last thing its caller did, wherever script runs next, such as the caller's next call; a game this server started
|
|
18761
|
+
summary: "run to the end of the current function and stop at the next line run at its caller's depth: in its caller, or, when the call was the last thing its caller did, wherever script runs next, such as the caller's next call; only for a game this server started",
|
|
18715
18762
|
requires: []
|
|
18716
18763
|
}
|
|
18717
18764
|
}
|
|
@@ -18729,7 +18776,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18729
18776
|
expression: {
|
|
18730
18777
|
type: "string",
|
|
18731
18778
|
ops: ["evaluate"],
|
|
18732
|
-
description: "evaluate: a GDScript expression, as Godot's Expression class parses it: members of self by name, locals, autoloads and global classes, operators, indexing and calls, such as wave.spawned[0] or Ledger.entries.size(). An expression that does not parse, or fails as it runs, comes to null, and the game
|
|
18779
|
+
description: "evaluate: a GDScript expression, as Godot's Expression class parses it: members of self by name, locals, autoloads and global classes, operators, indexing and calls, such as wave.spawned[0] or Ledger.entries.size(). An expression that does not parse, or fails as it runs, comes to null with failed saying why, as the engine words it, and a null with no failed is the value; for a game the editor plays, the reason reaches only the editor's Debugger panel, so a null there carries a note saying it may be either. The game reports what an expression raises while it is held inside a runtime_invoke call as raised by that call, and the call's answer says so under errorsNote."
|
|
18733
18780
|
}
|
|
18734
18781
|
},
|
|
18735
18782
|
requires: [],
|
|
@@ -42562,6 +42609,7 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
42562
42609
|
const frameId = typeof safeArgs.frameId === "number" ? safeArgs.frameId : undefined;
|
|
42563
42610
|
const expression = typeof safeArgs.expression === "string" ? safeArgs.expression : "";
|
|
42564
42611
|
const evaluated = await client.evaluate(expression, frameId);
|
|
42612
|
+
const nil = evaluated.type === "Nil" || evaluated.value === "null" || evaluated.value === "<null>";
|
|
42565
42613
|
return {
|
|
42566
42614
|
content: [
|
|
42567
42615
|
{
|
|
@@ -42570,7 +42618,10 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
42570
42618
|
frame: evaluated.frame,
|
|
42571
42619
|
expression,
|
|
42572
42620
|
value: evaluated.value,
|
|
42573
|
-
...evaluated.type === undefined ? {} : { type: evaluated.type }
|
|
42621
|
+
...evaluated.type === undefined ? {} : { type: evaluated.type },
|
|
42622
|
+
...nil ? {
|
|
42623
|
+
note: "null is also what an expression comes to when it does not parse or fails as it runs. For a game the editor plays, why reaches only the editor's Debugger panel, so this cannot say which it was."
|
|
42624
|
+
} : {}
|
|
42574
42625
|
})
|
|
42575
42626
|
}
|
|
42576
42627
|
]
|
|
@@ -49518,6 +49569,13 @@ function restartsOf(run) {
|
|
|
49518
49569
|
endedAt: new Date(one.endedAt).toISOString()
|
|
49519
49570
|
}));
|
|
49520
49571
|
}
|
|
49572
|
+
async function handToTheGame(slot, pid, projectPath) {
|
|
49573
|
+
const started = (await startTimesOf([pid])).get(pid);
|
|
49574
|
+
const by = `a game${projectPath === null ? "" : ` of ${projectPath}`} the editor played for editor_run`;
|
|
49575
|
+
if (started === undefined || !slot.handTo({ pid, started, by })) {
|
|
49576
|
+
slot.release();
|
|
49577
|
+
}
|
|
49578
|
+
}
|
|
49521
49579
|
function followTheRestart(run) {
|
|
49522
49580
|
if (run.throughEditor || run.pid === null || run.projectPath === null) {
|
|
49523
49581
|
return false;
|
|
@@ -50044,6 +50102,13 @@ function presetsIn(projectPath) {
|
|
|
50044
50102
|
function presetPlatform(projectPath, preset) {
|
|
50045
50103
|
return presetsIn(projectPath)?.find((held) => held.name === preset)?.platform ?? null;
|
|
50046
50104
|
}
|
|
50105
|
+
function engineEndSaid(ending) {
|
|
50106
|
+
if (ending.failure === null && ending.exitSignal === null) {
|
|
50107
|
+
return `the engine ended with ${exitCodeText(ending.exitCode)}`;
|
|
50108
|
+
}
|
|
50109
|
+
const how = howItEnded(ending);
|
|
50110
|
+
return how.startsWith("ended by") ? `the engine was ${how}` : how;
|
|
50111
|
+
}
|
|
50047
50112
|
function exportPresetRefusal(projectPath, preset) {
|
|
50048
50113
|
const presets = presetsIn(projectPath);
|
|
50049
50114
|
if (presets === null) {
|
|
@@ -50078,7 +50143,24 @@ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote =
|
|
|
50078
50143
|
if (verdict.exported) {
|
|
50079
50144
|
return { content: [{ type: "text", text: answerJson(verdict) }] };
|
|
50080
50145
|
}
|
|
50081
|
-
|
|
50146
|
+
if (written) {
|
|
50147
|
+
const errors = log.count("error");
|
|
50148
|
+
const because = [
|
|
50149
|
+
...ending.exitCode === 0 && ending.failure === null && ending.exitSignal === null ? [] : [engineEndSaid(ending)],
|
|
50150
|
+
...errors === 0 ? [] : [`it printed ${String(errors)} ${errors === 1 ? "error" : "errors"}, under entries`]
|
|
50151
|
+
];
|
|
50152
|
+
return {
|
|
50153
|
+
content: [
|
|
50154
|
+
{
|
|
50155
|
+
type: "text",
|
|
50156
|
+
text: `Export with preset '${asked.preset}' wrote ${asked.outputPath}, but it is not counted as exported, as ${because.join(" and ")}.`
|
|
50157
|
+
},
|
|
50158
|
+
{ type: "text", text: answerJson(verdict) }
|
|
50159
|
+
],
|
|
50160
|
+
isError: true
|
|
50161
|
+
};
|
|
50162
|
+
}
|
|
50163
|
+
const stale = after === null ? "" : ` The ${asked.outputPath} there is from before this export, which did not write it.`;
|
|
50082
50164
|
const why = ending.exitSignal === null ? stale : ` The engine was ${howItEnded(ending)}, so it has no exit code.${stale}`;
|
|
50083
50165
|
return {
|
|
50084
50166
|
content: [
|
|
@@ -50833,6 +50915,17 @@ function ownArguments(params) {
|
|
|
50833
50915
|
function needsItsOwnProcess(asked) {
|
|
50834
50916
|
return asked.savesIn !== undefined || asked.env !== undefined;
|
|
50835
50917
|
}
|
|
50918
|
+
function unheldNote(changed) {
|
|
50919
|
+
if (!isPlainObject3(changed)) {
|
|
50920
|
+
return "";
|
|
50921
|
+
}
|
|
50922
|
+
const unheld = Object.entries(changed).filter(([, change]) => isPlainObject3(change) && change["was"] === null).map(([name]) => name);
|
|
50923
|
+
if (unheld.length === 0) {
|
|
50924
|
+
return "";
|
|
50925
|
+
}
|
|
50926
|
+
const one = unheld.length === 1;
|
|
50927
|
+
return ` ${unheld.join(", ")} ${one ? "was" : "were"} not in the import file before, so only that reimport can tell whether the importer has ${one ? "it" : "them"}: one it lacks is dropped from the file then, and the reimport through set_options says which.`;
|
|
50928
|
+
}
|
|
50836
50929
|
|
|
50837
50930
|
class GodotServer {
|
|
50838
50931
|
stdio = null;
|
|
@@ -52464,7 +52557,7 @@ class GodotServer {
|
|
|
52464
52557
|
...written,
|
|
52465
52558
|
payload: {
|
|
52466
52559
|
...written.payload,
|
|
52467
|
-
note:
|
|
52560
|
+
note: `Not reimported: the editor imports it with these options on its next scan, or project_import reimport does now.${unheldNote(written.payload["changed"])}`
|
|
52468
52561
|
}
|
|
52469
52562
|
} : written);
|
|
52470
52563
|
}
|
|
@@ -55131,17 +55224,20 @@ ${error.text}`;
|
|
|
55131
55224
|
const sendingFrom = Date.now();
|
|
55132
55225
|
const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
|
|
55133
55226
|
this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
|
|
55227
|
+
const slot = await slotForCall();
|
|
55134
55228
|
if (profiled) {
|
|
55135
55229
|
const armed = await this.handleViaBridge("profile_start", {
|
|
55136
55230
|
arm: true,
|
|
55137
55231
|
frameFunctions: profileFrameFunctions(projectPath)
|
|
55138
55232
|
});
|
|
55139
55233
|
if (armed.isError === true) {
|
|
55234
|
+
slot.release();
|
|
55140
55235
|
return armed;
|
|
55141
55236
|
}
|
|
55142
55237
|
}
|
|
55143
55238
|
const answer = await this.handleViaBridge("play_scene", scene === null ? {} : { scenePath: `res://${scene}` });
|
|
55144
55239
|
if (answer.isError === true) {
|
|
55240
|
+
slot.release();
|
|
55145
55241
|
return answer;
|
|
55146
55242
|
}
|
|
55147
55243
|
const startedAt = Date.now();
|
|
@@ -55171,7 +55267,8 @@ ${error.text}`;
|
|
|
55171
55267
|
announcedBefore: alreadyPlaying,
|
|
55172
55268
|
seenPlaying: readBoolean(playAnswer, "playing") === true,
|
|
55173
55269
|
...which === null ? {} : { play: which },
|
|
55174
|
-
bootScene: runtimeWaitMs.scene
|
|
55270
|
+
bootScene: runtimeWaitMs.scene,
|
|
55271
|
+
slot
|
|
55175
55272
|
};
|
|
55176
55273
|
this.activeProcess = played;
|
|
55177
55274
|
this.noteTheLatestRun({ projectPath, pid: null, startedAt });
|
|
@@ -55187,6 +55284,11 @@ ${error.text}`;
|
|
|
55187
55284
|
sweepTranscripts();
|
|
55188
55285
|
step("sweep");
|
|
55189
55286
|
this.logDebug(`After the play: ${steps.join(", ")}`);
|
|
55287
|
+
const runtime = await this.runtimeUp(projectPath, alreadyPlaying, runtimeWaitMs, false);
|
|
55288
|
+
if (played.slot !== undefined) {
|
|
55289
|
+
played.slot.release();
|
|
55290
|
+
delete played.slot;
|
|
55291
|
+
}
|
|
55190
55292
|
return this.jsonTextResponse({
|
|
55191
55293
|
started: true,
|
|
55192
55294
|
through: "editor",
|
|
@@ -55203,7 +55305,7 @@ ${error.text}`;
|
|
|
55203
55305
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
55204
55306
|
reason: one.reason
|
|
55205
55307
|
})),
|
|
55206
|
-
runtime
|
|
55308
|
+
runtime,
|
|
55207
55309
|
message: "The editor is playing it, so its debugger holds it: the debug_* tools can reach it, " + "editor_output reads its console through the debug adapter and the runtime's report of it, and editor_run stop ends it." + endedToStartThis(ended)
|
|
55208
55310
|
});
|
|
55209
55311
|
}
|
|
@@ -55584,20 +55686,33 @@ ${error.text}`;
|
|
|
55584
55686
|
closeSync5(transcript.fd);
|
|
55585
55687
|
const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
|
|
55586
55688
|
const profile = profiled ? profilePath(startedAt) : undefined;
|
|
55587
|
-
const
|
|
55588
|
-
|
|
55589
|
-
|
|
55590
|
-
|
|
55591
|
-
|
|
55592
|
-
|
|
55593
|
-
|
|
55594
|
-
|
|
55595
|
-
|
|
55596
|
-
|
|
55597
|
-
|
|
55598
|
-
|
|
55599
|
-
|
|
55600
|
-
|
|
55689
|
+
const slot = await slotForCall();
|
|
55690
|
+
let launched;
|
|
55691
|
+
try {
|
|
55692
|
+
launched = await launchOutsideTheTree({
|
|
55693
|
+
command: godotPath,
|
|
55694
|
+
args: cmdArgs,
|
|
55695
|
+
...gameEnv === undefined ? {} : { env: gameEnv },
|
|
55696
|
+
run: {
|
|
55697
|
+
transcript: transcript.path,
|
|
55698
|
+
startedAt,
|
|
55699
|
+
projectPath,
|
|
55700
|
+
...this.servedBy(),
|
|
55701
|
+
...profile === undefined ? {} : { profile },
|
|
55702
|
+
...debugged ? { breakpoints: this.breakpointsForAGame(projectPath) } : { debugged: false },
|
|
55703
|
+
slot: { lock: slot.lock, from: slot.from, by: `a game of ${projectPath} editor_run started` }
|
|
55704
|
+
},
|
|
55705
|
+
...desktop === undefined ? {} : { desktop }
|
|
55706
|
+
});
|
|
55707
|
+
} catch (error) {
|
|
55708
|
+
slot.release();
|
|
55709
|
+
throw error;
|
|
55710
|
+
}
|
|
55711
|
+
if ("error" in launched) {
|
|
55712
|
+
slot.release();
|
|
55713
|
+
} else {
|
|
55714
|
+
slot.leave();
|
|
55715
|
+
}
|
|
55601
55716
|
if ("error" in launched) {
|
|
55602
55717
|
return launched;
|
|
55603
55718
|
}
|
|
@@ -55819,6 +55934,11 @@ ${error.text}`;
|
|
|
55819
55934
|
tie(run, endpoint) {
|
|
55820
55935
|
run.announcedPid = endpoint.pid;
|
|
55821
55936
|
this.noteBootOf(run, endpoint);
|
|
55937
|
+
const slot = run.slot;
|
|
55938
|
+
if (slot !== undefined) {
|
|
55939
|
+
delete run.slot;
|
|
55940
|
+
handToTheGame(slot, endpoint.pid, run.projectPath);
|
|
55941
|
+
}
|
|
55822
55942
|
}
|
|
55823
55943
|
playedByOurEditor(endpoint) {
|
|
55824
55944
|
const editor = this.godotBridge.getStatus().editorPid;
|
|
@@ -16,6 +16,22 @@ const UPDATE_MODE_NAMES: Dictionary[int, String] = {
|
|
|
16
16
|
Animation.UPDATE_DISCRETE: "discrete",
|
|
17
17
|
Animation.UPDATE_CAPTURE: "capture",
|
|
18
18
|
}
|
|
19
|
+
const INTERPOLATION_NAMES: Dictionary[int, String] = {
|
|
20
|
+
Animation.INTERPOLATION_NEAREST: "nearest",
|
|
21
|
+
Animation.INTERPOLATION_LINEAR: "linear",
|
|
22
|
+
Animation.INTERPOLATION_CUBIC: "cubic",
|
|
23
|
+
Animation.INTERPOLATION_LINEAR_ANGLE: "linear_angle",
|
|
24
|
+
Animation.INTERPOLATION_CUBIC_ANGLE: "cubic_angle",
|
|
25
|
+
}
|
|
26
|
+
## The track types whose keys are blended between, so how they are and whether a looping animation
|
|
27
|
+
## blends its last key into its first say how the track moves.
|
|
28
|
+
const INTERPOLATED_TYPES: Array[int] = [
|
|
29
|
+
Animation.TYPE_VALUE,
|
|
30
|
+
Animation.TYPE_POSITION_3D,
|
|
31
|
+
Animation.TYPE_ROTATION_3D,
|
|
32
|
+
Animation.TYPE_SCALE_3D,
|
|
33
|
+
Animation.TYPE_BLEND_SHAPE,
|
|
34
|
+
]
|
|
19
35
|
const TRACK_TYPE_NAMES: Dictionary[int, String] = {
|
|
20
36
|
Animation.TYPE_VALUE: "property",
|
|
21
37
|
Animation.TYPE_POSITION_3D: "position_3d",
|
|
@@ -174,6 +190,10 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
174
190
|
if kind == Animation.TYPE_VALUE:
|
|
175
191
|
var mode: int = anim.value_track_get_update_mode(track)
|
|
176
192
|
shaped["update"] = UPDATE_MODE_NAMES.get(mode, str(mode))
|
|
193
|
+
if kind in INTERPOLATED_TYPES:
|
|
194
|
+
var interpolation: int = anim.track_get_interpolation_type(track)
|
|
195
|
+
shaped["interpolation"] = INTERPOLATION_NAMES.get(interpolation, str(interpolation))
|
|
196
|
+
shaped["loopWrap"] = anim.track_get_interpolation_loop_wrap(track)
|
|
177
197
|
var keyframes: Array[Dictionary] = []
|
|
178
198
|
var methods: Dictionary = {}
|
|
179
199
|
for key: int in anim.track_get_key_count(track):
|
|
@@ -356,6 +356,12 @@ static func _entry(
|
|
|
356
356
|
var data: Dictionary = {
|
|
357
357
|
"name": node["name"], "type": node["type"], "path": path, "children": children,
|
|
358
358
|
}
|
|
359
|
+
var given_values: Dictionary = node["properties"]
|
|
360
|
+
var attached: Variant = given_values.get("script")
|
|
361
|
+
if attached is Script:
|
|
362
|
+
var script: Script = attached
|
|
363
|
+
if not str(script.get_global_name()).is_empty():
|
|
364
|
+
data["scriptClass"] = str(script.get_global_name())
|
|
359
365
|
# The root's file is the scene being read, so only another node's names a second scene.
|
|
360
366
|
if path != "." and node.has("instanceOf"):
|
|
361
367
|
data["instanceOf"] = node["instanceOf"]
|
|
@@ -109,6 +109,9 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
109
109
|
if anim.track_get_type(track_idx) == Animation.TYPE_VALUE:
|
|
110
110
|
var mode: int = anim.value_track_get_update_mode(track_idx)
|
|
111
111
|
answer["update"] = GdhAnimationReads.UPDATE_MODE_NAMES.get(mode, str(mode))
|
|
112
|
+
var blended: int = anim.track_get_interpolation_type(track_idx)
|
|
113
|
+
answer["interpolation"] = GdhAnimationReads.INTERPOLATION_NAMES.get(blended, str(blended))
|
|
114
|
+
answer["loopWrap"] = anim.track_get_interpolation_loop_wrap(track_idx)
|
|
112
115
|
if built[3]:
|
|
113
116
|
answer["existingTrack"] = true
|
|
114
117
|
var replaced: Array = built[5]
|
|
@@ -165,6 +168,7 @@ func _add_track(
|
|
|
165
168
|
var kind: Animation.TrackType = Animation.TYPE_VALUE
|
|
166
169
|
var path: NodePath = NodePath(target_path)
|
|
167
170
|
var update: int = -1
|
|
171
|
+
var interpolation: int = -1
|
|
168
172
|
if track_type == "property":
|
|
169
173
|
var prop_name: String = str(track.get("property", ""))
|
|
170
174
|
if prop_name.is_empty():
|
|
@@ -175,6 +179,11 @@ func _add_track(
|
|
|
175
179
|
)
|
|
176
180
|
if named != null:
|
|
177
181
|
update = GdhRead.as_int(named)
|
|
182
|
+
var blended: Variant = GdhAnimationReads.INTERPOLATION_NAMES.find_key(
|
|
183
|
+
str(track.get("interpolation", ""))
|
|
184
|
+
)
|
|
185
|
+
if blended != null:
|
|
186
|
+
interpolation = GdhRead.as_int(blended)
|
|
178
187
|
var values: Array = _key_values(target, prop_name, keyframes)
|
|
179
188
|
if not values[0]:
|
|
180
189
|
return _not_added(str(values[1]))
|
|
@@ -196,6 +205,12 @@ func _add_track(
|
|
|
196
205
|
misfit = _key_misfit(target, method_name, arguments, index)
|
|
197
206
|
method_found = target.has_method(method_name)
|
|
198
207
|
kind = Animation.TYPE_METHOD
|
|
208
|
+
# A method key is called at its time and never blended, so neither has anything to set.
|
|
209
|
+
for blending: String in ["interpolation", "loopWrap"]:
|
|
210
|
+
if track.has(blending):
|
|
211
|
+
return _not_added(
|
|
212
|
+
"track.%s is for property tracks; a method key is not blended." % blending
|
|
213
|
+
)
|
|
199
214
|
else:
|
|
200
215
|
return _not_added(
|
|
201
216
|
(
|
|
@@ -250,6 +265,10 @@ func _add_track(
|
|
|
250
265
|
_as_the_editor_makes(anim, track_idx, player, target)
|
|
251
266
|
if update >= 0:
|
|
252
267
|
anim.value_track_set_update_mode(track_idx, update as Animation.UpdateMode)
|
|
268
|
+
if interpolation >= 0:
|
|
269
|
+
anim.track_set_interpolation_type(track_idx, interpolation as Animation.InterpolationType)
|
|
270
|
+
if track.has("loopWrap"):
|
|
271
|
+
anim.track_set_interpolation_loop_wrap(track_idx, GdhRead.as_bool(track["loopWrap"]))
|
|
253
272
|
for index: int in keyframes.size():
|
|
254
273
|
var keyframe: Dictionary = keyframes[index]
|
|
255
274
|
if anim.track_insert_key(track_idx, GdhRead.as_float(keyframe["time"]), keys[index]) < 0:
|
|
@@ -160,7 +160,7 @@ func _hover(viewport: Viewport, point: Vector2) -> void:
|
|
|
160
160
|
var inside: Vector2 = container.get_global_transform_with_canvas().affine_inverse() * point
|
|
161
161
|
if container.stretch:
|
|
162
162
|
inside /= container.stretch_shrink
|
|
163
|
-
for child: Node in container.get_children():
|
|
163
|
+
for child: Node in container.get_children(true):
|
|
164
164
|
var shown: SubViewport = child as SubViewport
|
|
165
165
|
if shown != null and not shown.gui_disable_input:
|
|
166
166
|
_hover(shown, shown.get_final_transform().affine_inverse() * inside)
|
|
@@ -179,7 +179,7 @@ func _drop(viewport: Viewport, until: int) -> void:
|
|
|
179
179
|
var over: Control = instance_from_id(top) as Control
|
|
180
180
|
var container: SubViewportContainer = over as SubViewportContainer
|
|
181
181
|
if container != null:
|
|
182
|
-
for child: Node in container.get_children():
|
|
182
|
+
for child: Node in container.get_children(true):
|
|
183
183
|
var shown: SubViewport = child as SubViewport
|
|
184
184
|
if shown != null:
|
|
185
185
|
_drop(shown, 0)
|
|
@@ -264,7 +264,7 @@ func _pressed_at(viewport: Viewport, point: Vector2) -> Node:
|
|
|
264
264
|
var inside: Vector2 = container.get_global_transform_with_canvas().affine_inverse() * point
|
|
265
265
|
if container.stretch:
|
|
266
266
|
inside /= container.stretch_shrink
|
|
267
|
-
for child: Node in container.get_children():
|
|
267
|
+
for child: Node in container.get_children(true):
|
|
268
268
|
var shown: SubViewport = child as SubViewport
|
|
269
269
|
if shown != null and not shown.gui_disable_input:
|
|
270
270
|
var within: Node = _pressed_at(
|
|
@@ -277,9 +277,11 @@ func _pressed_at(viewport: Viewport, point: Vector2) -> Node:
|
|
|
277
277
|
|
|
278
278
|
## The controls [param node] holds that the engine keeps as roots of [param node]'s viewport: drawn
|
|
279
279
|
## on their own, or with no control above them. Not into another viewport, a window or a
|
|
280
|
-
## SubViewport, whose controls are that one's.
|
|
280
|
+
## SubViewport, whose controls are that one's. Internal children count, as they do for the engine,
|
|
281
|
+
## though get_children leaves them out unless asked: a layer added with INTERNAL_MODE_BACK was
|
|
282
|
+
## passed over, and the press predicted for the control under it.
|
|
281
283
|
static func _gather_roots(node: Node, roots: Array[Control]) -> void:
|
|
282
|
-
for child: Node in node.get_children():
|
|
284
|
+
for child: Node in node.get_children(true):
|
|
283
285
|
if child is Viewport:
|
|
284
286
|
continue
|
|
285
287
|
var control: Control = child as Control
|
|
@@ -331,8 +333,8 @@ func _at(item: CanvasItem, point: Vector2, holder: Transform2D) -> Control:
|
|
|
331
333
|
var control: Control = item as Control
|
|
332
334
|
var local: Vector2 = matrix.affine_inverse() * point
|
|
333
335
|
if control == null or not control.is_clipping_contents() or _has_point(control, local):
|
|
334
|
-
for index: int in range(item.get_child_count() - 1, -1, -1):
|
|
335
|
-
var child: CanvasItem = item.get_child(index) as CanvasItem
|
|
336
|
+
for index: int in range(item.get_child_count(true) - 1, -1, -1):
|
|
337
|
+
var child: CanvasItem = item.get_child(index, true) as CanvasItem
|
|
336
338
|
if child == null or child.top_level:
|
|
337
339
|
continue
|
|
338
340
|
var found: Control = _at(child, point, matrix)
|
|
@@ -61,9 +61,16 @@ func get_tree(params: Dictionary) -> Dictionary:
|
|
|
61
61
|
return reached
|
|
62
62
|
var root: Node = reached["node"]
|
|
63
63
|
|
|
64
|
+
# A game whose first scene hands over to another shows a tree that names neither, so which
|
|
65
|
+
# scene is showing was guessed from the project's documentation.
|
|
66
|
+
var scene: Node = _host.get_tree().current_scene
|
|
67
|
+
var showing: Variant = null
|
|
68
|
+
if scene != null:
|
|
69
|
+
showing = { "path": str(scene.get_path()), "file": scene.scene_file_path }
|
|
64
70
|
return {
|
|
65
71
|
"type": "tree",
|
|
66
72
|
"root": _serialize_node_tree(root, 0, max_depth, include_properties, named),
|
|
73
|
+
"current_scene": showing,
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
|
|
@@ -163,7 +163,7 @@ static func pressable_words(from: Node, open: Callable, most: int) -> String:
|
|
|
163
163
|
if offered.size() == most:
|
|
164
164
|
return ", ".join(offered) + ", and more"
|
|
165
165
|
offered.append(quoted)
|
|
166
|
-
var children: Array[Node] = node.get_children()
|
|
166
|
+
var children: Array[Node] = node.get_children(true)
|
|
167
167
|
for index: int in range(children.size() - 1, -1, -1):
|
|
168
168
|
pending.push_front(children[index])
|
|
169
169
|
return ", ".join(offered)
|