gdharness 1.3.58 → 1.3.59
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 +3 -3
- package/build/godot/addons/gdharness_editor/setting_reads.gd +58 -0
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +5 -2
- package/build/godot/operations/project_config.gd +21 -1
- package/build/godot/operations/setting_reads.gd +58 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -17986,7 +17986,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17986
17986
|
{
|
|
17987
17987
|
name: "project_settings",
|
|
17988
17988
|
title: "Project settings",
|
|
17989
|
-
description: "Reads or writes project.godot (settings, autoloads, the main scene, input actions and plugins) and the audio bus layout, default_bus_layout.tres unless audio/buses/default_bus_layout names another. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write
|
|
17989
|
+
description: "Reads or writes project.godot (settings, autoloads, the main scene, input actions and plugins) and the audio bus layout, default_bus_layout.tres unless audio/buses/default_bus_layout names another. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write names from the file (named under editorAdopted), even one the file already held, or reloads the bus layout, so it does not save the old values back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start, and enable_plugin notes a script it would not load. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is, and overrides with overridesNote when a feature's override still replaces it. add_autoload refuses a name the editor's Autoload tab refuses: not an identifier, a keyword, a built-in type or an engine class. add_input_action on an existing action, a ui_ one included, replaces its events, and its deadzone when one is given, answering was_new false with old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. unset takes a setting's line out of project.godot and answers removed, old_value and default_value, the value that holds now (null for a setting the engine does not declare; for an override, name.feature, holds is the base setting's value its builds read now); on one the file does not set it answers removed false and writes nothing, its note naming the nearest settings when the engine does not declare it either. Every write changes only the lines of the settings it names and keeps every other line as the file had it, in its place. get answers exists, true for any setting the engine knows, at_default, true when the value is the engine's own default and so has no line in project.godot, and overrides, the value each feature's override holds. An override answers override_of, its base setting, and one project.godot does not set answers exists false with holds, the base setting's value. Each setting under prefix carries at_default too; it is absent for a setting with no engine default, such as an input action the project added. A setting res://override.cfg or the project_settings_override file sets, which a run lays over project.godot and the editor does not, is answered with the project's value and overridden_in, the file and the value a run reads; a set adds overriddenNote.",
|
|
17990
17990
|
parameters: {
|
|
17991
17991
|
projectPath: PROJECT_PATH,
|
|
17992
17992
|
setting: {
|
|
@@ -19279,7 +19279,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19279
19279
|
title: "Editor status",
|
|
19280
19280
|
effect: "read",
|
|
19281
19281
|
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on a connected editor and on each running game (absent with neither), says it runs addon code other than what this server ships, and staleNote says which half moves: the project, by a gdharness upgrade it spells out to run in a shell, the editor by a restart, or this server by a reconnect. A false editor.lspListening or editor.dapListening comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
|
|
19282
|
-
answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. editor.openScenes lists the scenes open in the editor's tabs, leaving out a tab of a scene never saved, editor.editedScene the one it is showing (null when none is, or when that scene was never saved), and editor.unsavedScenes those with changes not yet written to disk, where an empty entry is a scene never saved; all three come with scan and are absent when it is null. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs headless with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
|
|
19282
|
+
answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves on this release's runtime addon, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. editor.openScenes lists the scenes open in the editor's tabs, leaving out a tab of a scene never saved, editor.editedScene the one it is showing (null when none is, or when that scene was never saved), and editor.unsavedScenes those with changes not yet written to disk, where an empty entry is a scene never saved; all three come with scan and are absent when it is null. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs headless with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
|
|
19283
19283
|
parameters: {},
|
|
19284
19284
|
requires: []
|
|
19285
19285
|
},
|
|
@@ -55687,7 +55687,7 @@ ${error.text}`;
|
|
|
55687
55687
|
run,
|
|
55688
55688
|
playingInEditor: playing,
|
|
55689
55689
|
...hold,
|
|
55690
|
-
runtimeConnected: games.some((game) => game.reachable && game.ofThisProject !== false),
|
|
55690
|
+
runtimeConnected: games.some((game) => game.reachable && game.ofThisProject !== false && game.addonIsStale !== true),
|
|
55691
55691
|
runtimes: games,
|
|
55692
55692
|
unreadable: announced.unspoken.map((game) => ({
|
|
55693
55693
|
pid: game.pid,
|
|
@@ -16,6 +16,13 @@ static func one(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
|
16
16
|
|
|
17
17
|
if result["exists"]:
|
|
18
18
|
var value: Variant = ProjectSettings.get_setting(setting_path)
|
|
19
|
+
var laid: Dictionary = laid_over(setting_path, override_files())
|
|
20
|
+
if not laid.is_empty():
|
|
21
|
+
if not Engine.is_editor_hint():
|
|
22
|
+
value = laid["project"]
|
|
23
|
+
result["overridden_in"] = {
|
|
24
|
+
"file": laid["file"], "value": values.serialize_value(laid["value"]),
|
|
25
|
+
}
|
|
19
26
|
result["value"] = values.serialize_value(value)
|
|
20
27
|
# A setting the engine registers exists whether or not the project sets it, so exists did
|
|
21
28
|
# not say whether project.godot has a line for it. At its default it has none: the engine
|
|
@@ -54,17 +61,25 @@ static func one(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
|
54
61
|
## wrong: 2 is as true as true is.
|
|
55
62
|
static func under(prefix: String, values: GdhSerialisation) -> Dictionary:
|
|
56
63
|
var found: Array[Dictionary] = []
|
|
64
|
+
var overrides: Array[Dictionary] = override_files()
|
|
57
65
|
for property: Dictionary in ProjectSettings.get_property_list():
|
|
58
66
|
var name: String = str(property.get("name", ""))
|
|
59
67
|
if not name.begins_with(prefix):
|
|
60
68
|
continue
|
|
61
69
|
var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
|
|
62
70
|
var value: Variant = ProjectSettings.get_setting(name)
|
|
71
|
+
var laid: Dictionary = laid_over(name, overrides)
|
|
72
|
+
if not laid.is_empty() and not Engine.is_editor_hint():
|
|
73
|
+
value = laid["project"]
|
|
63
74
|
var described: Dictionary = {
|
|
64
75
|
"setting": name,
|
|
65
76
|
"type": type_named(kind, value),
|
|
66
77
|
"value": values.serialize_value(value),
|
|
67
78
|
}
|
|
79
|
+
if not laid.is_empty():
|
|
80
|
+
described["overridden_in"] = {
|
|
81
|
+
"file": laid["file"], "value": values.serialize_value(laid["value"]),
|
|
82
|
+
}
|
|
68
83
|
# As a single get says it, so a family read can be told apart into what the project sets and
|
|
69
84
|
# the engine's defaults: "input/" is ninety actions the engine ships and the project's few.
|
|
70
85
|
var default_value: Variant = ProjectSettings.property_get_revert(name)
|
|
@@ -81,6 +96,49 @@ static func under(prefix: String, values: GdhSerialisation) -> Dictionary:
|
|
|
81
96
|
return { "prefix": prefix, "count": found.size(), "settings": found }
|
|
82
97
|
|
|
83
98
|
|
|
99
|
+
## The files a run lays over project.godot, loaded, in the order the engine lays them, each as
|
|
100
|
+
## {"file", "config"}: res://override.cfg, then the file
|
|
101
|
+
## application/config/project_settings_override
|
|
102
|
+
## names. The editor lays neither, so a game keeping a player's window size in user:// had that size
|
|
103
|
+
## answered headless as the project's own setting, while the editor answered the project's.
|
|
104
|
+
static func override_files() -> Array[Dictionary]:
|
|
105
|
+
var files: Array[Dictionary] = []
|
|
106
|
+
var named: String = str(
|
|
107
|
+
ProjectSettings.get_setting("application/config/project_settings_override", "")
|
|
108
|
+
)
|
|
109
|
+
for file: String in ["res://override.cfg", named]:
|
|
110
|
+
var config: ConfigFile = ConfigFile.new()
|
|
111
|
+
if not file.is_empty() and config.load(file) == OK:
|
|
112
|
+
files.append({ "file": file, "config": config })
|
|
113
|
+
return files
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## What the last of [param overrides], from [method override_files], to set [param setting_path]
|
|
117
|
+
## sets it to, as {"file", "value", "project"}, where project is what project.godot holds, or the
|
|
118
|
+
## engine's default where it has no line: the value the editor holds, unsaved changes aside. Empty
|
|
119
|
+
## when none sets it.
|
|
120
|
+
static func laid_over(setting_path: String, overrides: Array[Dictionary]) -> Dictionary:
|
|
121
|
+
var at: int = setting_path.find("/")
|
|
122
|
+
if at <= 0:
|
|
123
|
+
return {}
|
|
124
|
+
var section: String = setting_path.left(at)
|
|
125
|
+
var key: String = setting_path.substr(at + 1)
|
|
126
|
+
for index: int in range(overrides.size() - 1, -1, -1):
|
|
127
|
+
var config: ConfigFile = overrides[index]["config"]
|
|
128
|
+
if not config.has_section_key(section, key):
|
|
129
|
+
continue
|
|
130
|
+
var project: ConfigFile = ConfigFile.new()
|
|
131
|
+
var held: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
132
|
+
if project.load("res://project.godot") == OK and project.has_section_key(section, key):
|
|
133
|
+
held = project.get_value(section, key)
|
|
134
|
+
return {
|
|
135
|
+
"file": overrides[index]["file"],
|
|
136
|
+
"value": config.get_value(section, key),
|
|
137
|
+
"project": held,
|
|
138
|
+
}
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
|
|
84
142
|
## The type [param kind] names, with what [param value] holds when it is a typed array or
|
|
85
143
|
## dictionary, as project.godot writes it: a `Dictionary[String, bool]` was answered as Dictionary,
|
|
86
144
|
## which hides what its keys and values must be.
|
|
@@ -804,12 +804,15 @@ static func with_methods_offered(said: String, object: Object, method: String) -
|
|
|
804
804
|
# to a refusal.
|
|
805
805
|
var flags: int = entry.get("flags", 0)
|
|
806
806
|
var callable: bool = not object is Script or flags & METHOD_FLAG_STATIC
|
|
807
|
-
|
|
807
|
+
# A property's setter and getter are named by the engine, @asked_setter, and offered
|
|
808
|
+
# they
|
|
809
|
+
# crowded out the script's own methods with names nobody means.
|
|
810
|
+
if callable and not name.begins_with("@") and not name.is_empty() and not own.has(name):
|
|
808
811
|
own.append(name)
|
|
809
812
|
var every: Array[String] = own.duplicate()
|
|
810
813
|
for entry: Dictionary in GdhValues.methods_of(object):
|
|
811
814
|
var name: String = str(entry.get("name", ""))
|
|
812
|
-
if not every.has(name):
|
|
815
|
+
if not name.begins_with("@") and not every.has(name):
|
|
813
816
|
every.append(name)
|
|
814
817
|
var close: PackedStringArray = GdhRead.nearest(method, PackedStringArray(every))
|
|
815
818
|
if not close.is_empty():
|
|
@@ -89,8 +89,13 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
89
89
|
|
|
90
90
|
var old_value: Variant = null
|
|
91
91
|
var had_value: bool = ProjectSettings.has_setting(setting_path)
|
|
92
|
+
var laid: Dictionary = GdhSettingReads.laid_over(
|
|
93
|
+
setting_path, GdhSettingReads.override_files()
|
|
94
|
+
)
|
|
92
95
|
if had_value:
|
|
93
|
-
old_value =
|
|
96
|
+
old_value = (
|
|
97
|
+
ProjectSettings.get_setting(setting_path) if laid.is_empty() else laid["project"]
|
|
98
|
+
)
|
|
94
99
|
# A setting the engine registers is there with its default whether or not project.godot sets it,
|
|
95
100
|
# so has_setting alone called the description of a project that had never set one an old value.
|
|
96
101
|
# Whether it is new is the file's to say: one at its default has a line only where the project
|
|
@@ -131,6 +136,16 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
131
136
|
"was_default": was_default,
|
|
132
137
|
"saved": true,
|
|
133
138
|
}
|
|
139
|
+
if not laid.is_empty():
|
|
140
|
+
var read: Variant = _values.serialize_value(laid["value"])
|
|
141
|
+
answer["overridden_in"] = { "file": laid["file"], "value": read }
|
|
142
|
+
answer["overriddenNote"] = (
|
|
143
|
+
(
|
|
144
|
+
"A run on this machine still reads %s for it from %s, which a run lays over "
|
|
145
|
+
+ "project.godot and the editor does not."
|
|
146
|
+
)
|
|
147
|
+
% [JSON.stringify(read), laid["file"]]
|
|
148
|
+
)
|
|
134
149
|
# The same value in the same type is no change, and the file is left alone: a save rewrites it
|
|
135
150
|
# whole. The type is compared as well, so an int setting the file holds as 50.0 is still put
|
|
136
151
|
# right.
|
|
@@ -254,6 +269,11 @@ func unset_project_setting(params: Dictionary) -> Dictionary:
|
|
|
254
269
|
var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
255
270
|
var held: bool = ProjectSettings.has_setting(setting_path)
|
|
256
271
|
var value: Variant = ProjectSettings.get_setting(setting_path) if held else null
|
|
272
|
+
var laid: Dictionary = GdhSettingReads.laid_over(
|
|
273
|
+
setting_path, GdhSettingReads.override_files()
|
|
274
|
+
)
|
|
275
|
+
if held and not laid.is_empty():
|
|
276
|
+
value = laid["project"]
|
|
257
277
|
var answer: Dictionary = {
|
|
258
278
|
"setting_path": setting_path,
|
|
259
279
|
"default_value": _values.serialize_value(default_value) if default_value != null else null,
|
|
@@ -16,6 +16,13 @@ static func one(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
|
16
16
|
|
|
17
17
|
if result["exists"]:
|
|
18
18
|
var value: Variant = ProjectSettings.get_setting(setting_path)
|
|
19
|
+
var laid: Dictionary = laid_over(setting_path, override_files())
|
|
20
|
+
if not laid.is_empty():
|
|
21
|
+
if not Engine.is_editor_hint():
|
|
22
|
+
value = laid["project"]
|
|
23
|
+
result["overridden_in"] = {
|
|
24
|
+
"file": laid["file"], "value": values.serialize_value(laid["value"]),
|
|
25
|
+
}
|
|
19
26
|
result["value"] = values.serialize_value(value)
|
|
20
27
|
# A setting the engine registers exists whether or not the project sets it, so exists did
|
|
21
28
|
# not say whether project.godot has a line for it. At its default it has none: the engine
|
|
@@ -54,17 +61,25 @@ static func one(setting_path: String, values: GdhSerialisation) -> Dictionary:
|
|
|
54
61
|
## wrong: 2 is as true as true is.
|
|
55
62
|
static func under(prefix: String, values: GdhSerialisation) -> Dictionary:
|
|
56
63
|
var found: Array[Dictionary] = []
|
|
64
|
+
var overrides: Array[Dictionary] = override_files()
|
|
57
65
|
for property: Dictionary in ProjectSettings.get_property_list():
|
|
58
66
|
var name: String = str(property.get("name", ""))
|
|
59
67
|
if not name.begins_with(prefix):
|
|
60
68
|
continue
|
|
61
69
|
var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
|
|
62
70
|
var value: Variant = ProjectSettings.get_setting(name)
|
|
71
|
+
var laid: Dictionary = laid_over(name, overrides)
|
|
72
|
+
if not laid.is_empty() and not Engine.is_editor_hint():
|
|
73
|
+
value = laid["project"]
|
|
63
74
|
var described: Dictionary = {
|
|
64
75
|
"setting": name,
|
|
65
76
|
"type": type_named(kind, value),
|
|
66
77
|
"value": values.serialize_value(value),
|
|
67
78
|
}
|
|
79
|
+
if not laid.is_empty():
|
|
80
|
+
described["overridden_in"] = {
|
|
81
|
+
"file": laid["file"], "value": values.serialize_value(laid["value"]),
|
|
82
|
+
}
|
|
68
83
|
# As a single get says it, so a family read can be told apart into what the project sets and
|
|
69
84
|
# the engine's defaults: "input/" is ninety actions the engine ships and the project's few.
|
|
70
85
|
var default_value: Variant = ProjectSettings.property_get_revert(name)
|
|
@@ -81,6 +96,49 @@ static func under(prefix: String, values: GdhSerialisation) -> Dictionary:
|
|
|
81
96
|
return { "prefix": prefix, "count": found.size(), "settings": found }
|
|
82
97
|
|
|
83
98
|
|
|
99
|
+
## The files a run lays over project.godot, loaded, in the order the engine lays them, each as
|
|
100
|
+
## {"file", "config"}: res://override.cfg, then the file
|
|
101
|
+
## application/config/project_settings_override
|
|
102
|
+
## names. The editor lays neither, so a game keeping a player's window size in user:// had that size
|
|
103
|
+
## answered headless as the project's own setting, while the editor answered the project's.
|
|
104
|
+
static func override_files() -> Array[Dictionary]:
|
|
105
|
+
var files: Array[Dictionary] = []
|
|
106
|
+
var named: String = str(
|
|
107
|
+
ProjectSettings.get_setting("application/config/project_settings_override", "")
|
|
108
|
+
)
|
|
109
|
+
for file: String in ["res://override.cfg", named]:
|
|
110
|
+
var config: ConfigFile = ConfigFile.new()
|
|
111
|
+
if not file.is_empty() and config.load(file) == OK:
|
|
112
|
+
files.append({ "file": file, "config": config })
|
|
113
|
+
return files
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## What the last of [param overrides], from [method override_files], to set [param setting_path]
|
|
117
|
+
## sets it to, as {"file", "value", "project"}, where project is what project.godot holds, or the
|
|
118
|
+
## engine's default where it has no line: the value the editor holds, unsaved changes aside. Empty
|
|
119
|
+
## when none sets it.
|
|
120
|
+
static func laid_over(setting_path: String, overrides: Array[Dictionary]) -> Dictionary:
|
|
121
|
+
var at: int = setting_path.find("/")
|
|
122
|
+
if at <= 0:
|
|
123
|
+
return {}
|
|
124
|
+
var section: String = setting_path.left(at)
|
|
125
|
+
var key: String = setting_path.substr(at + 1)
|
|
126
|
+
for index: int in range(overrides.size() - 1, -1, -1):
|
|
127
|
+
var config: ConfigFile = overrides[index]["config"]
|
|
128
|
+
if not config.has_section_key(section, key):
|
|
129
|
+
continue
|
|
130
|
+
var project: ConfigFile = ConfigFile.new()
|
|
131
|
+
var held: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
132
|
+
if project.load("res://project.godot") == OK and project.has_section_key(section, key):
|
|
133
|
+
held = project.get_value(section, key)
|
|
134
|
+
return {
|
|
135
|
+
"file": overrides[index]["file"],
|
|
136
|
+
"value": config.get_value(section, key),
|
|
137
|
+
"project": held,
|
|
138
|
+
}
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
|
|
84
142
|
## The type [param kind] names, with what [param value] holds when it is a typed array or
|
|
85
143
|
## dictionary, as project.godot writes it: a `Dictionary[String, bool]` was answered as Dictionary,
|
|
86
144
|
## which hides what its keys and values must be.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.59",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|