gdharness 1.3.35 → 1.3.37
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 +288 -78
- package/build/godot/addons/gdharness_editor/animation_reads.gd +17 -1
- package/build/godot/addons/gdharness_editor/reading.gd +10 -2
- package/build/godot/addons/gdharness_editor/scene_file.gd +12 -6
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +57 -0
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +81 -4
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -2
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +59 -0
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +148 -0
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +10 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +70 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +223 -248
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +128 -5
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +107 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +15 -1
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -0
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +22 -0
- package/build/godot/operations/animation_reads.gd +17 -1
- package/build/godot/operations/classdb_queries.gd +16 -0
- package/build/godot/operations/dependencies.gd +46 -4
- package/build/godot/operations/editor_tools.gd +5 -5
- package/build/godot/operations/gdscript_authoring.gd +7 -3
- package/build/godot/operations/input_actions.gd +105 -0
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +15 -0
- package/build/godot/operations/reading.gd +10 -2
- package/build/keeper.js +5 -5
- package/package.json +1 -1
|
@@ -3,6 +3,17 @@ extends RefCounted
|
|
|
3
3
|
const GdhRead = preload("reading.gd")
|
|
4
4
|
const GdhLog = preload("logger.gd")
|
|
5
5
|
const GdhProjectFile = preload("project_file.gd")
|
|
6
|
+
const GdhPatterns = preload("patterns.gd")
|
|
7
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
8
|
+
## The calls that read an input action by name, Input's and an event's, up to the opening bracket.
|
|
9
|
+
const READS_AN_ACTION: String = (
|
|
10
|
+
"\\b(?:is_action(?:_just)?_(?:pressed|released)|is_action|get_action(?:_raw)?_strength"
|
|
11
|
+
+ "|action_press|action_release|get_axis|get_vector)\\("
|
|
12
|
+
)
|
|
13
|
+
## InputMap's call that makes an action at run time, which a script then reads as its own.
|
|
14
|
+
const ADDS_AN_ACTION: String = "\\badd_action\\("
|
|
15
|
+
## An argument that is a name written in the call and nothing else, a String or a StringName.
|
|
16
|
+
const NAME_ARGUMENT: String = "^&?(?:\"([^\"\\\\]*)\"|'([^'\\\\]*)')$"
|
|
6
17
|
## The device an event matches every device with, InputMap's ALL_DEVICES, which GDScript is not given.
|
|
7
18
|
const ALL_DEVICES: int = -1
|
|
8
19
|
const MOUSE_NAMES: String = (
|
|
@@ -113,6 +124,100 @@ func _init(p_log: GdhLog) -> void:
|
|
|
113
124
|
_log = p_log
|
|
114
125
|
|
|
115
126
|
|
|
127
|
+
## The input actions the project's scripts read by a name written in the call that neither the
|
|
128
|
+
## project nor the engine defines, each as {action, read_at} with the script and line of every read.
|
|
129
|
+
## Godot answers such a read with false and an error on every frame, so the input never works and
|
|
130
|
+
## nothing fails until the game runs. A name a script builds is not judged, and one a script adds
|
|
131
|
+
## with InputMap.add_action is its own to define.
|
|
132
|
+
func undefined_in_scripts() -> Array[Dictionary]:
|
|
133
|
+
var read: Dictionary = {}
|
|
134
|
+
var added: Dictionary = {}
|
|
135
|
+
for script: String in GdhFileWalk.new().find_files("res://", ".gd"):
|
|
136
|
+
var number: int = 0
|
|
137
|
+
for line: String in FileAccess.get_file_as_string(script).split("\n"):
|
|
138
|
+
number += 1
|
|
139
|
+
var code: String = GdhPatterns.without_comment(line)
|
|
140
|
+
for adding: RegExMatch in GdhPatterns.compiled(ADDS_AN_ACTION).search_all(code):
|
|
141
|
+
if not _in_a_string(code, adding.get_start()):
|
|
142
|
+
for name: String in _names_given(code, adding.get_end()):
|
|
143
|
+
added[name] = true
|
|
144
|
+
for reading: RegExMatch in GdhPatterns.compiled(READS_AN_ACTION).search_all(code):
|
|
145
|
+
if _in_a_string(code, reading.get_start()):
|
|
146
|
+
continue
|
|
147
|
+
for name: String in _names_given(code, reading.get_end()):
|
|
148
|
+
var at: Array = read.get_or_add(name, [])
|
|
149
|
+
var place: String = "%s:%d" % [script, number]
|
|
150
|
+
if not place in at:
|
|
151
|
+
at.append(place)
|
|
152
|
+
var names: Array = read.keys()
|
|
153
|
+
names.sort()
|
|
154
|
+
var undefined: Array[Dictionary] = []
|
|
155
|
+
for name: String in names:
|
|
156
|
+
if ProjectSettings.has_setting("input/" + name) or added.has(name):
|
|
157
|
+
continue
|
|
158
|
+
undefined.append({"action": name, "read_at": read[name]})
|
|
159
|
+
return undefined
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## One line for [param entry], one of [method undefined_in_scripts].
|
|
163
|
+
static func undefined_line(entry: Dictionary) -> String:
|
|
164
|
+
var listed: Array = entry.get("read_at", [])
|
|
165
|
+
var at: PackedStringArray = PackedStringArray(listed)
|
|
166
|
+
return (
|
|
167
|
+
"%s is not an input action of this project, and %s %s it"
|
|
168
|
+
% [str(entry.get("action", "")), ", ".join(at), "reads" if at.size() == 1 else "read"]
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
## The arguments of the call whose bracket opens before [param from] in [param code] that are each a
|
|
173
|
+
## name written in the call, to its closing bracket or the end of the line.
|
|
174
|
+
static func _names_given(code: String, from: int) -> Array[String]:
|
|
175
|
+
var names: Array[String] = []
|
|
176
|
+
var depth: int = 0
|
|
177
|
+
var quote: String = ""
|
|
178
|
+
var argument: String = ""
|
|
179
|
+
for at: int in range(from, code.length()):
|
|
180
|
+
var ch: String = code[at]
|
|
181
|
+
if not quote.is_empty():
|
|
182
|
+
if ch == quote and code[at - 1] != "\\":
|
|
183
|
+
quote = ""
|
|
184
|
+
elif ch == '"' or ch == "'":
|
|
185
|
+
quote = ch
|
|
186
|
+
elif ch == "(" or ch == "[" or ch == "{":
|
|
187
|
+
depth += 1
|
|
188
|
+
elif ch == ")" or ch == "]" or ch == "}":
|
|
189
|
+
if depth == 0:
|
|
190
|
+
_take_name(argument, names)
|
|
191
|
+
return names
|
|
192
|
+
depth -= 1
|
|
193
|
+
elif ch == "," and depth == 0:
|
|
194
|
+
_take_name(argument, names)
|
|
195
|
+
argument = ""
|
|
196
|
+
continue
|
|
197
|
+
argument += ch
|
|
198
|
+
return names
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
## Whether [param at] in [param code] is inside a string literal, where a call is text rather than one.
|
|
202
|
+
static func _in_a_string(code: String, at: int) -> bool:
|
|
203
|
+
var quote: String = ""
|
|
204
|
+
for index: int in range(at):
|
|
205
|
+
var ch: String = code[index]
|
|
206
|
+
if not quote.is_empty():
|
|
207
|
+
if ch == quote and code[index - 1] != "\\":
|
|
208
|
+
quote = ""
|
|
209
|
+
elif ch == '"' or ch == "'":
|
|
210
|
+
quote = ch
|
|
211
|
+
return not quote.is_empty()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
static func _take_name(argument: String, names: Array[String]) -> void:
|
|
215
|
+
var named: RegExMatch = GdhPatterns.compiled(NAME_ARGUMENT).search(argument.strip_edges())
|
|
216
|
+
if named != null:
|
|
217
|
+
var single: String = named.get_string(2)
|
|
218
|
+
names.append(named.get_string(1) if single.is_empty() else single)
|
|
219
|
+
|
|
220
|
+
|
|
116
221
|
# Add an input action to the InputMap
|
|
117
222
|
func add_input_action(params: Dictionary) -> Dictionary:
|
|
118
223
|
var action_name: String = str(params.get("action_name", ""))
|
|
@@ -3,6 +3,7 @@ extends RefCounted
|
|
|
3
3
|
const GdhDependencies = preload("dependencies.gd")
|
|
4
4
|
const GdhFileWalk = preload("file_walk.gd")
|
|
5
5
|
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
6
|
+
const GdhInputActions = preload("input_actions.gd")
|
|
6
7
|
const GdhLog = preload("logger.gd")
|
|
7
8
|
const GdhRead = preload("reading.gd")
|
|
8
9
|
const GdhSceneConnections = preload("scene_connections.gd")
|
|
@@ -119,6 +120,9 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
119
120
|
for entry: Dictionary in GdhDependencies.new(_log).missing_references():
|
|
120
121
|
details.append(GdhDependencies.missing_line(entry))
|
|
121
122
|
deductions += 5
|
|
123
|
+
for entry: Dictionary in GdhInputActions.new(_log).undefined_in_scripts():
|
|
124
|
+
details.append(GdhInputActions.undefined_line(entry))
|
|
125
|
+
deductions += 5
|
|
122
126
|
checks["references"] = {"name": "References", "passed": details.is_empty(), "details": details}
|
|
123
127
|
|
|
124
128
|
# 4. Scenes Check
|
|
@@ -7,6 +7,7 @@ const GdhDependencies = preload("dependencies.gd")
|
|
|
7
7
|
const GdhExportPresets = preload("export_presets.gd")
|
|
8
8
|
const GdhFileWalk = preload("file_walk.gd")
|
|
9
9
|
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
10
|
+
const GdhInputActions = preload("input_actions.gd")
|
|
10
11
|
const GdhLog = preload("logger.gd")
|
|
11
12
|
const GdhRead = preload("reading.gd")
|
|
12
13
|
const GdhSceneConnections = preload("scene_connections.gd")
|
|
@@ -219,6 +220,20 @@ func validate_project(params: Dictionary) -> Dictionary:
|
|
|
219
220
|
)
|
|
220
221
|
)
|
|
221
222
|
|
|
223
|
+
# An action read by a name the input map lacks answers false on every frame, so the input it was
|
|
224
|
+
# for never works, and the engine says so only while the game runs.
|
|
225
|
+
checks_performed.append("input_actions")
|
|
226
|
+
for entry: Dictionary in GdhInputActions.new(_log).undefined_in_scripts():
|
|
227
|
+
issues.append(
|
|
228
|
+
_finding(
|
|
229
|
+
"error",
|
|
230
|
+
"input_actions",
|
|
231
|
+
GdhInputActions.undefined_line(entry),
|
|
232
|
+
"Add it with project_settings add_input_action, or correct the name the script reads",
|
|
233
|
+
include_suggestions
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
222
237
|
# A connection whose method or signal is not there loads without a word and fails only once the
|
|
223
238
|
# game runs, on every emission or not at all; the references check sees files, not members.
|
|
224
239
|
checks_performed.append("connections")
|
|
@@ -215,7 +215,8 @@ static func offered(said: String, word: String, names: Array) -> String:
|
|
|
215
215
|
## [param said], the refusal for [param property], which [param target] does not declare, with the
|
|
216
216
|
## names it declares nearest it when any are, as a misspelt one is usually one of them, or else the
|
|
217
217
|
## variables its script declares. The engine's own are not listed when none is near, since an object
|
|
218
|
-
## has a hundred, while a script's are the few a caller most likely meant
|
|
218
|
+
## has a hundred, while a script's are the few a caller most likely meant; an object with no script
|
|
219
|
+
## is told where they are listed instead, since the refusal alone left nothing to try.
|
|
219
220
|
static func near_property(said: String, target: Object, property: String) -> String:
|
|
220
221
|
var headings: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
221
222
|
var names: Array[String] = []
|
|
@@ -233,8 +234,15 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
233
234
|
var close: PackedStringArray = nearest(property, PackedStringArray(names))
|
|
234
235
|
if not close.is_empty():
|
|
235
236
|
return "%s; the nearest it has %s %s" % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
236
|
-
|
|
237
|
+
# With a script that declares nothing readable, as one that does not compile, the name is likelier
|
|
238
|
+
# the script's than the class's, so the class's list would point the wrong way.
|
|
239
|
+
if scripted.is_empty() and target.get_script() != null:
|
|
237
240
|
return said
|
|
241
|
+
if scripted.is_empty():
|
|
242
|
+
return (
|
|
243
|
+
"%s; editor_classes info with className %s and includeInherited true lists the ones it has"
|
|
244
|
+
% [said, target.get_class()]
|
|
245
|
+
)
|
|
238
246
|
scripted.sort()
|
|
239
247
|
return "%s; its script declares %s" % [said, ", ".join(PackedStringArray(scripted))]
|
|
240
248
|
|
package/build/keeper.js
CHANGED
|
@@ -9190,7 +9190,7 @@ class GameDebugger {
|
|
|
9190
9190
|
case "stack": {
|
|
9191
9191
|
const frames = await this.stack();
|
|
9192
9192
|
if (frames === null) {
|
|
9193
|
-
return this.notHeld();
|
|
9193
|
+
return this.notHeld("stack to read");
|
|
9194
9194
|
}
|
|
9195
9195
|
return {
|
|
9196
9196
|
held: this.heldState(),
|
|
@@ -9218,9 +9218,9 @@ class GameDebugger {
|
|
|
9218
9218
|
...this.held.canContinue ? {} : { canContinue: false }
|
|
9219
9219
|
};
|
|
9220
9220
|
}
|
|
9221
|
-
notHeld() {
|
|
9221
|
+
notHeld(what) {
|
|
9222
9222
|
return {
|
|
9223
|
-
refused:
|
|
9223
|
+
refused: `The game is running, not held, so it has no ${what}: debug_control pause stops it where it is, and a breakpoint stops it on its line.`
|
|
9224
9224
|
};
|
|
9225
9225
|
}
|
|
9226
9226
|
async stack() {
|
|
@@ -9384,7 +9384,7 @@ class GameDebugger {
|
|
|
9384
9384
|
async variables(frame) {
|
|
9385
9385
|
const held = this.held;
|
|
9386
9386
|
if (held === null) {
|
|
9387
|
-
return this.notHeld();
|
|
9387
|
+
return this.notHeld("frame whose variables could be read");
|
|
9388
9388
|
}
|
|
9389
9389
|
const frames = await this.stack();
|
|
9390
9390
|
if (frames === null) {
|
|
@@ -9431,7 +9431,7 @@ class GameDebugger {
|
|
|
9431
9431
|
async evaluate(frame, expression) {
|
|
9432
9432
|
const held = this.held;
|
|
9433
9433
|
if (held === null) {
|
|
9434
|
-
return this.notHeld();
|
|
9434
|
+
return this.notHeld("frame to work an expression out in");
|
|
9435
9435
|
}
|
|
9436
9436
|
const read = await this.variables(frame);
|
|
9437
9437
|
if (!Array.isArray(read["scopes"])) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.37",
|
|
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",
|