gdharness 1.3.41 → 1.3.43

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 (107) hide show
  1. package/build/cli.js +131 -47
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +37 -12
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
  33. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
  35. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  36. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  37. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  38. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  39. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  40. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  41. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  42. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  43. package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
  44. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  45. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
  46. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  47. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  48. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  49. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  50. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  51. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  52. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  53. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
  54. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  55. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  56. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  57. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  58. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  59. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  60. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
  61. package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
  62. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  63. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  64. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  65. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  66. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  67. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  68. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  69. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  70. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  71. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  72. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  73. package/build/godot/loader/loader.gd +9 -9
  74. package/build/godot/operations/animation_reads.gd +84 -35
  75. package/build/godot/operations/audio_buses.gd +48 -19
  76. package/build/godot/operations/class_cache.gd +16 -11
  77. package/build/godot/operations/classdb_queries.gd +89 -44
  78. package/build/godot/operations/declared_values.gd +68 -32
  79. package/build/godot/operations/dependencies.gd +139 -93
  80. package/build/godot/operations/editor_tools.gd +9 -10
  81. package/build/godot/operations/export_presets.gd +100 -54
  82. package/build/godot/operations/file_reads.gd +114 -39
  83. package/build/godot/operations/file_walk.gd +12 -5
  84. package/build/godot/operations/gdscript_analysis.gd +105 -66
  85. package/build/godot/operations/gdscript_authoring.gd +138 -39
  86. package/build/godot/operations/godot_operations.gd +17 -11
  87. package/build/godot/operations/import_pipeline.gd +110 -59
  88. package/build/godot/operations/input_actions.gd +63 -39
  89. package/build/godot/operations/patterns.gd +3 -1
  90. package/build/godot/operations/plugins.gd +36 -16
  91. package/build/godot/operations/project_config.gd +144 -27
  92. package/build/godot/operations/project_diagnostics.gd +24 -11
  93. package/build/godot/operations/project_file.gd +16 -16
  94. package/build/godot/operations/project_validation.gd +71 -44
  95. package/build/godot/operations/property_paths.gd +22 -12
  96. package/build/godot/operations/property_values.gd +143 -78
  97. package/build/godot/operations/reading.gd +37 -12
  98. package/build/godot/operations/rename_checks.gd +2 -2
  99. package/build/godot/operations/resource_files.gd +11 -9
  100. package/build/godot/operations/same_values.gd +4 -4
  101. package/build/godot/operations/scene_connections.gd +27 -13
  102. package/build/godot/operations/script_check.gd +52 -49
  103. package/build/godot/operations/serialisation.gd +75 -51
  104. package/build/godot/operations/setting_reads.gd +7 -6
  105. package/build/godot/operations/value_shapes.gd +5 -6
  106. package/build/godot/tests/listed.gd +321 -268
  107. package/package.json +1 -1
@@ -1,8 +1,7 @@
1
1
  extends RefCounted
2
-
3
2
  ## Where a control drawing a list draws one of its items, for a click by words: a tab, a list entry,
4
- ## a tree row or a menu bar title. Each is drawn by the control rather than held as a node, so a click
5
- ## on the control's path presses its middle, which is whichever item happens to sit there.
3
+ ## a tree row or a menu bar title. Each is drawn by the control rather than held as a node, so a
4
+ ## click on the control's path presses its middle, which is whichever item happens to sit there.
6
5
 
7
6
  const GdhSays = preload("runtime_says.gd")
8
7
  const GdhValues = preload("runtime_values.gd")
@@ -27,12 +26,16 @@ static func item_rect(control: Control, item: Variant, wanted: String) -> Rect2:
27
26
  var list: ItemList = control as ItemList
28
27
  if list != null:
29
28
  var entry: int = item
30
- var scrolled: Vector2 = Vector2(list.get_h_scroll_bar().value, list.get_v_scroll_bar().value)
29
+ var scrolled: Vector2 = Vector2(
30
+ list.get_h_scroll_bar().value, list.get_v_scroll_bar().value
31
+ )
31
32
  var placed: Rect2 = list.get_item_rect(entry, false)
32
33
  placed.position -= scrolled
33
34
  var reached: bool = bounds.has_point(placed.get_center())
34
35
  return (
35
- placed if reached and list.get_item_at_position(placed.get_center(), true) == entry else Rect2()
36
+ placed
37
+ if reached and list.get_item_at_position(placed.get_center(), true) == entry
38
+ else Rect2()
36
39
  )
37
40
  var tree: Tree = control as Tree
38
41
  if tree != null:
@@ -63,9 +66,9 @@ static func tree_column(tree: Tree, row: TreeItem, wanted: String) -> int:
63
66
  ## numbers its items, and what it says.
64
67
  static func item_named(control: Control, item: Variant, says: String) -> Dictionary:
65
68
  if control is Tree:
66
- return {"kind": "row", "says": says}
69
+ return { "kind": "row", "says": says }
67
70
  var kind: String = "tab" if control is TabBar else ("menu" if control is MenuBar else "item")
68
- return {"kind": kind, "index": item, "says": says}
71
+ return { "kind": kind, "index": item, "says": says }
69
72
 
70
73
 
71
74
  ## Where in [param control] to press for [param item], the one of its items a click by
@@ -75,10 +78,17 @@ static func item_named(control: Control, item: Variant, says: String) -> Diction
75
78
  ## a tab or an entry into view before pressing it. A disabled item is refused as a disabled button
76
79
  ## is. A tree row is pressed on the cell saying the words, whose column goes into [param found].
77
80
  static func in_view(
78
- scene: SceneTree, control: Control, node_path: String, item: Variant, wanted: String, found: Dictionary
81
+ scene: SceneTree,
82
+ control: Control,
83
+ node_path: String,
84
+ item: Variant,
85
+ wanted: String,
86
+ found: Dictionary
79
87
  ) -> Dictionary:
80
88
  var named: Dictionary = found.get("item", {})
81
- var naming: String = 'the %s "%s" of %s' % [named.get("kind", "item"), named.get("says", ""), node_path]
89
+ var naming: String = (
90
+ 'the %s "%s" of %s' % [named.get("kind", "item"), named.get("says", ""), node_path]
91
+ )
82
92
  var index: int = item if item is int else -1
83
93
  var bar: TabBar = control as TabBar
84
94
  var list: ItemList = control as ItemList
@@ -88,7 +98,9 @@ static func in_view(
88
98
  or (list != null and list.is_item_disabled(index))
89
99
  or (menus != null and menus.is_menu_disabled(index))
90
100
  ):
91
- return {"type": "error", "message": "%s is disabled, so clicking it presses nothing" % naming}
101
+ return {
102
+ "type": "error", "message": "%s is disabled, so clicking it presses nothing" % naming,
103
+ }
92
104
  var placed: Rect2 = item_rect(control, item, wanted)
93
105
  var scrolled: bool = false
94
106
  if placed.size == Vector2.ZERO:
@@ -101,14 +113,18 @@ static func in_view(
101
113
  return {
102
114
  "type": "error",
103
115
  "message":
104
- "%s went while it was being scrolled into view, so nothing was clicked" % naming
116
+ (
117
+ "%s went while it was being scrolled into view, so nothing was clicked"
118
+ % naming
119
+ ),
105
120
  }
106
121
  placed = item_rect(control, item, wanted)
107
122
  if placed.size == Vector2.ZERO:
108
123
  var tried: String = " even scrolled to" if scrolled else ""
109
124
  return {
110
125
  "type": "error",
111
- "message": "%s is drawn nowhere a pointer reaches%s, so nothing was clicked" % [naming, tried]
126
+ "message":
127
+ "%s is drawn nowhere a pointer reaches%s, so nothing was clicked" % [naming, tried],
112
128
  }
113
129
  var tree: Tree = control as Tree
114
130
  if tree != null:
@@ -116,7 +132,7 @@ static func in_view(
116
132
  var column: int = tree_column(tree, row, wanted)
117
133
  if column != -1:
118
134
  named["column"] = column
119
- return {"point": placed.get_center(), "scrolled": scrolled}
135
+ return { "point": placed.get_center(), "scrolled": scrolled }
120
136
 
121
137
 
122
138
  ## Where [param menus] draws the title of [param menu], worked out as the engine works it out,
@@ -1,5 +1,4 @@
1
1
  extends RefCounted
2
-
3
2
  ## The menu a choice is made from and the item in it, for a choice made by what an item says or by
4
3
  ## where it is in the list.
5
4
 
@@ -1,5 +1,4 @@
1
1
  extends RefCounted
2
-
3
2
  ## What a pointer call names: a point, the stroke of a drag, a mouse button and its mask bit. Read
4
3
  ## here and sent from runtime_input.gd, which keeps where the pointer is.
5
4
 
@@ -29,14 +28,17 @@ static func point_in(params: Dictionary, node: Node, node_path: String, has_item
29
28
  return null
30
29
  if node is Node3D:
31
30
  return (
32
- "%s is a 3D node, clicked where it is drawn; x and y name a point in a Control's own space"
31
+ (
32
+ "%s is a 3D node, clicked where it is drawn; x and y name a point in a Control's "
33
+ + "own space"
34
+ )
33
35
  % node_path
34
36
  )
35
37
  if has_item:
36
38
  return (
37
39
  (
38
- "the words name an item %s draws, which is pressed where it is drawn, and x and y a point in it:"
39
- + " give one of them"
40
+ "the words name an item %s draws, which is pressed where it is drawn, and x and y "
41
+ + "a point in it: give one of them"
40
42
  )
41
43
  % node_path
42
44
  )
@@ -58,17 +60,17 @@ static func aim_of(params: Dictionary, root: Window) -> Variant:
58
60
  return standing["message"]
59
61
  var node: Node = standing["node"]
60
62
  if node is CanvasItem or node is Viewport:
61
- return {"node": node, "path": path}
63
+ return { "node": node, "path": path }
62
64
  return (
63
65
  "%s is a %s, and the points are in a Control's, a Node2D's or a Window's own space"
64
66
  % [path, node.get_class()]
65
67
  )
66
68
 
67
69
 
68
- ## Where [param local], in the space of the node [param aim] names, arrives: {"viewport", "position"},
69
- ## the viewport the pointer reaches it through and the point in that viewport's pixels, which is what
70
- ## an event sent there names. A Window's space is its own pixels, as the game's window pixels are the
71
- ## root's; a canvas item's is its own, the one it draws and reads _gui_input in.
70
+ ## Where [param local], in the space of the node [param aim] names, arrives: {"viewport",
71
+ ## "position"}, the viewport the pointer reaches it through and the point in that viewport's pixels,
72
+ ## which is what an event sent there names. A Window's space is its own pixels, as the game's window
73
+ ## pixels are the root's; a canvas item's is its own, the one it draws and reads _gui_input in.
72
74
  static func carried(aim: Dictionary, local: Vector2, root: Window) -> Dictionary:
73
75
  var node: Node = aim.get("node", root)
74
76
  var reached: Dictionary = {}
@@ -77,10 +79,12 @@ static func carried(aim: Dictionary, local: Vector2, root: Window) -> Dictionary
77
79
  reached = GdhScreen.reach(viewport, viewport.get_final_transform().affine_inverse() * local)
78
80
  else:
79
81
  var item: CanvasItem = node
80
- reached = GdhScreen.reach(item.get_viewport(), item.get_global_transform_with_canvas() * local)
82
+ reached = GdhScreen.reach(
83
+ item.get_viewport(), item.get_global_transform_with_canvas() * local
84
+ )
81
85
  var arriving: Viewport = reached["viewport"]
82
86
  var point: Vector2 = reached["point"]
83
- return {"viewport": arriving, "position": arriving.get_final_transform() * point}
87
+ return { "viewport": arriving, "position": arriving.get_final_transform() * point }
84
88
 
85
89
 
86
90
  ## What a pointer answer says of where [param points] went in [param viewport]: the node they were
@@ -96,12 +100,15 @@ static func where(aim: Dictionary, viewport: Viewport, points: Array[Vector2]) -
96
100
 
97
101
 
98
102
  ## The points of a drag: x, y, then each of via, then toX, toY. Answers the String saying what is
99
- ## wrong instead, since a point left out would otherwise read as the window's corner.
100
- static func stroke_of(params: Dictionary) -> Variant:
103
+ ## wrong instead, since a point left out would otherwise read as the window's corner. [param op] is
104
+ ## the drag the refusal names.
105
+ static func stroke_of(params: Dictionary, op: String = "mouse_drag") -> Variant:
101
106
  for key: String in ["x", "y", "toX", "toY"]:
102
107
  if not params.has(key):
103
- return "mouse_drag needs x, y, toX and toY: %s is missing." % key
104
- var points: Array[Vector2] = [Vector2(GdhRead.as_float(params["x"]), GdhRead.as_float(params["y"]))]
108
+ return "%s needs x, y, toX and toY: %s is missing." % [op, key]
109
+ var points: Array[Vector2] = [
110
+ Vector2(GdhRead.as_float(params["x"]), GdhRead.as_float(params["y"])),
111
+ ]
105
112
  var via: Variant = params.get("via", [])
106
113
  if not via is Array:
107
114
  return "via must be a list of [x, y] points."
@@ -171,7 +178,9 @@ static func button_event(
171
178
  ##
172
179
  ## The points are window pixels and the visible rect is canvas units, which differ by the window's
173
180
  ## content scale and any letterbox offset, so the rect is taken into window pixels first.
174
- static func outside_noted(answer: Dictionary, viewport: Viewport, points: Array[Vector2]) -> Dictionary:
181
+ static func outside_noted(
182
+ answer: Dictionary, viewport: Viewport, points: Array[Vector2]
183
+ ) -> Dictionary:
175
184
  var shown: Rect2 = viewport.get_final_transform() * viewport.get_visible_rect()
176
185
  var which: String = (
177
186
  "the window's" if viewport == viewport.get_tree().root else "%s's" % viewport.get_path()
@@ -195,9 +204,12 @@ static func no_such_button(named: Variant) -> Dictionary:
195
204
  "type": "error",
196
205
  "message":
197
206
  (
198
- "%s is not a mouse button. It takes: left, right, middle, wheel_up, wheel_down, or the number of one."
207
+ (
208
+ "%s is not a mouse button. It takes: left, right, middle, wheel_up, wheel_down, or "
209
+ + "the number of one."
210
+ )
199
211
  % JSON.stringify(named)
200
- )
212
+ ),
201
213
  }
202
214
 
203
215
 
@@ -238,7 +250,10 @@ static func button_of(raw: Variant) -> int:
238
250
  ## whether the game is.
239
251
  static func takes_no_click(control: Control, node_path: String, paused: bool) -> String:
240
252
  if not control.is_visible_in_tree():
241
- return "%s is not visible%s, so nothing can click it" % [node_path, GdhTargets.why_hidden(control)]
253
+ return (
254
+ "%s is not visible%s, so nothing can click it"
255
+ % [node_path, GdhTargets.why_hidden(control)]
256
+ )
242
257
  var button: BaseButton = control as BaseButton
243
258
  if button != null and button.disabled:
244
259
  return "%s is disabled, so clicking it presses nothing" % node_path
@@ -258,7 +273,10 @@ static func takes_no_click(control: Control, node_path: String, paused: bool) ->
258
273
  var cover: String = "" if over == null else "; %s is drawn over it" % over.get_path()
259
274
  if paused:
260
275
  return (
261
- "the game is paused and %s does not process while it is (%s), so clicking it does nothing%s"
276
+ (
277
+ "the game is paused and %s does not process while it is (%s), so clicking it "
278
+ + "does nothing%s"
279
+ )
262
280
  % [node_path, why, cover]
263
281
  )
264
282
  return "%s does not process (%s), so clicking it does nothing%s" % [node_path, why, cover]
@@ -1,5 +1,4 @@
1
1
  extends RefCounted
2
-
3
2
  ## What reaches the running game the way a controller reaches it: a button pressed and a stick or
4
3
  ## trigger moved, as the events a pad sends.
5
4
  ##
@@ -10,8 +9,8 @@ extends RefCounted
10
9
 
11
10
  const GdhRead = preload("reading.gd")
12
11
  const GdhValues = preload("runtime_values.gd")
13
-
14
- ## Controller buttons by the names Godot gives them, for a caller naming one rather than numbering it.
12
+ ## Controller buttons by the names Godot gives them, for a caller naming one rather than numbering
13
+ ## it.
15
14
  const JOY_BUTTONS: Dictionary[String, int] = {
16
15
  "a": JOY_BUTTON_A,
17
16
  "b": JOY_BUTTON_B,
@@ -35,7 +34,6 @@ const JOY_BUTTONS: Dictionary[String, int] = {
35
34
  "paddle4": JOY_BUTTON_PADDLE4,
36
35
  "touchpad": JOY_BUTTON_TOUCHPAD,
37
36
  }
38
-
39
37
  ## Stick and trigger axes by the names Godot gives them.
40
38
  const JOY_AXES: Dictionary[String, int] = {
41
39
  "left_x": JOY_AXIS_LEFT_X,
@@ -53,19 +51,21 @@ func _init(host: Node) -> void:
53
51
  _host = host
54
52
 
55
53
 
56
- ## Presses a controller button, and lets go of it again unless asked to hold it, as a key press does:
57
- ## a button left down is a press that never ends, and everything reading it goes on seeing it.
54
+ ## Presses a controller button, and lets go of it again unless asked to hold it, as a key press
55
+ ## does: a button left down is a press that never ends, and everything reading it goes on seeing it.
58
56
  func inject_joypad_button(params: Dictionary) -> Dictionary:
59
57
  var given: Variant = params.get("button", null)
60
58
  var button: int = _named_or_numbered(given, JOY_BUTTONS, JOY_BUTTON_MAX)
61
59
  if button < 0:
62
- return {"type": "error", "message": _not_one_of("button", given, JOY_BUTTONS, JOY_BUTTON_MAX)}
60
+ return {
61
+ "type": "error", "message": _not_one_of("button", given, JOY_BUTTONS, JOY_BUTTON_MAX),
62
+ }
63
63
  var device: int = GdhRead.as_int(params.get("device", 0), 0)
64
64
  var held: bool = GdhRead.as_bool(params.get("pressed", true), true)
65
65
  var whole: bool = not params.has("pressed")
66
66
  var holding: Variant = GdhValues.held_for(params)
67
67
  if holding is String:
68
- return {"type": "error", "message": holding}
68
+ return { "type": "error", "message": holding }
69
69
  var frames: int = holding
70
70
 
71
71
  var focused: String = GdhValues.focus_path(_host)
@@ -73,8 +73,8 @@ func inject_joypad_button(params: Dictionary) -> Dictionary:
73
73
  Input.parse_input_event(event)
74
74
  var lasted: Variant = null
75
75
  if whole:
76
- # A frame between the halves at least, as a key has, so a game that acts on the press and one
77
- # that acts on the release each get a frame to do it in.
76
+ # A frame between the halves at least, as a key has, so a game that acts on the press and
77
+ # one that acts on the release each get a frame to do it in.
78
78
  lasted = await GdhValues.hold(_host.get_tree(), frames)
79
79
  Input.parse_input_event(_button(button, device, false))
80
80
  # The engine hands a parsed event on at the next frame, so the focus is read after one.
@@ -101,13 +101,17 @@ func inject_joypad_motion(params: Dictionary) -> Dictionary:
101
101
  var given: Variant = params.get("axis", null)
102
102
  var axis: int = _named_or_numbered(given, JOY_AXES, JOY_AXIS_MAX)
103
103
  if axis < 0:
104
- return {"type": "error", "message": _not_one_of("axis", given, JOY_AXES, JOY_AXIS_MAX)}
104
+ return { "type": "error", "message": _not_one_of("axis", given, JOY_AXES, JOY_AXIS_MAX) }
105
105
  if not params.has("value"):
106
- return {"type": "error", "message": "joypad_motion needs value, where the axis is, -1 to 1"}
106
+ return {
107
+ "type": "error",
108
+ "message": "joypad_motion needs value, where the axis is, -1 to 1",
109
+ }
107
110
  var value: float = GdhRead.as_float(params.get("value"), 0.0)
108
111
  if value < -1.0 or value > 1.0:
109
112
  return {
110
- "type": "error", "message": "value %s is outside -1 to 1, the whole travel of an axis" % value
113
+ "type": "error",
114
+ "message": "value %s is outside -1 to 1, the whole travel of an axis" % value,
111
115
  }
112
116
  var device: int = GdhRead.as_int(params.get("device", 0), 0)
113
117
 
@@ -1,5 +1,4 @@
1
1
  extends RefCounted
2
-
3
2
  ## Colon paths through what a node holds: "/root/Main" with "_game:run:day" reaches the day off the
4
3
  ## run off the game the node keeps. Every op that reads, writes, calls or waits on a game's state
5
4
  ## walks the same way, so the walk and what it says when a step is not there live in one place.
@@ -7,7 +6,6 @@ extends RefCounted
7
6
  const GdhRead = preload("reading.gd")
8
7
  const GdhSerialisation = preload("serialisation.gd")
9
8
  const GdhValues = preload("runtime_values.gd")
10
-
11
9
  ## The calls a path makes on a list or a map: those that take no arguments and change nothing.
12
10
  ##
13
11
  ## Named rather than asked of the engine, because asking about a method a list does not have logs an
@@ -50,8 +48,8 @@ const MAP_CALLS: Array[String] = [
50
48
  "get_typed_value_class_name",
51
49
  "get_typed_value_script",
52
50
  ]
53
- ## The calls a path makes on a packed list, which has fewer than a list: measured on 4.7.2, it has no
54
- ## front(), back(), max(), min() or hash().
51
+ ## The calls a path makes on a packed list, which has fewer than a list: measured on 4.7.2, it has
52
+ ## no front(), back(), max(), min() or hash().
55
53
  const PACKED_CALLS: Array[String] = ["size", "is_empty", "duplicate"]
56
54
  ## What a packed list of bytes answers beside those, read as another list or as text. It has no
57
55
  ## to_byte_array(), which every other packed list has.
@@ -109,7 +107,7 @@ const LIST_READS: Array[String] = [
109
107
  "is_same_typed",
110
108
  ]
111
109
  const MAP_CHANGES: Array[String] = [
112
- "clear", "assign", "sort", "merge", "erase", "get_or_add", "set", "make_read_only"
110
+ "clear", "assign", "sort", "merge", "erase", "get_or_add", "set", "make_read_only",
113
111
  ]
114
112
  const MAP_READS: Array[String] = [
115
113
  "merged",
@@ -191,7 +189,8 @@ const VALUE_CALLS: Dictionary = {
191
189
  "max_axis_index",
192
190
  "min_axis_index",
193
191
  ],
194
- TYPE_VECTOR2I: ["length", "length_squared", "abs", "sign", "aspect", "max_axis_index", "min_axis_index"],
192
+ TYPE_VECTOR2I:
193
+ ["length", "length_squared", "abs", "sign", "aspect", "max_axis_index", "min_axis_index"],
195
194
  TYPE_VECTOR3:
196
195
  [
197
196
  "length",
@@ -266,14 +265,16 @@ const VALUE_CALLS: Dictionary = {
266
265
  "get_axis",
267
266
  ],
268
267
  TYPE_PLANE: ["normalized", "is_finite", "get_center"],
269
- TYPE_AABB: ["abs", "is_finite", "get_center", "get_volume", "get_longest_axis", "get_shortest_axis"],
268
+ TYPE_AABB:
269
+ ["abs", "is_finite", "get_center", "get_volume", "get_longest_axis", "get_shortest_axis"],
270
270
  TYPE_PROJECTION: ["inverse", "determinant"],
271
271
  }
272
272
  ## The calls an empty list has nothing to answer, which the engine reports as an error when asked.
273
273
  const NEEDS_AN_ELEMENT: Array[String] = ["front", "back", "pick_random"]
274
-
275
274
  ## Said after every call a path refuses for doing something rather than reading.
276
- const READS_ONLY: String = ", and a path, which a wait reads every frame, only calls methods that read"
275
+ const READS_ONLY: String = (
276
+ ", and a path, which a wait reads every frame, only calls methods that read"
277
+ )
277
278
 
278
279
 
279
280
  ## What [param reaching] names something on, which is the node itself until the name has a colon
@@ -315,8 +316,8 @@ static func walk_to(node: Object, node_path: String, reaching: String) -> Dictio
315
316
  for step: int in parts.size() - 1:
316
317
  var named: String = parts[step]
317
318
  if not can_read(holder, named):
318
- return {"type": "error", "message": nothing_there(holder, named, called)}
319
- chain.append({"holder": holder, "named": named, "called": called})
319
+ return { "type": "error", "message": nothing_there(holder, named, called) }
320
+ chain.append({ "holder": holder, "named": named, "called": called })
320
321
  holder = read_under(holder, named)
321
322
  called = "%s:%s" % [called, named]
322
323
  if not can_hold(holder):
@@ -327,9 +328,9 @@ static func walk_to(node: Object, node_path: String, reaching: String) -> Dictio
327
328
  (
328
329
  "%s holds %s, which has nothing to read %s off"
329
330
  % [called, type_string(typeof(holder)), wanted]
330
- )
331
+ ),
331
332
  }
332
- return {"holder": holder, "name": parts[parts.size() - 1], "called": called, "chain": chain}
333
+ return { "holder": holder, "name": parts[parts.size() - 1], "called": called, "chain": chain }
333
334
 
334
335
 
335
336
  ## The object [param given] names for a method's argument, as `{"object": ...}`, or as
@@ -347,7 +348,7 @@ static func object_at(root: Node, node: Node, node_path: String, given: Variant)
347
348
  (
348
349
  "an object is named by its path, not by the record runtime_inspect answered with:"
349
350
  + " its id does not survive the trip as a number"
350
- )
351
+ ),
351
352
  }
352
353
  var path: String = given
353
354
  var start: Node = node
@@ -358,26 +359,27 @@ static func object_at(root: Node, node: Node, node_path: String, given: Variant)
358
359
  start_path = path if colon == -1 else path.substr(0, colon)
359
360
  var standing: Dictionary = GdhValues.node_at(root, start_path)
360
361
  if standing.has("message"):
361
- return {"message": str(standing["message"])}
362
+ return { "message": str(standing["message"]) }
362
363
  start = standing["node"]
363
364
  if colon == -1:
364
- return {"object": start}
365
+ return { "object": start }
365
366
  reaching = path.substr(colon + 1)
366
367
  if reaching.is_empty():
367
- return {"message": "%s names nothing past the node" % path}
368
+ return { "message": "%s names nothing past the node" % path }
368
369
  var reached: Dictionary = walk_to(start, start_path, reaching)
369
370
  if reached.has("message"):
370
- return {"message": str(reached["message"])}
371
+ return { "message": str(reached["message"]) }
371
372
  var holder: Variant = reached["holder"]
372
373
  var name: String = reached["name"]
373
374
  if not can_read(holder, name):
374
- return {"message": nothing_there(holder, name, str(reached["called"]))}
375
+ return { "message": nothing_there(holder, name, str(reached["called"])) }
375
376
  var value: Variant = read_under(holder, name)
376
377
  if not value is Object or not is_instance_valid(value):
377
378
  return {
378
- "message": "%s:%s holds %s, not an object" % [reached["called"], name, type_string(typeof(value))]
379
+ "message":
380
+ "%s:%s holds %s, not an object" % [reached["called"], name, type_string(typeof(value))],
379
381
  }
380
- return {"object": value}
382
+ return { "object": value }
381
383
 
382
384
 
383
385
  ## Whether a step can be taken into [param value] at all: an object, a list, a map or a value with
@@ -444,8 +446,9 @@ static func value_calls(value: Variant) -> Array:
444
446
  return VALUE_CALLS.get(typeof(value), [])
445
447
 
446
448
 
447
- ## Whether [param value] is a packed list. Stepped into the way a list is, by index, and a write into
448
- ## one reaches the game's own copy, since packed lists share their storage until one is duplicated.
449
+ ## Whether [param value] is a packed list. Stepped into the way a list is, by index, and a write
450
+ ## into one reaches the game's own copy, since packed lists share their storage until one is
451
+ ## duplicated.
449
452
  static func packed(value: Variant) -> bool:
450
453
  return typeof(value) in PACKED
451
454
 
@@ -468,7 +471,7 @@ static func nothing_under(holder: Variant, named: String, called: String) -> Str
468
471
  ## way GDScript's own does, so reading the last of something does not mean asking how many first.
469
472
  static func can_read(holder: Variant, named: String) -> bool:
470
473
  var method: String = method_of(named)
471
- var given: Dictionary = arguments_of(named) if not method.is_empty() else {"args": []}
474
+ var given: Dictionary = arguments_of(named) if not method.is_empty() else { "args": [] }
472
475
  if given.has("message"):
473
476
  return false
474
477
  var args: Array = given["args"]
@@ -507,8 +510,8 @@ static func can_read(holder: Variant, named: String) -> bool:
507
510
  ## The key of [param map] a step names, in a list of one, or an empty list when it names none.
508
511
  ##
509
512
  ## A step is text, and a map is keyed by whatever the game put in it: text, a StringName, or a
510
- ## number, which a map of units by id is. Keyed by 3, "3" is not a key of it, measured on 4.7.2, so a
511
- ## step that reads as a number is tried as one after the two spellings of text.
513
+ ## number, which a map of units by id is. Keyed by 3, "3" is not a key of it, measured on 4.7.2, so
514
+ ## a step that reads as a number is tried as one after the two spellings of text.
512
515
  ##
513
516
  ## A map typed by its key is asked only in that type. It checks every key it is asked about and
514
517
  ## reports an engine error for one of another type, so a step into a map typed by an enum read the
@@ -611,13 +614,15 @@ static func method_of(named: String) -> String:
611
614
  ## whole number as an int. A path names what is read, and an argument that was itself a path or an
612
615
  ## object would make a read depend on other state in a way the step does not show.
613
616
  static func arguments_of(named: String) -> Dictionary:
614
- var inside: String = named.substr(named.find("(") + 1, named.length() - named.find("(") - 2).strip_edges()
617
+ var inside: String = (
618
+ named.substr(named.find("(") + 1, named.length() - named.find("(") - 2).strip_edges()
619
+ )
615
620
  if inside.is_empty():
616
- return {"args": []}
621
+ return { "args": [] }
617
622
  var reader: JSON = JSON.new()
618
623
  var parsed: Variant = reader.data if reader.parse("[%s]" % inside) == OK else null
619
624
  if not parsed is Array:
620
- return {"message": _not_literals(named)}
625
+ return { "message": _not_literals(named) }
621
626
  var args: Array = []
622
627
  for given: Variant in parsed:
623
628
  if given is float:
@@ -629,13 +634,16 @@ static func arguments_of(named: String) -> Dictionary:
629
634
  elif given == null or given is String or given is bool:
630
635
  args.append(given)
631
636
  else:
632
- return {"message": _not_literals(named)}
633
- return {"args": args}
637
+ return { "message": _not_literals(named) }
638
+ return { "args": args }
634
639
 
635
640
 
636
641
  static func _not_literals(named: String) -> String:
637
642
  return (
638
- "%s: a path gives a call literal arguments only, words in double quotes, numbers, true, false or null"
643
+ (
644
+ "%s: a path gives a call literal arguments only, words in double quotes, numbers, "
645
+ + "true, false or null"
646
+ )
639
647
  % named
640
648
  )
641
649
 
@@ -663,8 +671,8 @@ static func _arity(object: Object, method: String) -> Dictionary:
663
671
  ## when it may.
664
672
  ##
665
673
  ## A path is read, and a wait reads it every frame, so a call in one has to be a read. The engine
666
- ## marks its own reading methods const and `queue_free()`, `free()` and `set_name()` not, measured on
667
- ## 4.7.2, though it leaves some reads unmarked as well, `Engine.get_frames_drawn()` and
674
+ ## marks its own reading methods const and `queue_free()`, `free()` and `set_name()` not, measured
675
+ ## on 4.7.2, though it leaves some reads unmarked as well, `Engine.get_frames_drawn()` and
668
676
  ## `get_time_scale()` among them; so the refusal says the mark is missing, not that the method
669
677
  ## changes anything, and points at runtime_invoke call, which calls it. A game's methods carry no
670
678
  ## such mark, so one declared `-> void` is taken at its word, as doing something and answering
@@ -675,7 +683,9 @@ static func _arity(object: Object, method: String) -> Dictionary:
675
683
  ## Given [param given] arguments, a call is the engine's and marked as reading, or it is refused. A
676
684
  ## game's method taking arguments is trusted on nothing at all: one answering a value can as well
677
685
  ## change what it was asked about, and runtime_invoke call is how one is called on purpose.
678
- static func call_refused(object: Object, method: String, called_as: String, given: int = 0) -> String:
686
+ static func call_refused(
687
+ object: Object, method: String, called_as: String, given: int = 0
688
+ ) -> String:
679
689
  var arity: Dictionary = _arity(object, method)
680
690
  if arity.is_empty():
681
691
  return with_methods_offered("%s has no method %s" % [called_as, method], object, method)
@@ -729,7 +739,8 @@ static func with_methods_offered(said: String, object: Object, method: String) -
729
739
  var script: Script = attached
730
740
  for entry: Dictionary in script.get_script_method_list():
731
741
  var name: String = str(entry.get("name", ""))
732
- # A class called by its name runs only its static functions, so offering the rest leads to a refusal.
742
+ # A class called by its name runs only its static functions, so offering the rest leads
743
+ # to a refusal.
733
744
  var flags: int = entry.get("flags", 0)
734
745
  var callable: bool = not object is Script or flags & METHOD_FLAG_STATIC
735
746
  if callable and not name.is_empty() and not own.has(name):
@@ -741,7 +752,10 @@ static func with_methods_offered(said: String, object: Object, method: String) -
741
752
  every.append(name)
742
753
  var close: PackedStringArray = GdhRead.nearest(method, PackedStringArray(every))
743
754
  if not close.is_empty():
744
- return "%s; the nearest it has %s %s" % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
755
+ return (
756
+ "%s; the nearest it has %s %s"
757
+ % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
758
+ )
745
759
  if own.is_empty():
746
760
  return said
747
761
  own.sort()
@@ -808,7 +822,10 @@ static func unwritable_copy(chain: Array[Dictionary]) -> String:
808
822
  var named: String = link["named"]
809
823
  if not method_of(named).is_empty():
810
824
  return (
811
- "%s:%s is what a call answered, a copy the game keeps nowhere, so a part of it is not a place to write"
825
+ (
826
+ "%s:%s is what a call answered, a copy the game keeps nowhere, so a part of it "
827
+ + "is not a place to write"
828
+ )
812
829
  % [link["called"], named]
813
830
  )
814
831
  if not composite(link["holder"]):
@@ -833,7 +850,13 @@ static func write_back(chain: Array[Dictionary], changed: Variant) -> void:
833
850
  ## Only ever called once [method can_read] agrees.
834
851
  static func read_under(holder: Variant, named: String) -> Variant:
835
852
  if (
836
- (holder is Array or holder is Dictionary or packed(holder) or composite(holder) or textual(holder))
853
+ (
854
+ holder is Array
855
+ or holder is Dictionary
856
+ or packed(holder)
857
+ or composite(holder)
858
+ or textual(holder)
859
+ )
837
860
  and not method_of(named).is_empty()
838
861
  ):
839
862
  return Callable.create(holder, method_of(named)).call()
@@ -876,7 +899,7 @@ static func _index_in(named: String, size: int) -> int:
876
899
  ## the whole path, because the step is the typo.
877
900
  static func nothing_there(holder: Variant, named: String, called_as: String) -> String:
878
901
  var container_call: String = method_of(named)
879
- var given: Dictionary = arguments_of(named) if not container_call.is_empty() else {"args": []}
902
+ var given: Dictionary = arguments_of(named) if not container_call.is_empty() else { "args": [] }
880
903
  if given.has("message"):
881
904
  return "%s:%s" % [called_as, given["message"]]
882
905
  var given_args: Array = given["args"]
@@ -893,14 +916,21 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
893
916
  return container_calls_refused(
894
917
  called_as, "a packed list", container_call, given_count, packed_calls(holder)
895
918
  )
896
- return "%s is a packed list of %d, so there is no %s in it" % [called_as, len(holder), named]
919
+ return (
920
+ "%s is a packed list of %d, so there is no %s in it" % [called_as, len(holder), named]
921
+ )
897
922
  if textual(holder):
898
923
  if container_call.is_empty():
899
924
  return (
900
- "%s is a string, so a path makes only calls on it, which read it and take no arguments: %s"
925
+ (
926
+ "%s is a string, so a path makes only calls on it, which read it and take no "
927
+ + "arguments: %s"
928
+ )
901
929
  % [called_as, "(), ".join(STRING_CALLS) + "()"]
902
930
  )
903
- return container_calls_refused(called_as, "a string", container_call, given_count, STRING_CALLS)
931
+ return container_calls_refused(
932
+ called_as, "a string", container_call, given_count, STRING_CALLS
933
+ )
904
934
  if holder is Dictionary:
905
935
  if not container_call.is_empty():
906
936
  return container_calls_refused(called_as, "a map", container_call, given_count)
@@ -948,7 +978,10 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
948
978
  )
949
979
  return "%s is %s, so it has no %s; it has %s" % [called_as, sort, named, has]
950
980
  if not holder is Object:
951
- return "%s is %s, which has nothing to read %s off" % [called_as, type_string(typeof(holder)), named]
981
+ return (
982
+ "%s is %s, which has nothing to read %s off"
983
+ % [called_as, type_string(typeof(holder)), named]
984
+ )
952
985
  var object: Object = holder
953
986
  var method: String = method_of(named)
954
987
  if not method.is_empty():
@@ -975,12 +1008,17 @@ static func container_calls_refused(
975
1008
  elif sort == "a list":
976
1009
  calls = LIST_CALLS
977
1010
  if given > 0 and method in calls:
978
- return "%s is %s, and a path calls %s() on one with no arguments" % [called_as, sort, method]
1011
+ return (
1012
+ "%s is %s, and a path calls %s() on one with no arguments" % [called_as, sort, method]
1013
+ )
979
1014
  var spelled: Array[String] = []
980
1015
  for each: String in calls:
981
1016
  spelled.append(each + "()")
982
1017
  return (
983
- "%s is %s, and %s() is not one of the calls a path makes on one, which read it and take no arguments: %s"
1018
+ (
1019
+ "%s is %s, and %s() is not one of the calls a path makes on one, which read it and "
1020
+ + "take no arguments: %s"
1021
+ )
984
1022
  % [called_as, sort, method, ", ".join(spelled)]
985
1023
  )
986
1024