gdharness 1.3.2 → 1.3.4

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.
Files changed (46) hide show
  1. package/build/cli.js +163 -85
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +14 -11
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +157 -4
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
  5. package/build/godot/addons/gdharness_editor/file_walk.gd +134 -0
  6. package/build/godot/addons/gdharness_editor/file_walk.gd.uid +1 -0
  7. package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
  8. package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
  9. package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
  10. package/build/godot/addons/gdharness_editor/property_values.gd +249 -227
  11. package/build/godot/addons/gdharness_editor/reading.gd +15 -0
  12. package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
  13. package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +100 -15
  15. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
  16. package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -3
  17. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +402 -55
  18. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
  19. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
  20. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
  21. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +88 -5
  22. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +102 -337
  23. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
  24. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
  25. package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
  26. package/build/godot/addons/gdharness_runtime/runtime_input.gd +10 -26
  27. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
  28. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
  29. package/build/godot/addons/gdharness_runtime/runtime_values.gd +30 -0
  30. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
  31. package/build/godot/operations/animation_reads.gd +14 -11
  32. package/build/godot/operations/declared_values.gd +157 -4
  33. package/build/godot/operations/dependencies.gd +14 -39
  34. package/build/godot/operations/editor_tools.gd +7 -3
  35. package/build/godot/operations/file_reads.gd +15 -3
  36. package/build/godot/operations/file_walk.gd +1 -0
  37. package/build/godot/operations/gdscript_analysis.gd +229 -75
  38. package/build/godot/operations/gdscript_authoring.gd +142 -34
  39. package/build/godot/operations/import_pipeline.gd +17 -6
  40. package/build/godot/operations/logger.gd +7 -0
  41. package/build/godot/operations/property_paths.gd +225 -0
  42. package/build/godot/operations/property_values.gd +249 -227
  43. package/build/godot/operations/reading.gd +15 -0
  44. package/build/godot/operations/same_values.gd +177 -0
  45. package/build/godot/operations/serialisation.gd +75 -3
  46. package/package.json +1 -1
@@ -0,0 +1,225 @@
1
+ @tool
2
+ extends RefCounted
3
+
4
+ ## Where a property a tool writes lives: on the object given, on a resource built into it, or on an
5
+ ## element of one of its lists that the write has to make first.
6
+
7
+ const GdhRead = preload("reading.gd")
8
+
9
+ const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
10
+
11
+
12
+ ## The entry [param target] declares for [param property], or {} when it declares none, headings
13
+ ## left out.
14
+ static func declared_on(target: Object, property: String) -> Dictionary:
15
+ for declared: Dictionary in target.get_property_list():
16
+ if str(declared.get("name", "")) != property:
17
+ continue
18
+ if GdhRead.as_int(declared.get("usage", 0)) & HEADINGS:
19
+ continue
20
+ return declared
21
+ return {}
22
+
23
+
24
+ ## Where [param key] is written: as [holder, name, through], the object holding the property, its
25
+ ## name there, and the resources passed on the way. A key that is none of [param target]'s own may
26
+ ## start with the name of one holding a resource built into it, then a slash and a key of that
27
+ ## resource: "sources/0/0:0/0/probability" is the probability of tile 0:0 in the atlas source a
28
+ ## TileSet keeps as sources/0. A colon path cannot reach it, since the tile's own name holds colons.
29
+ ## A resource kept in a file of its own is not reached, nor, given the file [param home] that
30
+ ## [param target] is saved in, one built into another file, since writing it here would leave that
31
+ ## file as it was.
32
+ static func holder_of(target: Object, key: String, home: String = "") -> Array:
33
+ var through: Array[Resource] = []
34
+ var holder: Object = target
35
+ var name: String = key
36
+ while declared_on(holder, name).is_empty():
37
+ var reached: Resource = null
38
+ var at: int = name.find("/")
39
+ while at > 0:
40
+ var held: Resource = built_in_at(holder, name.left(at), home)
41
+ if held != null:
42
+ reached = held
43
+ break
44
+ at = name.find("/", at + 1)
45
+ if reached == null:
46
+ break
47
+ through.append(reached)
48
+ holder = reached
49
+ name = name.substr(at + 1)
50
+ return [holder, name, through]
51
+
52
+
53
+ ## The resource [param target] holds under [param property] when it is built into a file, or null;
54
+ ## given [param home], only one built into that file or into none yet.
55
+ static func built_in_at(target: Object, property: String, home: String = "") -> Resource:
56
+ var held: Resource = held_at(target, property)
57
+ if held == null or not held.is_built_in():
58
+ return null
59
+ var file: String = held.resource_path.get_slice("::", 0)
60
+ return held if home.is_empty() or file.is_empty() or file == home else null
61
+
62
+
63
+ ## The file [param target] is saved in: its scene's for a node, as the nodes of an instance are
64
+ ## saved in the scene holding it, and its own for a resource; "" when it has none yet.
65
+ static func home_of(target: Object) -> String:
66
+ if target is Node:
67
+ var top: Node = target
68
+ while top.owner != null:
69
+ top = top.owner
70
+ return top.scene_file_path
71
+ if target is Resource:
72
+ var resource: Resource = target
73
+ return resource.resource_path.get_slice("::", 0)
74
+ return ""
75
+
76
+
77
+ ## The resource [param target] holds under [param property], built in or kept in a file, or null.
78
+ static func held_at(target: Object, property: String) -> Resource:
79
+ if declared_on(target, property).is_empty():
80
+ return null
81
+ var held: Variant = target.get(property)
82
+ return held if held is Resource else null
83
+
84
+
85
+ ## Makes the element of a list [param name] names on [param target] when it is the next one, as the
86
+ ## inspector's Add Element does, and answers "" (also when [param name] is in no list), or why not,
87
+ ## [param called] being what [param target] is called. The engine lists an element's properties
88
+ ## only once the element exists, so a TileSet with no physics layer had no
89
+ ## physics_layer_0/collision_layer to write. A list the engine declares as an array grows through
90
+ ## the method named after its elements, add_physics_layer for physics_layer_, given the indices of
91
+ ## the lists around it; a list counted by a property, as a tile's polygons are by polygons_count,
92
+ ## grows by raising the count.
93
+ static func grow(target: Object, name: String, called: String) -> String:
94
+ var steps: PackedStringArray = name.split("/")
95
+ for depth: int in steps.size() - 1:
96
+ var step: String = steps[depth]
97
+ var cut: int = step.rfind("_")
98
+ if cut < 1 or not step.substr(cut + 1).is_valid_int():
99
+ continue
100
+ var index: int = step.substr(cut + 1).to_int()
101
+ var above: String = "/".join(steps.slice(0, depth))
102
+ var under: String = above + "/" if not above.is_empty() else ""
103
+ var prefix: String = under + step.left(cut + 1)
104
+ var element: String = step.left(cut)
105
+ var counted: String = under + element + "s_count"
106
+ var count: int = -1
107
+ var adding: Callable
108
+ if not declared_on(target, counted).is_empty():
109
+ count = GdhRead.as_int(target.get(counted))
110
+ adding = func() -> void: target.set(counted, count + 1)
111
+ elif _listed_array(target, prefix):
112
+ count = _elements(target, prefix)
113
+ var method: String = "add_" + element
114
+ var outer: Array = []
115
+ for enclosing: String in steps.slice(0, depth):
116
+ var at: int = enclosing.rfind("_")
117
+ if at > 0 and enclosing.substr(at + 1).is_valid_int():
118
+ outer.append(enclosing.substr(at + 1).to_int())
119
+ if target.has_method(method):
120
+ adding = func() -> void: target.callv(method, outer)
121
+ if count < 0 or index < count:
122
+ continue
123
+ var held: String = (
124
+ "%s has %d %s%s"
125
+ % [
126
+ above if not above.is_empty() else called,
127
+ count,
128
+ element.replace("_", " "),
129
+ "" if count == 1 else "s"
130
+ ]
131
+ )
132
+ if index > count:
133
+ return (
134
+ "%s, and a new one goes after the last, so %s%d cannot be written yet: write %s%d first."
135
+ % [held, prefix, index, prefix, count]
136
+ )
137
+ if not adding.is_valid():
138
+ return "%s, and nothing makes another, so %s cannot be written." % [held, name]
139
+ adding.call()
140
+ return ""
141
+ return ""
142
+
143
+
144
+ ## Whether [param target] declares a list whose elements' properties start with [param prefix],
145
+ ## as TileSet declares physics_layers for physics_layer_0/collision_layer.
146
+ static func _listed_array(target: Object, prefix: String) -> bool:
147
+ for declared: Dictionary in target.get_property_list():
148
+ var usage: int = GdhRead.as_int(declared.get("usage", 0))
149
+ if usage & PROPERTY_USAGE_ARRAY and str(declared.get("class_name", "")) == prefix:
150
+ return true
151
+ return false
152
+
153
+
154
+ ## How many elements of the list whose properties start with [param prefix] [param target] holds.
155
+ static func _elements(target: Object, prefix: String) -> int:
156
+ var count: int = 0
157
+ for declared: Dictionary in target.get_property_list():
158
+ var name: String = str(declared.get("name", ""))
159
+ if not name.begins_with(prefix):
160
+ continue
161
+ var index: String = name.substr(prefix.length()).get_slice("/", 0)
162
+ if index.is_valid_int():
163
+ count = maxi(count, index.to_int() + 1)
164
+ return count
165
+
166
+
167
+ ## What [param target] is called in a refusal: the class_name its script declares, or else its
168
+ ## engine class. A resource made as a project class is a Resource to the engine, and "Resource.kind"
169
+ ## named a property no Resource has.
170
+ static func class_called(target: Object) -> String:
171
+ var attached: Variant = target.get_script()
172
+ if attached is Script:
173
+ var script: Script = attached
174
+ if not str(script.get_global_name()).is_empty():
175
+ return str(script.get_global_name())
176
+ return target.get_class()
177
+
178
+
179
+ ## The refusal for [param name], which [param target] does not declare, when it runs through a
180
+ ## resource saved in a file other than [param home], the one [param target] is saved in; or "". The
181
+ ## write belongs in that file, since this one would be saved and that one left as it was: through a
182
+ ## TileMapLayer's tile_set kept in a file of its own, or through the mesh of an instance's editable
183
+ ## child, which is built into the scene the instance comes from.
184
+ static func held_elsewhere(target: Object, name: String, home: String) -> String:
185
+ var at: int = name.find("/")
186
+ while at > 0:
187
+ var head: String = name.left(at)
188
+ var rest: String = name.substr(at + 1)
189
+ var held: Resource = held_at(target, head)
190
+ var file: String = "" if held == null else held.resource_path.get_slice("::", 0)
191
+ if held != null and not held.is_built_in():
192
+ return (
193
+ "%s.%s is %s, a file of its own, so %s is written there, with resource_edit modify on %s."
194
+ % [class_called(target), head, file, rest, file]
195
+ )
196
+ if held != null and not file.is_empty() and not home.is_empty() and file != home:
197
+ return _built_in_elsewhere(target, head, rest, held, file)
198
+ at = name.find("/", at + 1)
199
+ return ""
200
+
201
+
202
+ ## The refusal for writing [param rest] into [param held], which [param target] holds under
203
+ ## [param head] and which is built into [param file], the scene an instance in this one comes from.
204
+ static func _built_in_elsewhere(
205
+ target: Object, head: String, rest: String, held: Resource, file: String
206
+ ) -> String:
207
+ var node: String = ""
208
+ if target is Node:
209
+ var placed: Node = target
210
+ var from: Node = placed
211
+ while from != null and from.scene_file_path != file:
212
+ from = from.owner
213
+ if from != null:
214
+ node = " on %s" % from.get_path_to(placed)
215
+ var text: String = (
216
+ (
217
+ "%s.%s is built into %s, so %s is written there, with scene_node set%s in %s, which changes "
218
+ + "it wherever that scene is used. To change it here alone, give %s a %s of its own: a "
219
+ + 'value with "_type" set to its class and its properties beside it.'
220
+ )
221
+ % [class_called(target), head, file, rest, node, file, head, held.get_class()]
222
+ )
223
+ if target is GeometryInstance3D and (held is Material or rest.contains("material")):
224
+ text += " A material for this instance alone goes in material_override."
225
+ return text
@@ -0,0 +1 @@
1
+ uid://mi7vnabydtlw