gdharness 1.3.3 → 1.3.5
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 +532 -456
- package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
- package/build/godot/addons/gdharness_editor/declared_values.gd +181 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
- package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
- package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
- package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +192 -231
- package/build/godot/addons/gdharness_editor/reading.gd +15 -0
- package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
- package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +97 -14
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
- package/build/godot/addons/gdharness_editor/tool_executor.gd +9 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +38 -35
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +20 -12
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +68 -2
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +94 -333
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/declared_values.gd +293 -0
- package/build/godot/addons/gdharness_runtime/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +26 -39
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +18 -0
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +62 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +48 -15
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +3 -2
- package/build/godot/operations/classdb_queries.gd +112 -46
- package/build/godot/operations/declared_values.gd +181 -4
- package/build/godot/operations/dependencies.gd +14 -39
- package/build/godot/operations/editor_tools.gd +4 -3
- package/build/godot/operations/file_reads.gd +15 -3
- package/build/godot/operations/gdscript_analysis.gd +229 -75
- package/build/godot/operations/gdscript_authoring.gd +157 -36
- package/build/godot/operations/import_pipeline.gd +17 -6
- package/build/godot/operations/logger.gd +7 -0
- package/build/godot/operations/property_paths.gd +225 -0
- package/build/godot/operations/property_values.gd +192 -231
- package/build/godot/operations/reading.gd +15 -0
- package/build/godot/operations/same_values.gd +177 -0
- package/build/godot/operations/serialisation.gd +75 -3
- package/build/keeper.js +83 -69
- package/package.json +1 -1
|
@@ -164,8 +164,9 @@ static func state_machine(tree: AnimationTree, state_machine_path: String) -> An
|
|
|
164
164
|
static func no_machine(tree: AnimationTree, state_machine_path: String) -> String:
|
|
165
165
|
if tree == null or tree.tree_root == null:
|
|
166
166
|
return (
|
|
167
|
-
"The AnimationTree has no tree_root.
|
|
168
|
-
+ "AnimationNodeStateMachine
|
|
167
|
+
"The AnimationTree has no tree_root. Give it one with scene_node set: tree_root "
|
|
168
|
+
+ '{"_type": "AnimationNodeStateMachine"} builds one into the scene, and the res:// path '
|
|
169
|
+
+ "of one made with resource_edit create keeps it in a file of its own."
|
|
169
170
|
)
|
|
170
171
|
var current: AnimationNodeStateMachine = tree.tree_root as AnimationNodeStateMachine
|
|
171
172
|
if current == null:
|
|
@@ -4,6 +4,9 @@ const GdhRead = preload("reading.gd")
|
|
|
4
4
|
const GdhLog = preload("logger.gd")
|
|
5
5
|
const GdhSerialisation = preload("serialisation.gd")
|
|
6
6
|
|
|
7
|
+
# How far below the closest name another may score and still be offered beside it.
|
|
8
|
+
const CLOSE_TO_THE_BEST: float = 0.2
|
|
9
|
+
|
|
7
10
|
# The base classes each category name stands for. Kept to the list the tool's schema offers, which
|
|
8
11
|
# a case holds: the schema offered physics3d and ui, which this refused, and physics answered the 3D
|
|
9
12
|
# bodies alone beside physics2d and physics3d.
|
|
@@ -114,20 +117,20 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
114
117
|
)
|
|
115
118
|
|
|
116
119
|
if not ClassDB.class_exists(class_name_str):
|
|
117
|
-
return _not_found(class_name_str)
|
|
120
|
+
return _not_found(class_name_str, _declared_classes(params))
|
|
118
121
|
|
|
119
122
|
var member: String = str(params.get("member", ""))
|
|
120
123
|
if not member.is_empty():
|
|
121
124
|
return _member_of(class_name_str, member)
|
|
122
125
|
|
|
123
|
-
var methods: Array[Dictionary] = _methods_of(class_name_str
|
|
124
|
-
var properties: Array[Dictionary] = _properties_of(class_name_str
|
|
125
|
-
var signals: Array[Dictionary] = _signals_of(class_name_str
|
|
126
|
+
var methods: Array[Dictionary] = _methods_of(class_name_str)
|
|
127
|
+
var properties: Array[Dictionary] = _properties_of(class_name_str)
|
|
128
|
+
var signals: Array[Dictionary] = _signals_of(class_name_str)
|
|
126
129
|
|
|
127
130
|
var enums: Dictionary = {}
|
|
128
|
-
for e: String in ClassDB.class_get_enum_list(class_name_str,
|
|
131
|
+
for e: String in ClassDB.class_get_enum_list(class_name_str, true):
|
|
129
132
|
var enum_values: Dictionary = {}
|
|
130
|
-
for c: String in ClassDB.class_get_enum_constants(class_name_str, e,
|
|
133
|
+
for c: String in ClassDB.class_get_enum_constants(class_name_str, e, true):
|
|
131
134
|
enum_values[c] = ClassDB.class_get_integer_constant(class_name_str, c)
|
|
132
135
|
enums[e] = enum_values
|
|
133
136
|
|
|
@@ -143,7 +146,7 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
143
146
|
)
|
|
144
147
|
)
|
|
145
148
|
|
|
146
|
-
|
|
149
|
+
var answer: Dictionary = {
|
|
147
150
|
"class_name": class_name_str,
|
|
148
151
|
"parent_class": ClassDB.get_parent_class(class_name_str),
|
|
149
152
|
"can_instantiate": ClassDB.can_instantiate(class_name_str),
|
|
@@ -156,43 +159,99 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
156
159
|
"signals": signals,
|
|
157
160
|
"enums": enums
|
|
158
161
|
}
|
|
162
|
+
if include_inherited:
|
|
163
|
+
answer["inherited"] = _inherited_names(class_name_str)
|
|
164
|
+
return answer
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# What [param class_name_str] inherits, by name under the ancestor declaring it, nearest first. In
|
|
168
|
+
# full, CharacterBody2D's inherited members ran to two hundred thousand characters, more than a
|
|
169
|
+
# client takes in one answer, and any one of them is read in full by asking for it as member.
|
|
170
|
+
func _inherited_names(class_name_str: String) -> Array[Dictionary]:
|
|
171
|
+
var inherited: Array[Dictionary] = []
|
|
172
|
+
var current: String = ClassDB.get_parent_class(class_name_str)
|
|
173
|
+
while not current.is_empty():
|
|
174
|
+
(
|
|
175
|
+
inherited
|
|
176
|
+
. append(
|
|
177
|
+
{
|
|
178
|
+
"class": current,
|
|
179
|
+
"methods": _names(_methods_of(current)),
|
|
180
|
+
"properties": _names(_properties_of(current)),
|
|
181
|
+
"signals": _names(_signals_of(current)),
|
|
182
|
+
"enums": Array(ClassDB.class_get_enum_list(current, true)),
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
current = ClassDB.get_parent_class(current)
|
|
187
|
+
return inherited
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
static func _names(members: Array[Dictionary]) -> Array[String]:
|
|
191
|
+
var names: Array[String] = []
|
|
192
|
+
for each: Dictionary in members:
|
|
193
|
+
names.append(str(each["name"]))
|
|
194
|
+
return names
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
static func _declared_classes(params: Dictionary) -> Dictionary:
|
|
198
|
+
var given: Variant = params.get("project_classes", {})
|
|
199
|
+
if given is Dictionary:
|
|
200
|
+
var declared: Dictionary = given
|
|
201
|
+
return declared
|
|
202
|
+
return {}
|
|
159
203
|
|
|
160
204
|
|
|
161
|
-
# One member of a class by name, wherever in its ancestry it is declared.
|
|
162
|
-
#
|
|
163
|
-
# The question a member is asked by name for is "does this engine have it, and what does it take":
|
|
164
|
-
# the whole class in answer to that ran to seventy thousand characters for Control, and the member
|
|
165
|
-
# is as often an ancestor's as the class's own, so the ancestry is walked without being asked. Each
|
|
166
|
-
# match says which class declares it. Nothing found names the members whose names contain the one
|
|
167
|
-
# asked for, since the usual miss is a spelling.
|
|
168
205
|
## Why [param wanted] has no answer here. A class_name a project script declares is not in ClassDB,
|
|
169
|
-
## and "Class not found" for one read as though the project had no such class.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
% [wanted, str(entry.get("path", ""))]
|
|
183
|
-
)
|
|
206
|
+
## and "Class not found" for one read as though the project had no such class. [param declared] is
|
|
207
|
+
## what the project's scripts declare on disk, class to path, rather than the engine's list, which is
|
|
208
|
+
## the class cache: that lags the scripts, and a class written a moment ago was refused as declared
|
|
209
|
+
## nowhere.
|
|
210
|
+
func _not_found(wanted: String, declared: Dictionary) -> Dictionary:
|
|
211
|
+
if declared.has(wanted):
|
|
212
|
+
return _log.failure(
|
|
213
|
+
(
|
|
214
|
+
(
|
|
215
|
+
"%s is not an engine class: it is the class_name %s declares, and editor_classes reads "
|
|
216
|
+
+ "the engine's ClassDB only. script_info structure on that script, with "
|
|
217
|
+
+ "includeInherited, answers what it declares and what it inherits."
|
|
184
218
|
)
|
|
219
|
+
% [wanted, str(declared[wanted])]
|
|
185
220
|
)
|
|
186
|
-
|
|
187
|
-
|
|
221
|
+
)
|
|
222
|
+
# The usual miss is a spelling, one letter off a class the engine has or one the project declares.
|
|
223
|
+
# Ranked as one pool and kept near the best, since a project class a letter off came behind three
|
|
224
|
+
# engine classes that shared only "Texture" with it.
|
|
225
|
+
var pool: PackedStringArray = ClassDB.get_class_list()
|
|
226
|
+
pool.append_array(PackedStringArray(declared.keys()))
|
|
227
|
+
var close: PackedStringArray = GdhRead.nearest(wanted, pool)
|
|
228
|
+
var best: float = 0.0 if close.is_empty() else close[0].to_lower().similarity(wanted.to_lower())
|
|
229
|
+
var engine: Array[String] = []
|
|
230
|
+
var own: Array[String] = []
|
|
231
|
+
for name: String in close:
|
|
232
|
+
if name.to_lower().similarity(wanted.to_lower()) < best - CLOSE_TO_THE_BEST:
|
|
233
|
+
continue
|
|
234
|
+
if declared.has(name):
|
|
235
|
+
own.append(name)
|
|
236
|
+
else:
|
|
237
|
+
engine.append(name)
|
|
188
238
|
var offered: String = (
|
|
189
239
|
""
|
|
190
|
-
if
|
|
240
|
+
if engine.is_empty()
|
|
191
241
|
else (
|
|
192
242
|
" The nearest engine %s %s."
|
|
193
|
-
% ["class is" if
|
|
243
|
+
% ["class is" if engine.size() == 1 else "classes are", ", ".join(engine)]
|
|
194
244
|
)
|
|
195
245
|
)
|
|
246
|
+
if not own.is_empty():
|
|
247
|
+
offered += (
|
|
248
|
+
" The nearest %s a project script declares %s %s, which script_info structure reads."
|
|
249
|
+
% [
|
|
250
|
+
"class" if own.size() == 1 else "classes",
|
|
251
|
+
"is" if own.size() == 1 else "are",
|
|
252
|
+
", ".join(own),
|
|
253
|
+
]
|
|
254
|
+
)
|
|
196
255
|
return _log.failure(
|
|
197
256
|
(
|
|
198
257
|
(
|
|
@@ -204,17 +263,24 @@ func _not_found(wanted: String) -> Dictionary:
|
|
|
204
263
|
)
|
|
205
264
|
|
|
206
265
|
|
|
266
|
+
# One member of a class by name, wherever in its ancestry it is declared.
|
|
267
|
+
#
|
|
268
|
+
# The question a member is asked by name for is "does this engine have it, and what does it take":
|
|
269
|
+
# the whole class in answer to that ran to seventy thousand characters for Control, and the member
|
|
270
|
+
# is as often an ancestor's as the class's own, so the ancestry is walked without being asked. Each
|
|
271
|
+
# match says which class declares it. Nothing found names the members whose names contain the one
|
|
272
|
+
# asked for, since the usual miss is a spelling.
|
|
207
273
|
func _member_of(class_name_str: String, member: String) -> Dictionary:
|
|
208
274
|
var found: Array[Dictionary] = []
|
|
209
275
|
var current: String = class_name_str
|
|
210
276
|
while not current.is_empty():
|
|
211
|
-
for m: Dictionary in _methods_of(current
|
|
277
|
+
for m: Dictionary in _methods_of(current):
|
|
212
278
|
if m["name"] == member:
|
|
213
279
|
found.append({"kind": "method", "declared_in": current, "member": m})
|
|
214
|
-
for p: Dictionary in _properties_of(current
|
|
280
|
+
for p: Dictionary in _properties_of(current):
|
|
215
281
|
if p["name"] == member:
|
|
216
282
|
found.append({"kind": "property", "declared_in": current, "member": p})
|
|
217
|
-
for s: Dictionary in _signals_of(current
|
|
283
|
+
for s: Dictionary in _signals_of(current):
|
|
218
284
|
if s["name"] == member:
|
|
219
285
|
found.append({"kind": "signal", "declared_in": current, "member": s})
|
|
220
286
|
for e: String in ClassDB.class_get_enum_list(current, true):
|
|
@@ -251,15 +317,15 @@ func _members_alike(class_name_str: String, member: String) -> Array[String]:
|
|
|
251
317
|
var wanted: String = member.to_lower()
|
|
252
318
|
var current: String = class_name_str
|
|
253
319
|
while not current.is_empty():
|
|
254
|
-
for m: Dictionary in _methods_of(current
|
|
320
|
+
for m: Dictionary in _methods_of(current):
|
|
255
321
|
var name: String = m["name"]
|
|
256
322
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
257
323
|
alike.append(name)
|
|
258
|
-
for p: Dictionary in _properties_of(current
|
|
324
|
+
for p: Dictionary in _properties_of(current):
|
|
259
325
|
var name: String = p["name"]
|
|
260
326
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
261
327
|
alike.append(name)
|
|
262
|
-
for s: Dictionary in _signals_of(current
|
|
328
|
+
for s: Dictionary in _signals_of(current):
|
|
263
329
|
var name: String = s["name"]
|
|
264
330
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
265
331
|
alike.append(name)
|
|
@@ -275,7 +341,7 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
275
341
|
_log.info("Inspecting inheritance for: " + class_name_str)
|
|
276
342
|
|
|
277
343
|
if not ClassDB.class_exists(class_name_str):
|
|
278
|
-
return _not_found(class_name_str)
|
|
344
|
+
return _not_found(class_name_str, _declared_classes(params))
|
|
279
345
|
|
|
280
346
|
var ancestors: Array[String] = []
|
|
281
347
|
var current: String = class_name_str
|
|
@@ -324,10 +390,10 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
324
390
|
}
|
|
325
391
|
|
|
326
392
|
|
|
327
|
-
func _methods_of(class_name_str: String
|
|
393
|
+
func _methods_of(class_name_str: String) -> Array[Dictionary]:
|
|
328
394
|
var methods: Array[Dictionary] = []
|
|
329
395
|
|
|
330
|
-
for m: Dictionary in ClassDB.class_get_method_list(class_name_str,
|
|
396
|
+
for m: Dictionary in ClassDB.class_get_method_list(class_name_str, true):
|
|
331
397
|
var args: Array[Dictionary] = []
|
|
332
398
|
var declared_args: Array = m.get("args", [])
|
|
333
399
|
for a: Dictionary in declared_args:
|
|
@@ -389,10 +455,10 @@ static func _type_name(info: Dictionary, nil_name: String) -> String:
|
|
|
389
455
|
return type_string(type)
|
|
390
456
|
|
|
391
457
|
|
|
392
|
-
func _properties_of(class_name_str: String
|
|
458
|
+
func _properties_of(class_name_str: String) -> Array[Dictionary]:
|
|
393
459
|
var properties: Array[Dictionary] = []
|
|
394
460
|
|
|
395
|
-
for p: Dictionary in ClassDB.class_get_property_list(class_name_str,
|
|
461
|
+
for p: Dictionary in ClassDB.class_get_property_list(class_name_str, true):
|
|
396
462
|
# A category, group or subgroup is an editor heading rather than a property.
|
|
397
463
|
var usage: int = GdhRead.as_int(p.get("usage", 0))
|
|
398
464
|
if usage & PROPERTY_USAGE_CATEGORY or usage & PROPERTY_USAGE_GROUP or usage & PROPERTY_USAGE_SUBGROUP:
|
|
@@ -412,10 +478,10 @@ func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Di
|
|
|
412
478
|
return properties
|
|
413
479
|
|
|
414
480
|
|
|
415
|
-
func _signals_of(class_name_str: String
|
|
481
|
+
func _signals_of(class_name_str: String) -> Array[Dictionary]:
|
|
416
482
|
var signals: Array[Dictionary] = []
|
|
417
483
|
|
|
418
|
-
for s: Dictionary in ClassDB.class_get_signal_list(class_name_str,
|
|
484
|
+
for s: Dictionary in ClassDB.class_get_signal_list(class_name_str, true):
|
|
419
485
|
var sig_args: Array[Dictionary] = []
|
|
420
486
|
var declared_args: Array = s.get("args", [])
|
|
421
487
|
for a: Dictionary in declared_args:
|
|
@@ -58,12 +58,70 @@ static func enum_named(choices: Dictionary, word: String) -> Variant:
|
|
|
58
58
|
return null
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
## The values of the engine enum [param property] of [param target] holds, by the constants its
|
|
62
|
+
## class declares them as: blend_mode's Add is BLEND_MODE_ADD in a script, and the hint gives only
|
|
63
|
+
## the inspector's word. Empty for an enum a script declares.
|
|
64
|
+
##
|
|
65
|
+
## The enum is named by the setter's argument, as Node.ProcessMode, since the property list gives
|
|
66
|
+
## an engine enum property no class of its own.
|
|
67
|
+
static func engine_constants(target: Object, property: String) -> Dictionary:
|
|
68
|
+
var values: Dictionary = {}
|
|
69
|
+
var engine_class: String = target.get_class()
|
|
70
|
+
var setter: String = str(ClassDB.class_get_property_setter(engine_class, property))
|
|
71
|
+
if setter.is_empty():
|
|
72
|
+
return values
|
|
73
|
+
var named: String = ""
|
|
74
|
+
for method: Dictionary in ClassDB.class_get_method_list(engine_class):
|
|
75
|
+
var arguments: Array = method.get("args", [])
|
|
76
|
+
if str(method.get("name", "")) == setter and not arguments.is_empty():
|
|
77
|
+
var value_argument: Dictionary = arguments[-1]
|
|
78
|
+
named = str(value_argument.get("class_name", ""))
|
|
79
|
+
break
|
|
80
|
+
var holder: String = named.get_slice(".", 0)
|
|
81
|
+
if named.count(".") != 1 or not ClassDB.class_exists(holder):
|
|
82
|
+
return values
|
|
83
|
+
for constant: String in ClassDB.class_get_enum_constants(holder, named.get_slice(".", 1)):
|
|
84
|
+
values[constant] = ClassDB.class_get_integer_constant(holder, constant)
|
|
85
|
+
return values
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## [param word] as the number of the value the enum property [param declared] of [param target]
|
|
89
|
+
## names by it, as [true, number], or [false, why] naming the values, with [param holder] naming the
|
|
90
|
+
## target in the refusal. [false, ""] for a word that is a number, which converts as one.
|
|
91
|
+
##
|
|
92
|
+
## An enum is written by the name of a value as well as its number, since the name is what the script
|
|
93
|
+
## and the inspector show and the number is only where the value happens to sit. Tried as the script
|
|
94
|
+
## spells it, then as the hint does, then as the engine's constant.
|
|
95
|
+
static func enum_by_name(target: Object, declared: Dictionary, word: String, holder: String) -> Array:
|
|
96
|
+
var property: String = str(declared.get("name", ""))
|
|
97
|
+
var choices: Dictionary = enum_of(target, declared)
|
|
98
|
+
var chosen: Variant = enum_named(choices, word)
|
|
99
|
+
if chosen == null:
|
|
100
|
+
chosen = enum_named(enum_values(str(declared.get("hint_string", ""))), word)
|
|
101
|
+
var constants: Dictionary = {}
|
|
102
|
+
if chosen == null:
|
|
103
|
+
constants = engine_constants(target, property)
|
|
104
|
+
chosen = enum_named(constants, word)
|
|
105
|
+
if chosen != null:
|
|
106
|
+
return [true, chosen]
|
|
107
|
+
if word.strip_edges().is_valid_int():
|
|
108
|
+
return [false, ""]
|
|
109
|
+
return [false, enum_refusal(holder, property, word, choices, constants)]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
static func enum_refusal(
|
|
113
|
+
holder: String, property: String, word: String, choices: Dictionary, constants: Dictionary = {}
|
|
114
|
+
) -> String:
|
|
115
|
+
var ways: String = "by name or by number"
|
|
116
|
+
if not constants.is_empty():
|
|
117
|
+
ways = "by name, by number or by the engine's constant, such as %s" % str(constants.keys()[0])
|
|
62
118
|
var said: String = (
|
|
63
|
-
"%s.%s is an enum, and %s is none of its values: send %s
|
|
64
|
-
% [holder, property, word, _listed(choices)]
|
|
119
|
+
"%s.%s is an enum, and %s is none of its values: send %s %s"
|
|
120
|
+
% [holder, property, word, _listed(choices), ways]
|
|
65
121
|
)
|
|
66
|
-
var
|
|
122
|
+
var named: PackedStringArray = PackedStringArray(choices.keys())
|
|
123
|
+
named.append_array(PackedStringArray(constants.keys()))
|
|
124
|
+
var close: PackedStringArray = GdhRead.nearest(word, named)
|
|
67
125
|
if close.is_empty():
|
|
68
126
|
return said + "."
|
|
69
127
|
return said + ". The nearest %s %s." % ["is" if close.size() == 1 else "are", ", ".join(close)]
|
|
@@ -76,6 +134,17 @@ static func outside(target: Object, declared: Dictionary, value: Variant, shown:
|
|
|
76
134
|
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
77
135
|
var hint_string: String = str(declared.get("hint_string", ""))
|
|
78
136
|
var property: String = str(declared.get("name", ""))
|
|
137
|
+
if hint == PROPERTY_HINT_ENUM and (typeof(value) == TYPE_STRING or typeof(value) == TYPE_STRING_NAME):
|
|
138
|
+
var names: PackedStringArray = PackedStringArray(enum_values(hint_string).keys())
|
|
139
|
+
if names.is_empty() or names.has(str(value)):
|
|
140
|
+
return ""
|
|
141
|
+
return (
|
|
142
|
+
(
|
|
143
|
+
"%s is none of the names %s lists: %s, which the inspector holds it to; the engine "
|
|
144
|
+
+ "stores it as given."
|
|
145
|
+
)
|
|
146
|
+
% [shown, property, ", ".join(names)]
|
|
147
|
+
)
|
|
79
148
|
if typeof(value) != TYPE_INT and typeof(value) != TYPE_FLOAT:
|
|
80
149
|
return ""
|
|
81
150
|
var number: float = GdhRead.as_float(value)
|
|
@@ -114,3 +183,111 @@ static func _listed(choices: Dictionary) -> String:
|
|
|
114
183
|
## A range's end as the script wrote it: the engine's hint gives @export_range(0, 10) as 0.0 and 10.0.
|
|
115
184
|
static func _bound(end: float) -> String:
|
|
116
185
|
return str(int(end)) if end == floorf(end) and absf(end) < 1e15 else str(end)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
## Puts back on [param built] what its script set as it was attached and was not among
|
|
189
|
+
## [param wanted], to the defaults it declares, outside the editor. Attaching runs the script's _init
|
|
190
|
+
## there, which the editor never runs for a script that is not a tool, so a resource_name _init chose
|
|
191
|
+
## was saved as if asked for. A property whose default the engine cannot report is left as made.
|
|
192
|
+
static func unmade(built: Resource, wanted: Dictionary) -> void:
|
|
193
|
+
var attached: Variant = built.get_script()
|
|
194
|
+
if Engine.is_editor_hint() or not attached is Script:
|
|
195
|
+
return
|
|
196
|
+
var script: Script = attached
|
|
197
|
+
var asked: Dictionary = {}
|
|
198
|
+
for key: Variant in wanted:
|
|
199
|
+
asked[str(key).get_slice(":", 0)] = true
|
|
200
|
+
for entry: Dictionary in built.get_property_list():
|
|
201
|
+
var name: String = str(entry["name"])
|
|
202
|
+
var usage: int = GdhRead.as_int(entry["usage"])
|
|
203
|
+
if usage & PROPERTY_USAGE_STORAGE == 0 or name == "script" or asked.has(name):
|
|
204
|
+
continue
|
|
205
|
+
var declared: Variant = ClassDB.class_get_property_default_value(built.get_class(), name)
|
|
206
|
+
if usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
|
|
207
|
+
declared = script.get_property_default_value(name)
|
|
208
|
+
var held: Variant = built.get(name)
|
|
209
|
+
if declared != null and not (typeof(held) == typeof(declared) and held == declared):
|
|
210
|
+
built.set(name, declared)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
## The element types a typed Array or Dictionary property declares: [element] for an array,
|
|
214
|
+
## [key, value] for a dictionary, [] when nothing is declared.
|
|
215
|
+
##
|
|
216
|
+
## Read from the declaration because the value cannot say: a script's exported `Array[int]` sits
|
|
217
|
+
## behind a placeholder in the editor, and measured on 4.7.2 that placeholder stores [1.5, 2.0]
|
|
218
|
+
## as given, which the game then fails to load into the typed array.
|
|
219
|
+
static func elements_of(declared: Dictionary) -> Array:
|
|
220
|
+
var type: int = GdhRead.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
|
|
221
|
+
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
222
|
+
var wanted: String = str(declared.get("hint_string", ""))
|
|
223
|
+
if wanted.is_empty():
|
|
224
|
+
return []
|
|
225
|
+
if hint == PROPERTY_HINT_TYPE_STRING:
|
|
226
|
+
var parts: PackedStringArray = wanted.split(";")
|
|
227
|
+
if type == TYPE_ARRAY and parts.size() == 1:
|
|
228
|
+
return [_type_in_hint(parts[0])]
|
|
229
|
+
if type == TYPE_DICTIONARY and parts.size() == 2:
|
|
230
|
+
return [_type_in_hint(parts[0]), _type_in_hint(parts[1])]
|
|
231
|
+
return []
|
|
232
|
+
if hint == PROPERTY_HINT_ARRAY_TYPE and type == TYPE_ARRAY:
|
|
233
|
+
return [_type_named(wanted)]
|
|
234
|
+
if hint == PROPERTY_HINT_DICTIONARY_TYPE and type == TYPE_DICTIONARY:
|
|
235
|
+
var parts: PackedStringArray = wanted.split(";")
|
|
236
|
+
if parts.size() == 2:
|
|
237
|
+
return [_type_named(parts[0]), _type_named(parts[1])]
|
|
238
|
+
return []
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## The type at the front of one entry of a type-string hint, "2:" or "24/17:Texture2D".
|
|
242
|
+
static func _type_in_hint(entry: String) -> int:
|
|
243
|
+
var number: String = entry.get_slice(":", 0).get_slice("/", 0)
|
|
244
|
+
return number.to_int() if number.is_valid_int() else TYPE_NIL
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
## The Variant type [param name] names, or TYPE_OBJECT for a class.
|
|
248
|
+
static func _type_named(name: String) -> int:
|
|
249
|
+
var named: String = name.get_slice(":", 0).strip_edges()
|
|
250
|
+
for type: int in TYPE_MAX:
|
|
251
|
+
if type_string(type) == named:
|
|
252
|
+
return type
|
|
253
|
+
return TYPE_OBJECT if not named.is_empty() else TYPE_NIL
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
## The class the objects of a typed list or map hold, and whether they are nodes, as
|
|
257
|
+
## [class, nodes], read from entry [param at] of [param declared]'s hint: "24/17:Item" in a type
|
|
258
|
+
## string, "Item" in an array or dictionary type.
|
|
259
|
+
static func object_kind(declared: Dictionary, at: int) -> Array:
|
|
260
|
+
var entry: String = str(declared.get("hint_string", "")).get_slice(";", at)
|
|
261
|
+
if GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE)) != PROPERTY_HINT_TYPE_STRING:
|
|
262
|
+
return [entry.strip_edges(), false]
|
|
263
|
+
var nodes: bool = entry.get_slice(":", 0).get_slice("/", 1) == str(PROPERTY_HINT_NODE_TYPE)
|
|
264
|
+
return [entry.get_slice(":", 1), nodes]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## The base class and script that type a list or map holding [param wanted], as [class, script],
|
|
268
|
+
## or [] when nothing in the project or the engine is called that.
|
|
269
|
+
static func object_typing(wanted: String) -> Array:
|
|
270
|
+
if ClassDB.class_exists(wanted):
|
|
271
|
+
return [StringName(wanted), null]
|
|
272
|
+
var script: Script = script_class(wanted)
|
|
273
|
+
return [] if script == null else [script.get_instance_base_type(), script]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## The script declaring the class_name [param name], or null.
|
|
277
|
+
static func script_class(name: String) -> Script:
|
|
278
|
+
for entry: Dictionary in ProjectSettings.get_global_class_list():
|
|
279
|
+
if str(entry.get("class", "")) == name:
|
|
280
|
+
return load(str(entry.get("path", ""))) as Script
|
|
281
|
+
return null
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
## Whether [param value] is the class [param name] names, an engine class or a script's class_name.
|
|
285
|
+
static func is_a(value: Object, name: String) -> bool:
|
|
286
|
+
if value.is_class(name):
|
|
287
|
+
return true
|
|
288
|
+
var script: Script = value.get_script()
|
|
289
|
+
while script != null:
|
|
290
|
+
if str(script.get_global_name()) == name:
|
|
291
|
+
return true
|
|
292
|
+
script = script.get_base_script()
|
|
293
|
+
return false
|
|
@@ -58,47 +58,21 @@ func get_dependencies(params: Dictionary) -> Dictionary:
|
|
|
58
58
|
var max_depth: int = depth if depth > 0 else 1000
|
|
59
59
|
var include_built_in: bool = GdhRead.as_bool(params.get("include_builtin", false))
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
(
|
|
63
|
-
|
|
64
|
-
+ (" for: " + resource_path if not resource_path.is_empty() else " for all resources")
|
|
65
|
-
)
|
|
66
|
-
)
|
|
61
|
+
if resource_path.is_empty():
|
|
62
|
+
return _log.failure("resource_path is required")
|
|
63
|
+
_log.info("Getting dependencies for: " + resource_path)
|
|
67
64
|
|
|
68
65
|
var dependencies: Dictionary = {}
|
|
69
66
|
var circular_references: Array = []
|
|
70
|
-
var
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
_files.with_nearest_files("Resource file does not exist: " + full_path, full_path)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
var walk: GdhDependencyWalk = GdhDependencyWalk.new(max_depth, include_built_in, circular_references)
|
|
83
|
-
dependencies[full_path] = _analyze_resource(full_path, 0, walk)
|
|
84
|
-
total_resources = 1
|
|
85
|
-
else:
|
|
86
|
-
var resource_extensions: Array[String] = ["tscn", "tres", "gd", "gdshader", "shader"]
|
|
87
|
-
var all_resources: Array[String] = []
|
|
88
|
-
for ext: String in resource_extensions:
|
|
89
|
-
all_resources.append_array(_files.find_files("res://", "." + ext))
|
|
90
|
-
|
|
91
|
-
# Each root gets a fresh walk so a cycle is reported from every resource it passes
|
|
92
|
-
# through, rather than only from whichever one happened to be walked first.
|
|
93
|
-
for res_path: String in all_resources:
|
|
94
|
-
var walk: GdhDependencyWalk = GdhDependencyWalk.new(
|
|
95
|
-
max_depth, include_built_in, circular_references
|
|
96
|
-
)
|
|
97
|
-
var deps: Array[Dictionary] = _analyze_resource(res_path, 0, walk)
|
|
98
|
-
if deps.size() > 0:
|
|
99
|
-
dependencies[res_path] = deps
|
|
100
|
-
|
|
101
|
-
total_resources = all_resources.size()
|
|
67
|
+
var full_path: String = resource_path
|
|
68
|
+
if not full_path.begins_with("res://"):
|
|
69
|
+
full_path = "res://" + full_path
|
|
70
|
+
if not FileAccess.file_exists(full_path):
|
|
71
|
+
return _log.failure(
|
|
72
|
+
_files.with_nearest_files("Resource file does not exist: " + full_path, full_path)
|
|
73
|
+
)
|
|
74
|
+
var walk: GdhDependencyWalk = GdhDependencyWalk.new(max_depth, include_built_in, circular_references)
|
|
75
|
+
dependencies[full_path] = _analyze_resource(full_path, 0, walk)
|
|
102
76
|
|
|
103
77
|
var references: int = 0
|
|
104
78
|
var distinct: Dictionary = {}
|
|
@@ -121,7 +95,8 @@ func get_dependencies(params: Dictionary) -> Dictionary:
|
|
|
121
95
|
"circular_references": circular_references,
|
|
122
96
|
"summary":
|
|
123
97
|
{
|
|
124
|
-
|
|
98
|
+
# The file the walk starts from, which a call always names one of.
|
|
99
|
+
"total_resources": 1,
|
|
125
100
|
# The files depended on, each once. The tree names a file again under everything that
|
|
126
101
|
# loads it, and counting its entries answered 11 for a scene with six dependencies.
|
|
127
102
|
"total_dependencies": distinct.size(),
|
|
@@ -10,6 +10,7 @@ extends RefCounted
|
|
|
10
10
|
const GdhAnimationTools = preload("../addons/gdharness_editor/tools/animation_tools.gd")
|
|
11
11
|
const GdhResourceTools = preload("../addons/gdharness_editor/tools/resource_tools.gd")
|
|
12
12
|
const GdhSceneTools = preload("../addons/gdharness_editor/tools/scene_tools.gd")
|
|
13
|
+
const GdhSignalTools = preload("../addons/gdharness_editor/tools/signal_tools.gd")
|
|
13
14
|
const GdhSceneFile = preload("../addons/gdharness_editor/scene_file.gd")
|
|
14
15
|
|
|
15
16
|
const SCENE_COMMANDS: Array[String] = [
|
|
@@ -21,10 +22,8 @@ const SCENE_COMMANDS: Array[String] = [
|
|
|
21
22
|
"duplicate_node",
|
|
22
23
|
"reparent_node",
|
|
23
24
|
"delete_node",
|
|
24
|
-
"connect_signal",
|
|
25
|
-
"disconnect_signal",
|
|
26
|
-
"list_connections",
|
|
27
25
|
]
|
|
26
|
+
const SIGNAL_COMMANDS: Array[String] = ["connect_signal", "disconnect_signal", "list_connections"]
|
|
28
27
|
const RESOURCE_COMMANDS: Array[String] = [
|
|
29
28
|
"set_tilemap_cells",
|
|
30
29
|
"create_resource",
|
|
@@ -55,6 +54,8 @@ static func run(params: Dictionary) -> Dictionary:
|
|
|
55
54
|
var tools: Node = null
|
|
56
55
|
if command in SCENE_COMMANDS:
|
|
57
56
|
tools = GdhSceneTools.new()
|
|
57
|
+
elif command in SIGNAL_COMMANDS:
|
|
58
|
+
tools = GdhSignalTools.new()
|
|
58
59
|
elif command in RESOURCE_COMMANDS:
|
|
59
60
|
tools = GdhResourceTools.new()
|
|
60
61
|
elif command in ANIMATION_COMMANDS:
|
|
@@ -10,8 +10,8 @@ extends RefCounted
|
|
|
10
10
|
## without making a node. Loading the file still does what loading does in the game: static
|
|
11
11
|
## initialisers run, and a resource with a script is made with it.
|
|
12
12
|
|
|
13
|
-
const GdhPropertyValues = preload("property_values.gd")
|
|
14
13
|
const GdhRead = preload("reading.gd")
|
|
14
|
+
const GdhSame = preload("same_values.gd")
|
|
15
15
|
const GdhSerialisation = preload("serialisation.gd")
|
|
16
16
|
|
|
17
17
|
|
|
@@ -188,6 +188,12 @@ static func _lay(
|
|
|
188
188
|
path,
|
|
189
189
|
state.get_node_index(index)
|
|
190
190
|
)
|
|
191
|
+
# Every file on the way puts the node in its own groups: the scene making it, and each scene
|
|
192
|
+
# instancing or inheriting that one.
|
|
193
|
+
var grouped: Array[String] = nodes[path]["groups"]
|
|
194
|
+
for group: StringName in state.get_node_groups(index):
|
|
195
|
+
if not grouped.has(str(group)):
|
|
196
|
+
grouped.append(str(group))
|
|
191
197
|
var held: Dictionary = nodes[path]["properties"]
|
|
192
198
|
for property: int in state.get_node_property_count(index):
|
|
193
199
|
held[str(state.get_node_property_name(index, property))] = state.get_node_property_value(
|
|
@@ -281,7 +287,8 @@ static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
|
|
|
281
287
|
|
|
282
288
|
static func _made(type: String) -> Dictionary:
|
|
283
289
|
var children: Array[String] = []
|
|
284
|
-
|
|
290
|
+
var groups: Array[String] = []
|
|
291
|
+
return {"name": "", "type": type, "properties": {}, "children": children, "groups": groups}
|
|
285
292
|
|
|
286
293
|
|
|
287
294
|
## Puts [param path] among [param parent]'s children, at [param at] when the file says where (a node
|
|
@@ -317,6 +324,11 @@ static func _entry(
|
|
|
317
324
|
data["instanceOf"] = node["instanceOf"]
|
|
318
325
|
if node.has("editableChildren"):
|
|
319
326
|
data["editableChildren"] = node["editableChildren"]
|
|
327
|
+
var groups: Array[String] = node["groups"]
|
|
328
|
+
if not groups.is_empty():
|
|
329
|
+
var sorted: Array[String] = groups.duplicate()
|
|
330
|
+
sorted.sort()
|
|
331
|
+
data["groups"] = sorted
|
|
320
332
|
if include_properties:
|
|
321
333
|
var given: Dictionary = node["properties"]
|
|
322
334
|
data["properties"] = _stored(str(node["type"]), given)
|
|
@@ -419,7 +431,7 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
419
431
|
if scripted.has(property)
|
|
420
432
|
else ClassDB.class_get_property_default_value(loaded.get_class(), property)
|
|
421
433
|
)
|
|
422
|
-
if
|
|
434
|
+
if GdhSame.same(value, default):
|
|
423
435
|
continue
|
|
424
436
|
properties[property] = serialiser.serialize_value(value)
|
|
425
437
|
var answer: Dictionary = {
|