gdharness 1.0.2 → 1.0.3
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.
|
@@ -182,10 +182,10 @@ func _found(node: Node, wanted_property: String) -> Dictionary:
|
|
|
182
182
|
if wanted_property.is_empty():
|
|
183
183
|
return entry
|
|
184
184
|
entry["property"] = wanted_property
|
|
185
|
-
# Through a path as well, for the reason [method
|
|
185
|
+
# Through a path as well, for the reason [method walk_to] gives. A step that is not there on
|
|
186
186
|
# this node reads as not having the property rather than as a refusal, which is the rule this
|
|
187
187
|
# whole function is written to: a find matches nodes of several classes on purpose.
|
|
188
|
-
var reached: Dictionary =
|
|
188
|
+
var reached: Dictionary = walk_to(node, str(entry["path"]), wanted_property)
|
|
189
189
|
if reached.has("message"):
|
|
190
190
|
entry["has_property"] = false
|
|
191
191
|
return entry
|
|
@@ -194,7 +194,7 @@ func _found(node: Node, wanted_property: String) -> Dictionary:
|
|
|
194
194
|
var has: bool = _can_read(holder, named)
|
|
195
195
|
entry["has_property"] = has
|
|
196
196
|
if has:
|
|
197
|
-
entry["value"] = _values.serialize(
|
|
197
|
+
entry["value"] = _values.serialize(read_under(holder, named))
|
|
198
198
|
return entry
|
|
199
199
|
|
|
200
200
|
|
|
@@ -548,7 +548,7 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
548
548
|
return standing
|
|
549
549
|
var node: Node = standing["node"]
|
|
550
550
|
|
|
551
|
-
var reached: Dictionary =
|
|
551
|
+
var reached: Dictionary = walk_to(node, node_path, property)
|
|
552
552
|
if reached.has("message"):
|
|
553
553
|
return reached
|
|
554
554
|
|
|
@@ -563,7 +563,7 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
563
563
|
"type": "property",
|
|
564
564
|
"path": node_path,
|
|
565
565
|
"property": property,
|
|
566
|
-
"value": _values.serialize(
|
|
566
|
+
"value": _values.serialize(read_under(holder, named)),
|
|
567
567
|
}
|
|
568
568
|
|
|
569
569
|
|
|
@@ -579,10 +579,16 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
579
579
|
## at a time rather than handed to that method, which answers null for a path that goes wrong
|
|
580
580
|
## halfway along and for one that ends on null.
|
|
581
581
|
##
|
|
582
|
+
## A step written as a call, "get_viewport()", calls a method of that name that takes no arguments
|
|
583
|
+
## and walks into what it returned. Some of what hangs off a node is only reachable by asking:
|
|
584
|
+
## which control holds the focus is a question for the viewport a control is in, and the viewport
|
|
585
|
+
## is a method's answer rather than a property. Spelled with the brackets so a reader sees a call
|
|
586
|
+
## where one happens, and so a method can never shadow a property of the same name.
|
|
587
|
+
##
|
|
582
588
|
## Answers with a `message` instead when a step along the way is not there or holds something that
|
|
583
589
|
## is not an object, naming the step rather than the whole path: "/root/Main:_game has no property
|
|
584
590
|
## clocks" is a typo found, and "no property _game:clocks:speed" is a puzzle.
|
|
585
|
-
func
|
|
591
|
+
static func walk_to(node: Node, node_path: String, reaching: String) -> Dictionary:
|
|
586
592
|
var parts: PackedStringArray = reaching.split(":")
|
|
587
593
|
var holder: Variant = node
|
|
588
594
|
var called: String = node_path
|
|
@@ -590,7 +596,7 @@ func _reached(node: Node, node_path: String, reaching: String) -> Dictionary:
|
|
|
590
596
|
var named: String = parts[step]
|
|
591
597
|
if not _can_read(holder, named):
|
|
592
598
|
return {"type": "error", "message": _nothing_there(holder, named, called)}
|
|
593
|
-
holder =
|
|
599
|
+
holder = read_under(holder, named)
|
|
594
600
|
called = "%s:%s" % [called, named]
|
|
595
601
|
if not _can_hold(holder):
|
|
596
602
|
var wanted: String = parts[step + 1]
|
|
@@ -629,10 +635,33 @@ static func _can_read(holder: Variant, named: String) -> bool:
|
|
|
629
635
|
return map.has(named) or map.has(StringName(named))
|
|
630
636
|
if holder is Object:
|
|
631
637
|
var object: Object = holder
|
|
638
|
+
var method: String = _called(named)
|
|
639
|
+
if not method.is_empty():
|
|
640
|
+
return _arguments_required(object, method) == 0
|
|
632
641
|
return _has_property(object, named)
|
|
633
642
|
return false
|
|
634
643
|
|
|
635
644
|
|
|
645
|
+
## The method a step written as a call names, "get_viewport()" being get_viewport, or "" for a
|
|
646
|
+
## step that is a name.
|
|
647
|
+
static func _called(named: String) -> String:
|
|
648
|
+
return named.trim_suffix("()") if named.ends_with("()") else ""
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
## How many arguments [param method] on [param object] has to be given, or -1 when there is no such
|
|
652
|
+
## method. Read off the method list rather than [method Object.has_method], because a path can only
|
|
653
|
+
## call a method with nothing, and a method with a required argument called with nothing raises
|
|
654
|
+
## inside the game rather than answering.
|
|
655
|
+
static func _arguments_required(object: Object, method: String) -> int:
|
|
656
|
+
for entry: Dictionary in object.get_method_list():
|
|
657
|
+
if str(entry.get("name", "")) != method:
|
|
658
|
+
continue
|
|
659
|
+
var params: Array = entry.get("args", [])
|
|
660
|
+
var defaults: Array = entry.get("default_args", [])
|
|
661
|
+
return params.size() - defaults.size()
|
|
662
|
+
return -1
|
|
663
|
+
|
|
664
|
+
|
|
636
665
|
## Put [param value] where [param named] points. Only ever called once [method _can_read] agrees,
|
|
637
666
|
## and lists and maps are reference types, so writing into one reaches the game's own copy.
|
|
638
667
|
static func _write(holder: Variant, named: String, value: Variant) -> void:
|
|
@@ -653,8 +682,9 @@ static func _write(holder: Variant, named: String, value: Variant) -> void:
|
|
|
653
682
|
object.set(named, value)
|
|
654
683
|
|
|
655
684
|
|
|
656
|
-
## What [param holder] holds under [param named]
|
|
657
|
-
|
|
685
|
+
## What [param holder] holds under [param named], or answers to it when [param named] is a call.
|
|
686
|
+
## Only ever called once [method _can_read] agrees.
|
|
687
|
+
static func read_under(holder: Variant, named: String) -> Variant:
|
|
658
688
|
if holder is Array:
|
|
659
689
|
var items: Array = holder
|
|
660
690
|
return items[_index_in(named, items.size())]
|
|
@@ -664,6 +694,9 @@ static func _read(holder: Variant, named: String) -> Variant:
|
|
|
664
694
|
return map[named]
|
|
665
695
|
return map[StringName(named)]
|
|
666
696
|
var object: Object = holder
|
|
697
|
+
var method: String = _called(named)
|
|
698
|
+
if not method.is_empty():
|
|
699
|
+
return object.call(method)
|
|
667
700
|
return object.get(named)
|
|
668
701
|
|
|
669
702
|
|
|
@@ -701,6 +734,23 @@ static func _nothing_there(holder: Variant, named: String, called: String) -> St
|
|
|
701
734
|
if keys.size() > 8:
|
|
702
735
|
rest = " and %d more" % [keys.size() - 8]
|
|
703
736
|
return "%s has no key %s; it is keyed by %s%s" % [called, named, ", ".join(some), rest]
|
|
737
|
+
var object: Object = holder
|
|
738
|
+
var method: String = _called(named)
|
|
739
|
+
if not method.is_empty():
|
|
740
|
+
var required: int = _arguments_required(object, method)
|
|
741
|
+
if required == -1:
|
|
742
|
+
return "%s has no method %s" % [called, method]
|
|
743
|
+
return (
|
|
744
|
+
"%s.%s takes %d argument%s, and a path can only call a method that takes none"
|
|
745
|
+
% [called, method, required, "" if required == 1 else "s"]
|
|
746
|
+
)
|
|
747
|
+
# A method spelled as a property is the likeliest thing behind a name the object has no property
|
|
748
|
+
# for, and the caller is one pair of brackets away from what they meant.
|
|
749
|
+
if object.has_method(named):
|
|
750
|
+
return (
|
|
751
|
+
"%s has no property %s; it has a method of that name, which a path calls as %s()"
|
|
752
|
+
% [called, named, named]
|
|
753
|
+
)
|
|
704
754
|
return "%s has no property %s" % [called, named]
|
|
705
755
|
|
|
706
756
|
|
|
@@ -726,18 +776,29 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
726
776
|
return standing
|
|
727
777
|
var node: Node = standing["node"]
|
|
728
778
|
|
|
729
|
-
# Through a path as well, for the reason [method
|
|
779
|
+
# Through a path as well, for the reason [method walk_to] gives, and read back off the same
|
|
730
780
|
# holder afterwards: a set that does not take says so by answering with the old value, which is
|
|
731
781
|
# how a typed container refusing a write is told from one accepting it.
|
|
732
|
-
var reached: Dictionary =
|
|
782
|
+
var reached: Dictionary = walk_to(node, node_path, property)
|
|
733
783
|
if reached.has("message"):
|
|
734
784
|
return reached
|
|
735
785
|
|
|
736
786
|
var holder: Variant = reached["holder"]
|
|
737
787
|
var named: String = reached["name"]
|
|
788
|
+
# A call can be walked through and read, and is not a place: what it returns is the method's
|
|
789
|
+
# to hand out, and writing "into" it would set nothing the game keeps.
|
|
790
|
+
if not _called(named).is_empty():
|
|
791
|
+
return {
|
|
792
|
+
"type": "error",
|
|
793
|
+
"message":
|
|
794
|
+
(
|
|
795
|
+
"%s:%s is a call, and a call is not a place to write: name a property"
|
|
796
|
+
% [reached["called"], named]
|
|
797
|
+
)
|
|
798
|
+
}
|
|
738
799
|
if not _can_read(holder, named):
|
|
739
800
|
return {"type": "error", "message": _nothing_there(holder, named, str(reached["called"]))}
|
|
740
|
-
var old_value: Variant =
|
|
801
|
+
var old_value: Variant = read_under(holder, named)
|
|
741
802
|
# The same rule the call path holds: a value that cannot become what the property holds is
|
|
742
803
|
# refused rather than written. Writing it means the engine picks something, and what it picks
|
|
743
804
|
# for a word where a number goes is zero, which the answer then reports as the new value.
|
|
@@ -759,7 +820,7 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
759
820
|
"path": node_path,
|
|
760
821
|
"property": property,
|
|
761
822
|
"old_value": _values.serialize(old_value),
|
|
762
|
-
"new_value": _values.serialize(
|
|
823
|
+
"new_value": _values.serialize(read_under(holder, named))
|
|
763
824
|
}
|
|
764
825
|
|
|
765
826
|
|
|
@@ -776,10 +837,10 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
776
837
|
return standing
|
|
777
838
|
var node: Node = standing["node"]
|
|
778
839
|
|
|
779
|
-
# Through a path as well, for the reason [method
|
|
840
|
+
# Through a path as well, for the reason [method walk_to] gives. What a game does hangs off its
|
|
780
841
|
# nodes as much as its state does, so reading `_game:run:day` while being unable to call
|
|
781
842
|
# `_game:run:advance` answers half of what a node holds and refuses the other half.
|
|
782
|
-
var reached: Dictionary =
|
|
843
|
+
var reached: Dictionary = walk_to(node, node_path, method)
|
|
783
844
|
if reached.has("message"):
|
|
784
845
|
return reached
|
|
785
846
|
|
|
@@ -798,6 +859,11 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
798
859
|
}
|
|
799
860
|
var holder: Object = reached["holder"]
|
|
800
861
|
var named: String = reached["name"]
|
|
862
|
+
# The method a call ends on is the one being called whether or not it carries the brackets a
|
|
863
|
+
# step along the way would: "get_viewport():gui_get_focus_owner()" is the same call as without
|
|
864
|
+
# the last pair.
|
|
865
|
+
if not _called(named).is_empty():
|
|
866
|
+
named = _called(named)
|
|
801
867
|
if not holder.has_method(named):
|
|
802
868
|
return {"type": "error", "message": "%s has no method %s" % [reached["called"], named]}
|
|
803
869
|
|
|
@@ -888,13 +954,13 @@ func _serialize_node_tree(
|
|
|
888
954
|
func _named_properties_of(node: Node, path: String, named: Array[String]) -> Dictionary:
|
|
889
955
|
var properties: Dictionary = {}
|
|
890
956
|
for wanted: String in named:
|
|
891
|
-
var reached: Dictionary =
|
|
957
|
+
var reached: Dictionary = walk_to(node, path, wanted)
|
|
892
958
|
if reached.has("message"):
|
|
893
959
|
continue
|
|
894
960
|
var holder: Variant = reached["holder"]
|
|
895
961
|
var name: String = reached["name"]
|
|
896
962
|
if _can_read(holder, name):
|
|
897
|
-
properties[wanted] = _values.serialize(
|
|
963
|
+
properties[wanted] = _values.serialize(read_under(holder, name))
|
|
898
964
|
return properties
|
|
899
965
|
|
|
900
966
|
|
|
@@ -128,44 +128,15 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
128
128
|
# met" after the whole timeout, which is what a game that never reached the state answers too,
|
|
129
129
|
# so a mistyped name and a condition that did not happen read the same. The property read
|
|
130
130
|
# refuses that typo in one call, and two tools disagreeing about it is the fault.
|
|
131
|
-
var
|
|
132
|
-
if
|
|
133
|
-
return
|
|
131
|
+
var watched: Dictionary = _watched(node, node_path, property)
|
|
132
|
+
if watched.has("message"):
|
|
133
|
+
return watched
|
|
134
134
|
|
|
135
|
-
var current: Variant =
|
|
135
|
+
var current: Variant = watched["value"]
|
|
136
136
|
var wanted: Variant = _values.fitted(params["value"], typeof(current))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# from where they are standing.
|
|
141
|
-
if not Values.comparable(current, wanted):
|
|
142
|
-
return {
|
|
143
|
-
"type": "error",
|
|
144
|
-
"message":
|
|
145
|
-
(
|
|
146
|
-
(
|
|
147
|
-
"%s.%s holds %s and the value to wait for is %s. Those cannot be compared, and"
|
|
148
|
-
+ " waiting on it would stop the game rather than answer about it."
|
|
149
|
-
)
|
|
150
|
-
% [node_path, property, type_string(typeof(current)), type_string(typeof(wanted))]
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
# And a value that cannot become what the property holds, which lands worse than the halt: a
|
|
154
|
-
# word where a number goes became 0.0, 0.0 already equalled the property, and the wait answered
|
|
155
|
-
# met the instant it started, about a state nobody asked about, with the real value beside it.
|
|
156
|
-
# A refusal is read and a met is acted on.
|
|
157
|
-
if not Values.acceptable(wanted, typeof(current)):
|
|
158
|
-
return {
|
|
159
|
-
"type": "error",
|
|
160
|
-
"message":
|
|
161
|
-
(
|
|
162
|
-
(
|
|
163
|
-
"%s.%s holds %s and the value to wait for is %s, which cannot become one:"
|
|
164
|
-
+ " waiting on it would answer about a state nobody asked for."
|
|
165
|
-
)
|
|
166
|
-
% [node_path, property, type_string(typeof(current)), type_string(typeof(wanted))]
|
|
167
|
-
)
|
|
168
|
-
}
|
|
137
|
+
var refused: String = _not_comparable(current, wanted, node_path, property)
|
|
138
|
+
if not refused.is_empty():
|
|
139
|
+
return {"type": "error", "message": refused}
|
|
169
140
|
|
|
170
141
|
var started: int = Time.get_ticks_msec()
|
|
171
142
|
# The type is checked every time round, not only at the top: a property that holds an object a
|
|
@@ -178,7 +149,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
178
149
|
await _host.get_tree().process_frame
|
|
179
150
|
if not is_instance_valid(node):
|
|
180
151
|
return {"type": "error", "message": "%s was freed while waiting" % node_path}
|
|
181
|
-
|
|
152
|
+
watched = _watched(node, node_path, property)
|
|
153
|
+
if watched.has("message"):
|
|
154
|
+
return watched
|
|
155
|
+
current = watched["value"]
|
|
182
156
|
|
|
183
157
|
return {
|
|
184
158
|
"type": "condition",
|
|
@@ -190,6 +164,60 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
190
164
|
}
|
|
191
165
|
|
|
192
166
|
|
|
167
|
+
## Why [param wanted] cannot be waited for against [param current], or "" when it can.
|
|
168
|
+
##
|
|
169
|
+
## Refused before anything is evaluated, because the evaluation is what does the damage: an object
|
|
170
|
+
## compared against anything else is a hard error in GDScript, raised inside the game, which holds
|
|
171
|
+
## it at a debugger break. Both types are named because the caller cannot see either from where
|
|
172
|
+
## they are standing.
|
|
173
|
+
##
|
|
174
|
+
## And a value that cannot become what the property holds, which lands worse than the halt: a word
|
|
175
|
+
## where a number goes became 0.0, 0.0 already equalled the property, and the wait answered met the
|
|
176
|
+
## instant it started, about a state nobody asked about, with the real value beside it. A refusal
|
|
177
|
+
## is read and a met is acted on.
|
|
178
|
+
static func _not_comparable(current: Variant, wanted: Variant, node_path: String, property: String) -> String:
|
|
179
|
+
var types: Array[String] = [
|
|
180
|
+
node_path, property, type_string(typeof(current)), type_string(typeof(wanted))
|
|
181
|
+
]
|
|
182
|
+
if not Values.comparable(current, wanted):
|
|
183
|
+
return (
|
|
184
|
+
(
|
|
185
|
+
"%s.%s holds %s and the value to wait for is %s. Those cannot be compared, and"
|
|
186
|
+
+ " waiting on it would stop the game rather than answer about it."
|
|
187
|
+
)
|
|
188
|
+
% types
|
|
189
|
+
)
|
|
190
|
+
if not Values.acceptable(wanted, typeof(current)):
|
|
191
|
+
return (
|
|
192
|
+
(
|
|
193
|
+
"%s.%s holds %s and the value to wait for is %s, which cannot become one:"
|
|
194
|
+
+ " waiting on it would answer about a state nobody asked for."
|
|
195
|
+
)
|
|
196
|
+
% types
|
|
197
|
+
)
|
|
198
|
+
return ""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
## What [param property] reads as on [param node] right now, under `value`, or a `message` saying
|
|
202
|
+
## why it cannot be read.
|
|
203
|
+
##
|
|
204
|
+
## Through the same walk as the property read, so a wait can watch what a node holds,
|
|
205
|
+
## "_game:run:day", and what a call answers, "get_viewport():gui_get_focus_owner()". Walked from the
|
|
206
|
+
## node every time rather than read off the holder found at the start, because a game replaces what
|
|
207
|
+
## its nodes hold: a run object built afresh each day would leave a wait watching the day before. A
|
|
208
|
+
## step that has gone in the meantime ends the wait with what went.
|
|
209
|
+
static func _watched(node: Node, node_path: String, property: String) -> Dictionary:
|
|
210
|
+
var reached: Dictionary = Queries.walk_to(node, node_path, property)
|
|
211
|
+
if reached.has("message"):
|
|
212
|
+
return reached
|
|
213
|
+
var holder: Variant = reached["holder"]
|
|
214
|
+
var named: String = reached["name"]
|
|
215
|
+
var missing: String = Queries.nothing_under(holder, named, str(reached["called"]))
|
|
216
|
+
if not missing.is_empty():
|
|
217
|
+
return {"type": "error", "message": missing}
|
|
218
|
+
return {"value": Queries.read_under(holder, named)}
|
|
219
|
+
|
|
220
|
+
|
|
193
221
|
## Waits until something under [param node_path] has [param said] written on it.
|
|
194
222
|
##
|
|
195
223
|
## A screen rather than one node, because a panel following a clock builds its labels again every
|