gdharness 1.2.33 → 1.2.34
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 +49 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +224 -0
- package/build/godot/addons/gdharness_editor/animation_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +76 -5
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +30 -143
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +4 -0
- package/build/godot/operations/animation_reads.gd +224 -0
- package/build/godot/operations/file_reads.gd +76 -5
- package/build/godot/operations/godot_operations.gd +17 -0
- package/build/godot/operations/input_actions.gd +51 -7
- package/build/index.js +48 -18
- package/package.json +1 -1
|
@@ -170,19 +170,63 @@ func add_input_action(params: Dictionary) -> Dictionary:
|
|
|
170
170
|
if action.is_empty():
|
|
171
171
|
return {}
|
|
172
172
|
|
|
173
|
+
# An action that exists is rebound, which is how one of the engine's ui_ actions is changed, so
|
|
174
|
+
# what it held is answered rather than lost without a word.
|
|
175
|
+
var setting: String = "input/" + action_name
|
|
176
|
+
var answer: Dictionary = {
|
|
177
|
+
"action_name": action_name,
|
|
178
|
+
"events_count": events_config.size(),
|
|
179
|
+
"deadzone": deadzone,
|
|
180
|
+
"events": events_config,
|
|
181
|
+
"was_new": not ProjectSettings.has_setting(setting),
|
|
182
|
+
}
|
|
183
|
+
if not answer["was_new"]:
|
|
184
|
+
var held: Variant = ProjectSettings.get_setting(setting)
|
|
185
|
+
var old: Dictionary = held if held is Dictionary else {}
|
|
186
|
+
var old_events: Array[Dictionary] = []
|
|
187
|
+
var listed: Variant = old.get("events", [])
|
|
188
|
+
if listed is Array:
|
|
189
|
+
var events_held: Array = listed
|
|
190
|
+
for event: Variant in events_held:
|
|
191
|
+
if event is InputEvent:
|
|
192
|
+
var bound: InputEvent = event
|
|
193
|
+
old_events.append(_shaped(bound))
|
|
194
|
+
answer["old_events"] = old_events
|
|
195
|
+
answer["old_deadzone"] = Read.as_float(old.get("deadzone", 0.5), 0.5)
|
|
196
|
+
|
|
173
197
|
# Through ProjectSettings so the file is saved the way the editor saves it, header and
|
|
174
198
|
# every other line kept; a ConfigFile of project.godot drops the comments on the way out.
|
|
175
|
-
ProjectSettings.set_setting(
|
|
199
|
+
ProjectSettings.set_setting(setting, action)
|
|
176
200
|
var err: Error = ProjectFile.save()
|
|
177
201
|
if err != OK:
|
|
178
202
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
179
203
|
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
return answer
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
## [param event] in the form add_input_action answers its events with, and as the engine names it
|
|
208
|
+
## under text, which also covers a kind of event that form has no fields for.
|
|
209
|
+
static func _shaped(event: InputEvent) -> Dictionary:
|
|
210
|
+
var shaped: Dictionary = {"class_name": event.get_class(), "text": event.as_text()}
|
|
211
|
+
if event is InputEventKey:
|
|
212
|
+
var key: InputEventKey = event
|
|
213
|
+
shaped["keycode"] = key.keycode
|
|
214
|
+
if key.physical_keycode != KEY_NONE:
|
|
215
|
+
shaped["physical_keycode"] = key.physical_keycode
|
|
216
|
+
for modifier: String in ["ctrl_pressed", "alt_pressed", "shift_pressed"]:
|
|
217
|
+
if key.get(modifier):
|
|
218
|
+
shaped[modifier] = true
|
|
219
|
+
elif event is InputEventMouseButton:
|
|
220
|
+
var mouse: InputEventMouseButton = event
|
|
221
|
+
shaped["button_index"] = mouse.button_index
|
|
222
|
+
elif event is InputEventJoypadButton:
|
|
223
|
+
var pad: InputEventJoypadButton = event
|
|
224
|
+
shaped["button_index"] = pad.button_index
|
|
225
|
+
elif event is InputEventJoypadMotion:
|
|
226
|
+
var motion: InputEventJoypadMotion = event
|
|
227
|
+
shaped["axis"] = motion.axis
|
|
228
|
+
shaped["axis_value"] = motion.axis_value
|
|
229
|
+
return shaped
|
|
186
230
|
|
|
187
231
|
|
|
188
232
|
# Remove an input action the project defines, which add_input_action had no way back from.
|
package/build/index.js
CHANGED
|
@@ -25071,6 +25071,12 @@ function isWithinRoot(rootPath, candidatePath) {
|
|
|
25071
25071
|
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
25072
25072
|
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
25073
25073
|
}
|
|
25074
|
+
var KEPT_ID = /^[A-Za-z0-9_]+$/;
|
|
25075
|
+
function keptInside(given) {
|
|
25076
|
+
const at = given.indexOf("::");
|
|
25077
|
+
const inner = at === -1 ? "" : given.slice(at + 2);
|
|
25078
|
+
return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
|
|
25079
|
+
}
|
|
25074
25080
|
function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
25075
25081
|
const trimmed = candidatePath.trim();
|
|
25076
25082
|
if (trimmed === "") {
|
|
@@ -25099,6 +25105,13 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
|
25099
25105
|
reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
|
|
25100
25106
|
};
|
|
25101
25107
|
}
|
|
25108
|
+
const kept = keptInside(trimmed);
|
|
25109
|
+
if (kept.inner !== undefined) {
|
|
25110
|
+
return {
|
|
25111
|
+
ok: false,
|
|
25112
|
+
reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
|
|
25113
|
+
};
|
|
25114
|
+
}
|
|
25102
25115
|
if (withoutScheme.includes(":")) {
|
|
25103
25116
|
return {
|
|
25104
25117
|
ok: false,
|
|
@@ -35040,7 +35053,7 @@ var TOOL_SPECS = [
|
|
|
35040
35053
|
{
|
|
35041
35054
|
name: "project_settings",
|
|
35042
35055
|
title: "Project settings",
|
|
35043
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. 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. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change touches only its own lines. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
35056
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. 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. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under 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. Every write keeps the sections and keys of project.godot in the order the file had them, so a change touches only its own lines. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
35044
35057
|
parameters: {
|
|
35045
35058
|
projectPath: PROJECT_PATH2,
|
|
35046
35059
|
setting: {
|
|
@@ -35138,8 +35151,8 @@ var TOOL_SPECS = [
|
|
|
35138
35151
|
requires: ["scenePath"]
|
|
35139
35152
|
},
|
|
35140
35153
|
add_input_action: {
|
|
35141
|
-
effect: "
|
|
35142
|
-
summary: "register an input action and its events",
|
|
35154
|
+
effect: "destructive",
|
|
35155
|
+
summary: "register an input action and its events, or rebind one that exists",
|
|
35143
35156
|
requires: ["actionName", "events"]
|
|
35144
35157
|
},
|
|
35145
35158
|
remove_input_action: {
|
|
@@ -35487,7 +35500,7 @@ var TOOL_SPECS = [
|
|
|
35487
35500
|
{
|
|
35488
35501
|
name: "scene_animation",
|
|
35489
35502
|
title: "Animations",
|
|
35490
|
-
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another.
|
|
35503
|
+
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. read and read_states need 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 running the scene's scripts. The ops that write need the editor connected.",
|
|
35491
35504
|
parameters: {
|
|
35492
35505
|
projectPath: PROJECT_PATH2,
|
|
35493
35506
|
scenePath: SCENE_PATH,
|
|
@@ -35519,7 +35532,11 @@ var TOOL_SPECS = [
|
|
|
35519
35532
|
ops: ["read_states", "add_state", "connect_states"],
|
|
35520
35533
|
description: "The AnimationTree node."
|
|
35521
35534
|
},
|
|
35522
|
-
stateName: {
|
|
35535
|
+
stateName: {
|
|
35536
|
+
type: "string",
|
|
35537
|
+
ops: ["add_state"],
|
|
35538
|
+
description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position."
|
|
35539
|
+
},
|
|
35523
35540
|
stateMachinePath: {
|
|
35524
35541
|
type: "string",
|
|
35525
35542
|
ops: ["read_states", "add_state", "connect_states"],
|
|
@@ -35694,7 +35711,7 @@ var TOOL_SPECS = [
|
|
|
35694
35711
|
projectPath: PROJECT_PATH2,
|
|
35695
35712
|
resourcePath: {
|
|
35696
35713
|
...RESOURCE_PATH,
|
|
35697
|
-
description: `${RESOURCE_FILE} move: a directory too.`
|
|
35714
|
+
description: `${RESOURCE_FILE} move: a directory too. read: also a resource kept inside a scene or resource file, by the path a read answers a property holding it with, such as "res://hero.tscn::Animation_jump".`
|
|
35698
35715
|
},
|
|
35699
35716
|
resourceType: {
|
|
35700
35717
|
type: "string",
|
|
@@ -38950,7 +38967,11 @@ class GodotServer {
|
|
|
38950
38967
|
case "scene_create":
|
|
38951
38968
|
return op === "create" ? await bridge("create_scene") : await bridge("save_scene");
|
|
38952
38969
|
case "scene_tree":
|
|
38953
|
-
return await this.readFromFile(args, "scenePath", "read_scene_tree",
|
|
38970
|
+
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
38971
|
+
scenePath: file,
|
|
38972
|
+
depth: readNumber(args, "depth") ?? -1,
|
|
38973
|
+
includeProperties: readBoolean(args, "includeProperties") ?? false
|
|
38974
|
+
}), async () => await bridge("list_scene_nodes"));
|
|
38954
38975
|
case "scene_node":
|
|
38955
38976
|
switch (op) {
|
|
38956
38977
|
case "add":
|
|
@@ -38980,9 +39001,17 @@ class GodotServer {
|
|
|
38980
39001
|
case "scene_animation":
|
|
38981
39002
|
switch (op) {
|
|
38982
39003
|
case "read":
|
|
38983
|
-
return await
|
|
39004
|
+
return await this.readFromFile(args, "scenePath", "read_animations", (file) => ({
|
|
39005
|
+
scenePath: file,
|
|
39006
|
+
playerNodePath: readNonEmptyString(args, "playerNodePath") ?? ".",
|
|
39007
|
+
animationName: readString(args, "animationName") ?? ""
|
|
39008
|
+
}), async () => await bridge("read_animations"));
|
|
38984
39009
|
case "read_states":
|
|
38985
|
-
return await
|
|
39010
|
+
return await this.readFromFile(args, "scenePath", "read_animation_states", (file) => ({
|
|
39011
|
+
scenePath: file,
|
|
39012
|
+
animTreePath: readString(args, "animTreePath") ?? "",
|
|
39013
|
+
stateMachinePath: readString(args, "stateMachinePath") ?? ""
|
|
39014
|
+
}), async () => await bridge("read_animation_states"));
|
|
38986
39015
|
case "create":
|
|
38987
39016
|
return await bridge("create_animation");
|
|
38988
39017
|
case "add_track":
|
|
@@ -39009,8 +39038,14 @@ class GodotServer {
|
|
|
39009
39038
|
switch (op) {
|
|
39010
39039
|
case "move":
|
|
39011
39040
|
return await this.handleMoveFile(args);
|
|
39012
|
-
case "read":
|
|
39013
|
-
|
|
39041
|
+
case "read": {
|
|
39042
|
+
const { file, inner } = keptInside(readString(args, "resourcePath") ?? "");
|
|
39043
|
+
const kept = inner === undefined ? {} : { resourcePath: file, subResource: inner };
|
|
39044
|
+
return await this.readFromFile({ ...args, ...kept }, "resourcePath", "read_resource", (found) => ({
|
|
39045
|
+
resourcePath: inner === undefined ? found : `${found}::${inner}`,
|
|
39046
|
+
includeDefaults: readBoolean(args, "includeDefaults") ?? false
|
|
39047
|
+
}), async () => await bridge("read_resource", kept));
|
|
39048
|
+
}
|
|
39014
39049
|
case "create":
|
|
39015
39050
|
return await bridge("create_resource");
|
|
39016
39051
|
case "modify":
|
|
@@ -43400,7 +43435,7 @@ class GodotServer {
|
|
|
43400
43435
|
...leftChanged === undefined ? {} : { note: leftChanged }
|
|
43401
43436
|
});
|
|
43402
43437
|
}
|
|
43403
|
-
async readFromFile(args, pathKey, operation, throughTheEditor) {
|
|
43438
|
+
async readFromFile(args, pathKey, operation, paramsFor, throughTheEditor) {
|
|
43404
43439
|
const project = this.project(args);
|
|
43405
43440
|
if (!project.ok) {
|
|
43406
43441
|
return project.response;
|
|
@@ -43418,12 +43453,7 @@ class GodotServer {
|
|
|
43418
43453
|
if (file === null) {
|
|
43419
43454
|
return this.createErrorResponse(`${pathKey} must name a file inside the project.`);
|
|
43420
43455
|
}
|
|
43421
|
-
const
|
|
43422
|
-
scenePath: file,
|
|
43423
|
-
depth: readNumber(args, "depth") ?? -1,
|
|
43424
|
-
includeProperties: readBoolean(args, "includeProperties") ?? false
|
|
43425
|
-
} : { resourcePath: file, includeDefaults: readBoolean(args, "includeDefaults") ?? false };
|
|
43426
|
-
const outcome = await this.operation(operation, params, projectPath);
|
|
43456
|
+
const outcome = await this.operation(operation, paramsFor(file), projectPath);
|
|
43427
43457
|
if (!outcome.ok) {
|
|
43428
43458
|
return this.answer(outcome);
|
|
43429
43459
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.34",
|
|
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",
|