gdharness 1.3.0 → 1.3.2
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 +322 -96
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
- package/build/godot/addons/gdharness_editor/declared_values.gd +116 -0
- package/build/godot/addons/gdharness_editor/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +65 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +222 -59
- package/build/godot/addons/gdharness_editor/reading.gd +65 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +175 -74
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +88 -84
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +65 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +52 -25
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +25 -15
- package/build/godot/operations/declared_values.gd +116 -0
- package/build/godot/operations/dependencies.gd +141 -33
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +14 -14
- package/build/godot/operations/file_reads.gd +65 -25
- package/build/godot/operations/file_walk.gd +34 -0
- package/build/godot/operations/gdscript_analysis.gd +27 -22
- package/build/godot/operations/gdscript_authoring.gd +122 -17
- package/build/godot/operations/godot_operations.gd +66 -63
- package/build/godot/operations/import_pipeline.gd +59 -20
- package/build/godot/operations/input_actions.gd +90 -18
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +83 -20
- package/build/godot/operations/project_diagnostics.gd +36 -11
- package/build/godot/operations/property_values.gd +222 -59
- package/build/godot/operations/reading.gd +65 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +9 -6
- package/build/godot/operations/script_check.gd +23 -7
- package/build/godot/operations/serialisation.gd +49 -94
- package/build/godot/operations/value_shapes.gd +118 -0
- package/package.json +1 -1
|
@@ -4,12 +4,12 @@ extends RefCounted
|
|
|
4
4
|
## question, where one is on screen, a property's value, the metrics. What it changes, a property
|
|
5
5
|
## written or a method called, is in runtime_changes.gd.
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
7
|
+
const GdhPaths = preload("runtime_paths.gd")
|
|
8
|
+
const GdhRead = preload("reading.gd")
|
|
9
|
+
const GdhSays = preload("runtime_says.gd")
|
|
10
|
+
const GdhScreen = preload("runtime_screen.gd")
|
|
11
|
+
const GdhValues = preload("runtime_values.gd")
|
|
12
|
+
const GdhWords = preload("runtime_words.gd")
|
|
13
13
|
|
|
14
14
|
## The most nodes one find answers with, unless asked for fewer: enough for any real query and
|
|
15
15
|
## far short of the tree dump a query exists to avoid.
|
|
@@ -32,7 +32,7 @@ const LIMIT_CEILING: int = 5000
|
|
|
32
32
|
const FRAMES_TIMED: int = 120
|
|
33
33
|
|
|
34
34
|
var _host: Node
|
|
35
|
-
var _values:
|
|
35
|
+
var _values: GdhValues
|
|
36
36
|
## How long each of the last frames took, oldest first, in microseconds, as the game timed them.
|
|
37
37
|
var _frame_usec: PackedInt64Array = PackedInt64Array()
|
|
38
38
|
## How many physics steps ran in each of those frames, in the same order.
|
|
@@ -43,7 +43,7 @@ var _last_physics_frames: int = 0
|
|
|
43
43
|
|
|
44
44
|
## The host is the autoload, which is how the tree is reached: it is not in one when the
|
|
45
45
|
## modules are built, and a fixture may put it in one later.
|
|
46
|
-
func _init(host: Node, values:
|
|
46
|
+
func _init(host: Node, values: GdhValues) -> void:
|
|
47
47
|
_host = host
|
|
48
48
|
_values = values
|
|
49
49
|
|
|
@@ -56,8 +56,8 @@ func _init(host: Node, values: Values) -> void:
|
|
|
56
56
|
## characters, and the same question as a find with one property answered two.
|
|
57
57
|
func get_tree(params: Dictionary) -> Dictionary:
|
|
58
58
|
var root_path: String = str(params.get("root", "/root"))
|
|
59
|
-
var max_depth: int =
|
|
60
|
-
var include_properties: bool =
|
|
59
|
+
var max_depth: int = GdhRead.as_int(params.get("depth", 3), 3)
|
|
60
|
+
var include_properties: bool = GdhRead.as_bool(params.get("include_properties", false))
|
|
61
61
|
var named: Array[String] = []
|
|
62
62
|
var listed: Variant = params.get("properties", [])
|
|
63
63
|
if listed is Array:
|
|
@@ -66,7 +66,7 @@ func get_tree(params: Dictionary) -> Dictionary:
|
|
|
66
66
|
else:
|
|
67
67
|
return {"type": "error", "message": "properties must be a list of property names"}
|
|
68
68
|
|
|
69
|
-
var reached: Dictionary =
|
|
69
|
+
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
70
70
|
if reached.has("message"):
|
|
71
71
|
return reached
|
|
72
72
|
var root: Node = reached["node"]
|
|
@@ -101,8 +101,8 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
101
101
|
for filter: String in FILTERS:
|
|
102
102
|
wanted[filter] = str(params.get(filter, ""))
|
|
103
103
|
var wanted_property: String = str(params.get("property", ""))
|
|
104
|
-
var include_hidden: bool =
|
|
105
|
-
var limit: int =
|
|
104
|
+
var include_hidden: bool = GdhRead.as_bool(params.get("include_hidden", true), true)
|
|
105
|
+
var limit: int = GdhRead.as_int(params.get("limit", FIND_LIMIT), FIND_LIMIT)
|
|
106
106
|
if limit < 1 or limit > LIMIT_CEILING:
|
|
107
107
|
return {
|
|
108
108
|
"type": "error", "message": "limit is from 1 to %d, and %d was asked for" % [LIMIT_CEILING, limit]
|
|
@@ -114,12 +114,12 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
114
114
|
wanted["script"] = "res://" + wanted["script"]
|
|
115
115
|
|
|
116
116
|
# Split once here rather than for each node the walk passes.
|
|
117
|
-
var names: Array[String] =
|
|
117
|
+
var names: Array[String] = GdhSays.split_at_bars(wanted["name"])
|
|
118
118
|
if not wanted["name"].is_empty() and names.is_empty():
|
|
119
119
|
var bars_only: String = 'name "%s" is only bars, so it names no node; write the names between them'
|
|
120
120
|
return {"type": "error", "message": bars_only % wanted["name"]}
|
|
121
121
|
|
|
122
|
-
var reached: Dictionary =
|
|
122
|
+
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
123
123
|
if reached.has("message"):
|
|
124
124
|
return reached
|
|
125
125
|
var root: Node = reached["node"]
|
|
@@ -131,14 +131,14 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
131
131
|
# find would have matched if the name had been read the way it was probably meant.
|
|
132
132
|
var plain_names: Array[String] = []
|
|
133
133
|
for named: String in names:
|
|
134
|
-
if
|
|
134
|
+
if GdhSays.is_plain(named):
|
|
135
135
|
plain_names.append(named)
|
|
136
136
|
var nearly: int = 0
|
|
137
137
|
# The same for the words: a glob is matched against the whole text, and one written as a prefix
|
|
138
138
|
# by habit, "Taken together*", missed the label whose sentence those words are in the middle of.
|
|
139
139
|
# Nothing found then read as the words not being on screen, which is the wrong conclusion when
|
|
140
140
|
# checking that a change to the text landed.
|
|
141
|
-
var widened_says: String =
|
|
141
|
+
var widened_says: String = GdhSays.widened(wanted["says"])
|
|
142
142
|
var widened: Dictionary[String, String] = {}
|
|
143
143
|
for filter: String in wanted:
|
|
144
144
|
widened[filter] = widened_says if filter == "says" else wanted[filter]
|
|
@@ -192,7 +192,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
192
192
|
near_words.append('"%s"' % named)
|
|
193
193
|
var opened: Array[String] = []
|
|
194
194
|
for named: String in names:
|
|
195
|
-
opened.append("*%s*" % named if
|
|
195
|
+
opened.append("*%s*" % named if GdhSays.is_plain(named) else named)
|
|
196
196
|
(
|
|
197
197
|
notes
|
|
198
198
|
. append(
|
|
@@ -203,7 +203,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
203
203
|
"" if nearly > 0 else "hidden ",
|
|
204
204
|
holding,
|
|
205
205
|
" or ".join(PackedStringArray(near_words)),
|
|
206
|
-
|
|
206
|
+
GdhSays.joined_at_bars(opened),
|
|
207
207
|
"" if nearly > 0 else " with includeHidden true",
|
|
208
208
|
]
|
|
209
209
|
)
|
|
@@ -304,19 +304,19 @@ func _found(node: Node, wanted_property: String) -> Dictionary:
|
|
|
304
304
|
if wanted_property.is_empty():
|
|
305
305
|
return entry
|
|
306
306
|
entry["property"] = wanted_property
|
|
307
|
-
# Through a path as well, for the reason [method
|
|
307
|
+
# Through a path as well, for the reason [method GdhPaths.walk_to] gives. A step that is not there on
|
|
308
308
|
# this node reads as not having the property rather than as a refusal, which is the rule this
|
|
309
309
|
# whole function is written to: a find matches nodes of several classes on purpose.
|
|
310
|
-
var reached: Dictionary =
|
|
310
|
+
var reached: Dictionary = GdhPaths.walk_to(node, str(entry["path"]), wanted_property)
|
|
311
311
|
if reached.has("message"):
|
|
312
312
|
entry["has_property"] = false
|
|
313
313
|
return entry
|
|
314
314
|
var holder: Variant = reached["holder"]
|
|
315
315
|
var named: String = reached["name"]
|
|
316
|
-
var has: bool =
|
|
316
|
+
var has: bool = GdhPaths.can_read(holder, named)
|
|
317
317
|
entry["has_property"] = has
|
|
318
318
|
if has:
|
|
319
|
-
entry["value"] = _values.serialize(
|
|
319
|
+
entry["value"] = _values.serialize(GdhPaths.read_under(holder, named))
|
|
320
320
|
return entry
|
|
321
321
|
|
|
322
322
|
|
|
@@ -356,7 +356,7 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
|
|
|
356
356
|
# What this node says, rather than everything said underneath it. A row is then found by the
|
|
357
357
|
# label in it, and the path answered is that label's, which is where the words a caller is
|
|
358
358
|
# looking at actually are: matching every container above it would answer with the screen.
|
|
359
|
-
if not wanted["says"].is_empty() and not
|
|
359
|
+
if not wanted["says"].is_empty() and not GdhSays.says(node, wanted["says"]):
|
|
360
360
|
return false
|
|
361
361
|
var script: Variant = node.get_script()
|
|
362
362
|
if not wanted["script"].is_empty():
|
|
@@ -408,14 +408,14 @@ static func _script_is(attached: Script, wanted: String) -> bool:
|
|
|
408
408
|
## whose interesting half is below the cut says so in a number rather than in a flag.
|
|
409
409
|
func read_text(params: Dictionary) -> Dictionary:
|
|
410
410
|
var root_path: String = str(params.get("root", "/root"))
|
|
411
|
-
var include_hidden: bool =
|
|
412
|
-
var limit: int =
|
|
411
|
+
var include_hidden: bool = GdhRead.as_bool(params.get("include_hidden", false))
|
|
412
|
+
var limit: int = GdhRead.as_int(params.get("limit", READ_LIMIT), READ_LIMIT)
|
|
413
413
|
if limit < 1 or limit > LIMIT_CEILING:
|
|
414
414
|
return {
|
|
415
415
|
"type": "error", "message": "limit is from 1 to %d, and %d was asked for" % [LIMIT_CEILING, limit]
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
var reached: Dictionary =
|
|
418
|
+
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
419
419
|
if reached.has("message"):
|
|
420
420
|
return reached
|
|
421
421
|
var root: Node = reached["node"]
|
|
@@ -453,7 +453,7 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
453
453
|
func _read_into(node: Node, include_hidden: bool, most: int, into: Array[String]) -> int:
|
|
454
454
|
var left_out: int = 0
|
|
455
455
|
if include_hidden or shown(node):
|
|
456
|
-
for said: String in
|
|
456
|
+
for said: String in GdhWords.lines_said_by(node):
|
|
457
457
|
if into.size() < most:
|
|
458
458
|
into.append(said)
|
|
459
459
|
else:
|
|
@@ -472,7 +472,7 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
472
472
|
if node_path.is_empty():
|
|
473
473
|
return {"type": "error", "message": "Node path required"}
|
|
474
474
|
|
|
475
|
-
var standing: Dictionary =
|
|
475
|
+
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
476
476
|
if standing.has("message"):
|
|
477
477
|
return standing
|
|
478
478
|
var node: Node = standing["node"]
|
|
@@ -491,7 +491,7 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
491
491
|
"canvas": _values.serialize(canvas_rect),
|
|
492
492
|
"window":
|
|
493
493
|
_values.serialize(
|
|
494
|
-
|
|
494
|
+
GdhScreen.rect_in_window(
|
|
495
495
|
control.get_viewport(),
|
|
496
496
|
control.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, control.size)
|
|
497
497
|
)
|
|
@@ -500,7 +500,7 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
500
500
|
if node is Node2D:
|
|
501
501
|
var item: Node2D = node
|
|
502
502
|
var canvas_position: Vector2 = item.get_global_transform_with_canvas().origin
|
|
503
|
-
var window_position: Vector2 =
|
|
503
|
+
var window_position: Vector2 = GdhScreen.in_window(item.get_viewport(), canvas_position)
|
|
504
504
|
return {
|
|
505
505
|
"type": "point",
|
|
506
506
|
"path": node_path,
|
|
@@ -544,12 +544,12 @@ func _in_the_frame(node_path: String, item: Node3D) -> Dictionary:
|
|
|
544
544
|
if found.has("aim"):
|
|
545
545
|
var aim: Vector2 = found["aim"]
|
|
546
546
|
answer["canvas"] = _values.serialize(aim)
|
|
547
|
-
answer["window"] = _values.serialize(
|
|
547
|
+
answer["window"] = _values.serialize(GdhScreen.in_window(drawn_in, aim))
|
|
548
548
|
if found.has("rect"):
|
|
549
549
|
var covered: Rect2 = found["rect"]
|
|
550
550
|
answer["covers"] = {
|
|
551
551
|
"canvas": _values.serialize(covered),
|
|
552
|
-
"window": _values.serialize(
|
|
552
|
+
"window": _values.serialize(GdhScreen.rect_in_window(drawn_in, covered)),
|
|
553
553
|
}
|
|
554
554
|
return answer
|
|
555
555
|
|
|
@@ -634,12 +634,12 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
634
634
|
if node_path.is_empty() or property.is_empty():
|
|
635
635
|
return {"type": "error", "message": "Node path and property required"}
|
|
636
636
|
|
|
637
|
-
var standing: Dictionary =
|
|
637
|
+
var standing: Dictionary = GdhValues.target_at(_host.get_tree().root, node_path)
|
|
638
638
|
if standing.has("message"):
|
|
639
639
|
return standing
|
|
640
640
|
var target: Object = standing["object"]
|
|
641
641
|
|
|
642
|
-
var reached: Dictionary =
|
|
642
|
+
var reached: Dictionary = GdhPaths.walk_to(target, node_path, property)
|
|
643
643
|
if reached.has("message"):
|
|
644
644
|
return reached
|
|
645
645
|
|
|
@@ -647,14 +647,14 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
647
647
|
var named: String = reached["name"]
|
|
648
648
|
# Asked of the holder rather than read and compared to null, because a property the holder does
|
|
649
649
|
# not have and a property that is null both read as null.
|
|
650
|
-
if not
|
|
651
|
-
return {"type": "error", "message":
|
|
650
|
+
if not GdhPaths.can_read(holder, named):
|
|
651
|
+
return {"type": "error", "message": GdhPaths.nothing_there(holder, named, str(reached["called"]))}
|
|
652
652
|
|
|
653
653
|
return {
|
|
654
654
|
"type": "property",
|
|
655
655
|
"path": node_path,
|
|
656
656
|
"property": property,
|
|
657
|
-
"value": _values.serialize(
|
|
657
|
+
"value": _values.serialize(GdhPaths.read_under(holder, named)),
|
|
658
658
|
}
|
|
659
659
|
|
|
660
660
|
|
|
@@ -793,13 +793,13 @@ func _serialize_node_tree(
|
|
|
793
793
|
func _named_properties_of(node: Node, path: String, named: Array[String]) -> Dictionary:
|
|
794
794
|
var properties: Dictionary = {}
|
|
795
795
|
for wanted: String in named:
|
|
796
|
-
var reached: Dictionary =
|
|
796
|
+
var reached: Dictionary = GdhPaths.walk_to(node, path, wanted)
|
|
797
797
|
if reached.has("message"):
|
|
798
798
|
continue
|
|
799
799
|
var holder: Variant = reached["holder"]
|
|
800
800
|
var name: String = reached["name"]
|
|
801
|
-
if
|
|
802
|
-
properties[wanted] = _values.serialize(
|
|
801
|
+
if GdhPaths.can_read(holder, name):
|
|
802
|
+
properties[wanted] = _values.serialize(GdhPaths.read_under(holder, name))
|
|
803
803
|
return properties
|
|
804
804
|
|
|
805
805
|
|
|
@@ -3,7 +3,7 @@ extends RefCounted
|
|
|
3
3
|
## Whether a node says what a caller asked for in `says`: the one set of rules a find, a click and a
|
|
4
4
|
## wait all match words by, so the three never disagree about which control a word names.
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const GdhWords = preload("runtime_words.gd")
|
|
7
7
|
|
|
8
8
|
## Stands in for a bar written as `\|` while `says` is split into its alternatives: a character from
|
|
9
9
|
## the private use area, which no game's text uses.
|
|
@@ -25,7 +25,7 @@ static var _spaces: RegEx = null
|
|
|
25
25
|
|
|
26
26
|
## Whether [param node]'s own words are what [param wanted] asks for.
|
|
27
27
|
static func says(node: Node, wanted: String) -> bool:
|
|
28
|
-
return matches(
|
|
28
|
+
return matches(GdhWords.said_by(node), wanted)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
## Whether [param said] is what [param wanted] asks for.
|
|
@@ -57,7 +57,7 @@ static func matches(said: String, wanted: String) -> bool:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
## Each alternative of [param wanted] as a glob, open at both ends for words written plain, with
|
|
60
|
-
## every space as `?`.
|
|
60
|
+
## every space as `?`. GdhWords that match once spaced match this as written, since spacing changes
|
|
61
61
|
## only the characters a `?` stands for, so a text that does not is answered without being spaced.
|
|
62
62
|
## Kept by what was written, as [method alternatives] keeps its split.
|
|
63
63
|
static func _loose(wanted: String) -> Array[String]:
|
|
@@ -2,13 +2,13 @@ extends RefCounted
|
|
|
2
2
|
|
|
3
3
|
## Which control a click means when it names the control by the words on it rather than by path.
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
5
|
+
const GdhItems = preload("runtime_items.gd")
|
|
6
|
+
const GdhQueries = preload("runtime_queries.gd")
|
|
7
|
+
const GdhRead = preload("reading.gd")
|
|
8
|
+
const GdhSays = preload("runtime_says.gd")
|
|
9
|
+
const GdhScreen = preload("runtime_screen.gd")
|
|
10
|
+
const GdhValues = preload("runtime_values.gd")
|
|
11
|
+
const GdhWords = preload("runtime_words.gd")
|
|
12
12
|
|
|
13
13
|
## How many of the controls saying the same words a refusal names, with the index for each.
|
|
14
14
|
const LISTED_CANDIDATES: int = 10
|
|
@@ -26,7 +26,7 @@ const WHOLE: int = 1
|
|
|
26
26
|
## The control a click by [param wanted] words presses, found under [param root_path] in the tree
|
|
27
27
|
## [param root] heads, or the refusal saying why there is none or which of several:
|
|
28
28
|
## {"path", "found"} or {"type", "message"}, with `item` as well when the match is an item of a
|
|
29
|
-
## control drawing a list, which [method
|
|
29
|
+
## control drawing a list, which [method GdhItems.item_rect] places.
|
|
30
30
|
##
|
|
31
31
|
## Matched the way a find matches `says`, against a control's own words, and only among controls
|
|
32
32
|
## the player can see. A match inside a button presses the button, because the label is what says
|
|
@@ -45,7 +45,7 @@ const WHOLE: int = 1
|
|
|
45
45
|
## on screen for a player, and a click at it lands on the thing on top. The screen a game shows
|
|
46
46
|
## over its hall covers the hall's drawers without hiding them, so their words matched as well.
|
|
47
47
|
static func control_saying(root: Node, root_path: String, wanted: String, which: Variant) -> Dictionary:
|
|
48
|
-
var reached: Dictionary =
|
|
48
|
+
var reached: Dictionary = GdhValues.node_at(root, root_path)
|
|
49
49
|
if reached.has("message"):
|
|
50
50
|
return reached
|
|
51
51
|
var matched: Array[Control] = []
|
|
@@ -66,22 +66,22 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
66
66
|
var pending: Array[Node] = [reached["node"]]
|
|
67
67
|
while not pending.is_empty():
|
|
68
68
|
var node: Node = pending.pop_front()
|
|
69
|
-
if node is Control and
|
|
70
|
-
if not
|
|
69
|
+
if node is Control and GdhSays.says(node, wanted):
|
|
70
|
+
if not GdhQueries.shown(node):
|
|
71
71
|
hidden += 1
|
|
72
|
-
if _says_exactly(
|
|
72
|
+
if _says_exactly(GdhWords.said_by(node), wanted):
|
|
73
73
|
exact_out_of_reach.append(node)
|
|
74
74
|
out_of_reach_as.append("hidden")
|
|
75
|
-
elif
|
|
75
|
+
elif GdhWords.draws_a_list(node):
|
|
76
76
|
# Each item a match of its own, so the words of one tab are matched as a whole and the
|
|
77
77
|
# index picks between two items saying them as it does between two buttons.
|
|
78
78
|
var drawing: Control = node
|
|
79
|
-
for entry: Dictionary in
|
|
79
|
+
for entry: Dictionary in GdhWords.items_of(drawing):
|
|
80
80
|
var words: String = entry["says"]
|
|
81
|
-
if not
|
|
81
|
+
if not GdhSays.matches(words, wanted):
|
|
82
82
|
continue
|
|
83
83
|
var whole: bool = _says_exactly(words, wanted)
|
|
84
|
-
var drawn_at_now: Rect2 =
|
|
84
|
+
var drawn_at_now: Rect2 = GdhItems.item_rect(drawing, entry["item"], wanted)
|
|
85
85
|
matched.append(drawing)
|
|
86
86
|
said.append(words)
|
|
87
87
|
exact.append(whole)
|
|
@@ -95,7 +95,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
95
95
|
else:
|
|
96
96
|
var control: Control = node
|
|
97
97
|
var target: Control = _pressed_through(control)
|
|
98
|
-
var words: String =
|
|
98
|
+
var words: String = GdhWords.said_by(node)
|
|
99
99
|
var whole: bool = _says_exactly(words, wanted)
|
|
100
100
|
var buried: bool = not whole and _inside_a_word(words, wanted)
|
|
101
101
|
var at: int = matched.find(target)
|
|
@@ -193,7 +193,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
193
193
|
% [_described(best, ranks[0], wanted, under), order, _candidates(targets, lines)]
|
|
194
194
|
)
|
|
195
195
|
}
|
|
196
|
-
var index: int = 0 if which == null else
|
|
196
|
+
var index: int = 0 if which == null else GdhRead.as_int(which, -1)
|
|
197
197
|
if index < 0 or index >= targets.size():
|
|
198
198
|
# A number arrives from JSON as a float, and 5 was quoted back as 5.0.
|
|
199
199
|
var given: String = str(index) if index >= 0 else str(which)
|
|
@@ -295,7 +295,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
295
295
|
var answer: Dictionary = {"path": str(targets[index].get_path()), "found": found}
|
|
296
296
|
if picked_items[index] != null:
|
|
297
297
|
answer["item"] = picked_items[index]
|
|
298
|
-
found["item"] =
|
|
298
|
+
found["item"] = GdhItems.item_named(targets[index], picked_items[index], lines[index])
|
|
299
299
|
return answer
|
|
300
300
|
|
|
301
301
|
|
|
@@ -345,9 +345,9 @@ static func pointer_turned_off_by(control: Control) -> Control:
|
|
|
345
345
|
## named WARD, and read as its words run together it said WARD only as part of more, level with
|
|
346
346
|
## "WARDSPITE" over its own description, and a click on WARD was refused as not clear.
|
|
347
347
|
static func _says_exactly(written: String, wanted: String) -> bool:
|
|
348
|
-
var said: String =
|
|
349
|
-
for words: String in
|
|
350
|
-
if not
|
|
348
|
+
var said: String = GdhSays.spaced(written)
|
|
349
|
+
for words: String in GdhSays.alternatives(wanted):
|
|
350
|
+
if not GdhSays.is_plain(words):
|
|
351
351
|
if said.matchn(words):
|
|
352
352
|
return true
|
|
353
353
|
continue
|
|
@@ -364,11 +364,11 @@ static func _says_exactly(written: String, wanted: String) -> bool:
|
|
|
364
364
|
## never starting where a word starts: "out" in "Scout". Words starting a longer one count as said,
|
|
365
365
|
## "Recruit" in "Recruits", and so does anything matched by a pattern.
|
|
366
366
|
static func _inside_a_word(written: String, wanted: String) -> bool:
|
|
367
|
-
var said: String =
|
|
367
|
+
var said: String = GdhSays.spaced(written).to_lower()
|
|
368
368
|
var found: bool = false
|
|
369
|
-
for words: String in
|
|
369
|
+
for words: String in GdhSays.alternatives(wanted):
|
|
370
370
|
var looked_for: String = words.to_lower()
|
|
371
|
-
if not
|
|
371
|
+
if not GdhSays.is_plain(words) or not _in_a_word(looked_for.unicode_at(0)):
|
|
372
372
|
return false
|
|
373
373
|
var at: int = said.find(looked_for)
|
|
374
374
|
while at >= 0:
|
|
@@ -391,8 +391,8 @@ static func _described(count: int, rank: int, wanted: String, under: String) ->
|
|
|
391
391
|
var what: String = "button" if rank & ENABLED_BUTTON else "control"
|
|
392
392
|
var how: String = '"%s"' % wanted
|
|
393
393
|
var plain: bool = true
|
|
394
|
-
for words: String in
|
|
395
|
-
plain = plain and
|
|
394
|
+
for words: String in GdhSays.alternatives(wanted):
|
|
395
|
+
plain = plain and GdhSays.is_plain(words)
|
|
396
396
|
if plain:
|
|
397
397
|
how = ('exactly "%s"' if rank & WHOLE else '"%s" as part of more') % wanted
|
|
398
398
|
if count == 1:
|
|
@@ -756,7 +756,7 @@ static func _window_over(target: Control, point: Vector2, window: Window) -> boo
|
|
|
756
756
|
if window.get_parent() == null or window.is_ancestor_of(target):
|
|
757
757
|
return false
|
|
758
758
|
var host: Viewport = window.get_parent().get_viewport()
|
|
759
|
-
for step: Dictionary in
|
|
759
|
+
for step: Dictionary in GdhScreen.steps_out(target.get_viewport(), point):
|
|
760
760
|
if step["viewport"] != host:
|
|
761
761
|
continue
|
|
762
762
|
var there: Vector2 = step["point"]
|
|
@@ -820,7 +820,7 @@ static func _off_screen(target: Control, aim: Vector2) -> bool:
|
|
|
820
820
|
var point: Vector2 = target.get_global_transform_with_canvas() * aim + _moved_by(target, moves)
|
|
821
821
|
if _clipped_away(target, point, moves) or not target.get_viewport().get_visible_rect().has_point(point):
|
|
822
822
|
return true
|
|
823
|
-
var reached: Dictionary =
|
|
823
|
+
var reached: Dictionary = GdhScreen.reach(target.get_viewport(), point)
|
|
824
824
|
var viewport: Viewport = reached["viewport"]
|
|
825
825
|
var there: Vector2 = reached["point"]
|
|
826
826
|
return not viewport.get_visible_rect().has_point(there)
|
|
@@ -855,11 +855,11 @@ static func _clipped_away(
|
|
|
855
855
|
## words. A line in another font or holding an image measures off from what is drawn, which a wide
|
|
856
856
|
## line with nothing between the words and its start hardly does.
|
|
857
857
|
static func point_on_words(label: RichTextLabel, wanted: String) -> Dictionary:
|
|
858
|
-
var text: String =
|
|
858
|
+
var text: String = GdhSays.spaced(GdhWords.said_by(label))
|
|
859
859
|
var start: int = -1
|
|
860
860
|
var length: int = 0
|
|
861
|
-
for words: String in
|
|
862
|
-
if not
|
|
861
|
+
for words: String in GdhSays.alternatives(wanted):
|
|
862
|
+
if not GdhSays.is_plain(words):
|
|
863
863
|
continue
|
|
864
864
|
var at: int = text.findn(words)
|
|
865
865
|
if at >= 0 and (start < 0 or at < start):
|
|
@@ -927,7 +927,7 @@ static func _open_menu_note(root: Node, wanted: String) -> String:
|
|
|
927
927
|
if menu != null and menu.visible:
|
|
928
928
|
for item: int in menu.item_count:
|
|
929
929
|
var text: String = menu.get_item_text(item)
|
|
930
|
-
if
|
|
930
|
+
if GdhSays.matches(text, wanted):
|
|
931
931
|
var owner: Node = menu.get_parent()
|
|
932
932
|
var named: Node = owner if owner is MenuButton or owner is OptionButton else menu
|
|
933
933
|
return (
|
|
@@ -3,11 +3,11 @@ extends RefCounted
|
|
|
3
3
|
## What reaches the running game the way a keyboard reaches it: text typed into the field that has
|
|
4
4
|
## the focus, and an item chosen out of a menu the way a keyboard chooses one.
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
6
|
+
const GdhMenus = preload("runtime_menus.gd")
|
|
7
|
+
const GdhRead = preload("reading.gd")
|
|
8
|
+
const GdhTargets = preload("runtime_targets.gd")
|
|
9
|
+
const GdhValues = preload("runtime_values.gd")
|
|
10
|
+
const GdhWords = preload("runtime_words.gd")
|
|
11
11
|
|
|
12
12
|
## The two characters a field reads as keys rather than as text.
|
|
13
13
|
const NEWLINE: int = 10
|
|
@@ -41,7 +41,7 @@ func _init(host: Node) -> void:
|
|
|
41
41
|
## back now rather than echoed from the request, so a call that typed somewhere unhelpful says so.
|
|
42
42
|
func inject_text(params: Dictionary) -> Dictionary:
|
|
43
43
|
var text: String = str(params.get("text", ""))
|
|
44
|
-
var over: bool =
|
|
44
|
+
var over: bool = GdhRead.as_bool(params.get("replace", false))
|
|
45
45
|
if text.is_empty() and not over:
|
|
46
46
|
return {"type": "error", "message": "text needs something to type, or replace to empty a field"}
|
|
47
47
|
|
|
@@ -127,7 +127,7 @@ static func _select_everything(focused: Control) -> bool:
|
|
|
127
127
|
static func _what_it_holds(focused: Control) -> Variant:
|
|
128
128
|
var field: LineEdit = focused as LineEdit
|
|
129
129
|
if field != null:
|
|
130
|
-
return
|
|
130
|
+
return GdhWords.masked(field) if field.secret else field.text
|
|
131
131
|
var box: TextEdit = focused as TextEdit
|
|
132
132
|
if box == null:
|
|
133
133
|
return null
|
|
@@ -219,12 +219,12 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
219
219
|
var node_path: String = str(params.get("path", ""))
|
|
220
220
|
if node_path.is_empty():
|
|
221
221
|
return {"type": "error", "message": "Node path required"}
|
|
222
|
-
var standing: Dictionary =
|
|
222
|
+
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
223
223
|
if standing.has("message"):
|
|
224
224
|
return standing
|
|
225
225
|
var node: Node = standing["node"]
|
|
226
226
|
|
|
227
|
-
var menu: PopupMenu =
|
|
227
|
+
var menu: PopupMenu = GdhMenus.menu_of(node)
|
|
228
228
|
if menu == null:
|
|
229
229
|
return {
|
|
230
230
|
"type": "error",
|
|
@@ -241,7 +241,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
241
241
|
"message":
|
|
242
242
|
(
|
|
243
243
|
"%s needs the item named, by text or index. It holds: %s"
|
|
244
|
-
% [node_path, ", ".join(
|
|
244
|
+
% [node_path, ", ".join(GdhMenus.items_of(menu))]
|
|
245
245
|
)
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -255,7 +255,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
255
255
|
return {
|
|
256
256
|
"type": "error",
|
|
257
257
|
"message":
|
|
258
|
-
"%s is not visible%s, so its menu cannot be opened" % [node_path,
|
|
258
|
+
"%s is not visible%s, so its menu cannot be opened" % [node_path, GdhTargets.why_hidden(holder)]
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
# Shown first, because a menu nobody has opened has no focus to move and the press would go to
|
|
@@ -263,15 +263,15 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
263
263
|
# a menu: one rebuilt on about_to_popup was read as it stood before, measured on 4.7.2, and the
|
|
264
264
|
# index found in the old items chose another item of the new ones. An OptionButton opens its own;
|
|
265
265
|
# a bare PopupMenu is popped where it already sits, which leaves a menu already open where it is.
|
|
266
|
-
var opened: bool =
|
|
266
|
+
var opened: bool = GdhMenus.open_the_menu(node, menu)
|
|
267
267
|
await _host.get_tree().process_frame
|
|
268
268
|
|
|
269
|
-
var found: Array[int] =
|
|
269
|
+
var found: Array[int] = GdhMenus.wanted_items(menu, params)
|
|
270
270
|
if found.size() > 1:
|
|
271
271
|
await _shut(menu, opened)
|
|
272
272
|
var tied: Array[String] = []
|
|
273
273
|
for at: int in found:
|
|
274
|
-
tied.append("%d: %s" % [at,
|
|
274
|
+
tied.append("%d: %s" % [at, GdhWords.item_says(menu, at)])
|
|
275
275
|
return {
|
|
276
276
|
"type": "error",
|
|
277
277
|
"message":
|
|
@@ -294,7 +294,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
294
294
|
"type": "chosen",
|
|
295
295
|
"path": node_path,
|
|
296
296
|
"index": index,
|
|
297
|
-
"text":
|
|
297
|
+
"text": GdhWords.item_says(menu, index),
|
|
298
298
|
"id": menu.get_item_id(index),
|
|
299
299
|
"opened": opened,
|
|
300
300
|
"menu": str(menu.get_path()),
|
|
@@ -329,13 +329,13 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
329
329
|
)
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
answer["control_afterwards"] =
|
|
332
|
+
answer["control_afterwards"] = GdhValues.afterwards(node)
|
|
333
333
|
# What the button in front of the menu reads now: a menu item that fired changes the thing
|
|
334
334
|
# holding it. A button the pick took away has nothing to read, and that it went is the answer.
|
|
335
335
|
if answer["control_afterwards"] == "in_tree" and node is OptionButton:
|
|
336
336
|
var chooser: OptionButton = node
|
|
337
337
|
answer["selected"] = chooser.get_selected()
|
|
338
|
-
answer["shows"] =
|
|
338
|
+
answer["shows"] = GdhWords.said_by(chooser)
|
|
339
339
|
return answer
|
|
340
340
|
|
|
341
341
|
|
|
@@ -352,9 +352,9 @@ func _shut(menu: PopupMenu, opened: bool) -> void:
|
|
|
352
352
|
## Why item [param index] of [param menu] is not something to choose, or "" when it is.
|
|
353
353
|
static func _not_a_choice(node_path: String, menu: PopupMenu, index: int) -> String:
|
|
354
354
|
if index < 0:
|
|
355
|
-
return "%s has no such item. It holds: %s" % [node_path, ", ".join(
|
|
355
|
+
return "%s has no such item. It holds: %s" % [node_path, ", ".join(GdhMenus.items_of(menu))]
|
|
356
356
|
if menu.is_item_separator(index):
|
|
357
|
-
var heading: String =
|
|
357
|
+
var heading: String = GdhWords.item_says(menu, index).strip_edges()
|
|
358
358
|
if heading.is_empty():
|
|
359
359
|
return "%s item %d is a separator, not a choice" % [node_path, index]
|
|
360
360
|
return (
|
|
@@ -362,7 +362,7 @@ static func _not_a_choice(node_path: String, menu: PopupMenu, index: int) -> Str
|
|
|
362
362
|
% [node_path, index, heading]
|
|
363
363
|
)
|
|
364
364
|
if menu.is_item_disabled(index):
|
|
365
|
-
return "%s item %d, %s, is disabled" % [node_path, index,
|
|
365
|
+
return "%s item %d, %s, is disabled" % [node_path, index, GdhWords.item_says(menu, index)]
|
|
366
366
|
return ""
|
|
367
367
|
|
|
368
368
|
|