gdharness 1.0.27 → 1.0.28
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 +4 -4
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +25 -12
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +3 -22
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +2 -1
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +163 -0
- package/build/godot/addons/gdharness_runtime/runtime_words.gd.uid +1 -0
- package/build/index.js +4 -4
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -16538,7 +16538,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16538
16538
|
says: {
|
|
16539
16539
|
type: "string",
|
|
16540
16540
|
ops: ["find"],
|
|
16541
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
16541
|
+
description: "find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
16542
16542
|
},
|
|
16543
16543
|
limit: {
|
|
16544
16544
|
type: "integer",
|
|
@@ -16567,7 +16567,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16567
16567
|
operations: {
|
|
16568
16568
|
tree: { summary: "the live scene tree", requires: [] },
|
|
16569
16569
|
text: {
|
|
16570
|
-
summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it",
|
|
16570
|
+
summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without its tags, text only as far as it has been typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items read a line each",
|
|
16571
16571
|
requires: []
|
|
16572
16572
|
},
|
|
16573
16573
|
find: {
|
|
@@ -16716,7 +16716,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16716
16716
|
type: "string",
|
|
16717
16717
|
ops: ["text", "choose"],
|
|
16718
16718
|
blank: true,
|
|
16719
|
-
description: 'text: what to type. A newline is Enter and a tab is Tab, a space is a space, and "" with replace empties the field. choose: the item to take, by what it says.'
|
|
16719
|
+
description: 'text: what to type. A newline is Enter and a tab is Tab, a space is a space, and "" with replace empties the field. choose: the item to take, by what it says as drawn, which in a game with translations is the translation; the key it holds is matched after that.'
|
|
16720
16720
|
},
|
|
16721
16721
|
replace: {
|
|
16722
16722
|
type: "boolean",
|
|
@@ -16778,7 +16778,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16778
16778
|
requires: ["keycode"]
|
|
16779
16779
|
},
|
|
16780
16780
|
text: {
|
|
16781
|
-
summary: "type a string into the field being edited, a character at a time, and say what it landed in",
|
|
16781
|
+
summary: "type a string into the field being edited, a character at a time, and say what it landed in and what it holds afterwards, a secret field as its mask",
|
|
16782
16782
|
requires: ["text"]
|
|
16783
16783
|
},
|
|
16784
16784
|
mouse_click: {
|
|
@@ -10,6 +10,7 @@ const Values = preload("runtime_values.gd")
|
|
|
10
10
|
## reports are the same place by construction rather than by agreement.
|
|
11
11
|
const Queries = preload("runtime_queries.gd")
|
|
12
12
|
const Read = preload("reading.gd")
|
|
13
|
+
const Words = preload("runtime_words.gd")
|
|
13
14
|
|
|
14
15
|
## The distance from a capital letter to its small one in Unicode. A keycode holds the capital.
|
|
15
16
|
const TO_SMALL: int = 32
|
|
@@ -239,11 +240,12 @@ static func _select_everything(focused: Control) -> bool:
|
|
|
239
240
|
## What the field says now, or null where the focus is not a field at all.
|
|
240
241
|
##
|
|
241
242
|
## A spin box rewrites its own text out of the number it parsed, so this is the only thing that
|
|
242
|
-
## says whether what was typed became what the field means.
|
|
243
|
+
## says whether what was typed became what the field means. A secret field answers with its mask,
|
|
244
|
+
## which still says how many characters it took.
|
|
243
245
|
static func _what_it_holds(focused: Control) -> Variant:
|
|
244
246
|
var field: LineEdit = focused as LineEdit
|
|
245
247
|
if field != null:
|
|
246
|
-
return field.text
|
|
248
|
+
return Words.masked(field) if field.secret else field.text
|
|
247
249
|
var box: TextEdit = focused as TextEdit
|
|
248
250
|
if box == null:
|
|
249
251
|
return null
|
|
@@ -666,7 +668,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
666
668
|
if menu.is_item_disabled(index):
|
|
667
669
|
return {
|
|
668
670
|
"type": "error",
|
|
669
|
-
"message": "%s item %d, %s, is disabled" % [node_path, index,
|
|
671
|
+
"message": "%s item %d, %s, is disabled" % [node_path, index, Words.item_says(menu, index)]
|
|
670
672
|
}
|
|
671
673
|
|
|
672
674
|
# Shown first, because a menu nobody has opened has no focus to move and Enter would go to
|
|
@@ -689,7 +691,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
689
691
|
"type": "chosen",
|
|
690
692
|
"path": node_path,
|
|
691
693
|
"index": index,
|
|
692
|
-
"text":
|
|
694
|
+
"text": Words.item_says(menu, index),
|
|
693
695
|
"id": menu.get_item_id(index),
|
|
694
696
|
"opened": opened,
|
|
695
697
|
"menu": str(menu.get_path()),
|
|
@@ -699,7 +701,7 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
699
701
|
var chooser: OptionButton = node as OptionButton
|
|
700
702
|
if chooser != null:
|
|
701
703
|
answer["selected"] = chooser.get_selected()
|
|
702
|
-
answer["shows"] = chooser
|
|
704
|
+
answer["shows"] = Words.said_by(chooser)
|
|
703
705
|
return answer
|
|
704
706
|
|
|
705
707
|
|
|
@@ -718,6 +720,11 @@ static func _menu_of(node: Node) -> PopupMenu:
|
|
|
718
720
|
|
|
719
721
|
## Which item was asked for: `text`, matched exactly and then case-insensitively, or `index`.
|
|
720
722
|
## Minus one when neither names one that is there.
|
|
723
|
+
##
|
|
724
|
+
## `text` is matched against the words the item shows before the ones it holds. In a game with
|
|
725
|
+
## translations they differ, and the shown ones are what a caller has read off the screen: an item
|
|
726
|
+
## held as `ACT_ENGAGE` and shown as "Hire" refused "Hire" and listed the keys. The held words
|
|
727
|
+
## still count after them, for a caller that has the key from the source.
|
|
721
728
|
static func _wanted_item(menu: PopupMenu, params: Dictionary) -> int:
|
|
722
729
|
if params.has("index"):
|
|
723
730
|
var asked: int = Read.as_int(params.get("index", -1), -1)
|
|
@@ -725,20 +732,26 @@ static func _wanted_item(menu: PopupMenu, params: Dictionary) -> int:
|
|
|
725
732
|
var wanted: String = str(params.get("text", ""))
|
|
726
733
|
if wanted.is_empty():
|
|
727
734
|
return -1
|
|
735
|
+
var shown: Array[String] = []
|
|
736
|
+
var held: Array[String] = []
|
|
728
737
|
for index: int in menu.get_item_count():
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
for
|
|
732
|
-
|
|
733
|
-
|
|
738
|
+
shown.append(Words.item_says(menu, index))
|
|
739
|
+
held.append(menu.get_item_text(index))
|
|
740
|
+
for words: Array[String] in [shown, held]:
|
|
741
|
+
for index: int in words.size():
|
|
742
|
+
if words[index] == wanted:
|
|
743
|
+
return index
|
|
744
|
+
for index: int in words.size():
|
|
745
|
+
if words[index].nocasecmp_to(wanted) == 0:
|
|
746
|
+
return index
|
|
734
747
|
return -1
|
|
735
748
|
|
|
736
749
|
|
|
737
|
-
## What the menu
|
|
750
|
+
## What the menu shows, for a refusal that names the choices rather than the miss.
|
|
738
751
|
static func _items_of(menu: PopupMenu) -> Array[String]:
|
|
739
752
|
var said: Array[String] = []
|
|
740
753
|
for index: int in menu.get_item_count():
|
|
741
|
-
said.append("%d: %s" % [index,
|
|
754
|
+
said.append("%d: %s" % [index, Words.item_says(menu, index)])
|
|
742
755
|
return said
|
|
743
756
|
|
|
744
757
|
|
|
@@ -6,6 +6,7 @@ extends RefCounted
|
|
|
6
6
|
const Paths = preload("runtime_paths.gd")
|
|
7
7
|
const Read = preload("reading.gd")
|
|
8
8
|
const Values = preload("runtime_values.gd")
|
|
9
|
+
const Words = preload("runtime_words.gd")
|
|
9
10
|
|
|
10
11
|
## The most nodes one find answers with, unless asked for fewer: enough for any real query and
|
|
11
12
|
## far short of the tree dump a query exists to avoid.
|
|
@@ -251,7 +252,7 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
|
|
|
251
252
|
# What this node says, rather than everything said underneath it. A row is then found by the
|
|
252
253
|
# label in it, and the path answered is that label's, which is where the words a caller is
|
|
253
254
|
# looking at actually are: matching every container above it would answer with the screen.
|
|
254
|
-
if not wanted["says"].is_empty() and not _says(said_by(node), wanted["says"]):
|
|
255
|
+
if not wanted["says"].is_empty() and not _says(Words.said_by(node), wanted["says"]):
|
|
255
256
|
return false
|
|
256
257
|
var script: Variant = node.get_script()
|
|
257
258
|
if not wanted["script"].is_empty():
|
|
@@ -346,8 +347,7 @@ func _read_into(node: Node, include_hidden: bool, most: int, into: Array[String]
|
|
|
346
347
|
if not include_hidden and not _drawn(node):
|
|
347
348
|
return 0
|
|
348
349
|
var left_out: int = 0
|
|
349
|
-
|
|
350
|
-
if not said.is_empty():
|
|
350
|
+
for said: String in Words.lines_said_by(node):
|
|
351
351
|
if into.size() < most:
|
|
352
352
|
into.append(said)
|
|
353
353
|
else:
|
|
@@ -373,25 +373,6 @@ static func _drawn(node: Node) -> bool:
|
|
|
373
373
|
return window == null or window.visible
|
|
374
374
|
|
|
375
375
|
|
|
376
|
-
## What one node says, or "" for a node that says nothing. Anything with a `text` property, which
|
|
377
|
-
## is every label, button and field the interface is built out of.
|
|
378
|
-
##
|
|
379
|
-
## Public because three questions are the same question: what a screen reads as, which nodes say a
|
|
380
|
-
## given word, and whether anything has come to say it yet. Two copies of what a node says is how
|
|
381
|
-
## the three of them come to disagree about a SpinBox.
|
|
382
|
-
##
|
|
383
|
-
## Read rather than looked up in the property list, which the engine builds afresh on every call: a
|
|
384
|
-
## wait asks this of every node on the screen every frame, and on a hall of 3,500 nodes the lookup
|
|
385
|
-
## took the game it was watching from 60 frames a second to 11. A node without the property reads
|
|
386
|
-
## as null, which is not a string.
|
|
387
|
-
static func said_by(node: Node) -> String:
|
|
388
|
-
var text: Variant = node.get("text")
|
|
389
|
-
if not text is String:
|
|
390
|
-
return ""
|
|
391
|
-
var words: String = text
|
|
392
|
-
return words.strip_edges()
|
|
393
|
-
|
|
394
|
-
|
|
395
376
|
func get_rect(params: Dictionary) -> Dictionary:
|
|
396
377
|
var node_path: String = str(params.get("path", ""))
|
|
397
378
|
if node_path.is_empty():
|
|
@@ -7,6 +7,7 @@ const Paths = preload("runtime_paths.gd")
|
|
|
7
7
|
const Queries = preload("runtime_queries.gd")
|
|
8
8
|
const Read = preload("reading.gd")
|
|
9
9
|
const Values = preload("runtime_values.gd")
|
|
10
|
+
const Words = preload("runtime_words.gd")
|
|
10
11
|
|
|
11
12
|
## The longest one wait may last, whatever the request says: past this the server has long
|
|
12
13
|
## since given up on the reply.
|
|
@@ -291,7 +292,7 @@ func _anything_says(node_path: String, said: String, include_hidden: bool) -> bo
|
|
|
291
292
|
var pending: Array[Node] = [root]
|
|
292
293
|
while not pending.is_empty():
|
|
293
294
|
var node: Node = pending.pop_back()
|
|
294
|
-
if
|
|
295
|
+
if Words.said_by(node).containsn(said) and (include_hidden or Queries.shown(node)):
|
|
295
296
|
return true
|
|
296
297
|
pending.append_array(node.get_children(true))
|
|
297
298
|
return false
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## What a node says to a player, read the way it is drawn: the parsed words of rich text, as much of
|
|
4
|
+
## a line as is typed out so far, the translation rather than its key, a mask over a secret, and the
|
|
5
|
+
## items of the controls that draw a list.
|
|
6
|
+
##
|
|
7
|
+
## One module because three questions are the same question: what a screen reads as, which nodes say
|
|
8
|
+
## a given word, and whether anything has come to say it yet. Two copies of what a node says is how
|
|
9
|
+
## the three of them come to disagree about a SpinBox.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## What one node says, or "" for a node that says nothing. Anything with a `text` property, which
|
|
13
|
+
## is every label, button and field the interface is built out of.
|
|
14
|
+
##
|
|
15
|
+
## Read rather than looked up in the property list, which the engine builds afresh on every call: a
|
|
16
|
+
## wait asks this of every node on the screen every frame, and on a hall of 3,500 nodes the lookup
|
|
17
|
+
## took the game it was watching from 60 frames a second to 11. A node without the property reads
|
|
18
|
+
## as null, which is not a string.
|
|
19
|
+
##
|
|
20
|
+
## A label is asked about first and read directly, because a screen is mostly labels and this runs
|
|
21
|
+
## for each of them on every look a wait takes.
|
|
22
|
+
static func said_by(node: Node) -> String:
|
|
23
|
+
var label: Label = node as Label
|
|
24
|
+
if label != null:
|
|
25
|
+
return _label_says(label)
|
|
26
|
+
if _draws_a_list(node):
|
|
27
|
+
return "\n".join(lines_said_by(node))
|
|
28
|
+
return _text_of(node)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Every line [param node] draws for a player to read, one to an entry: a label's one line, or each
|
|
32
|
+
## item of a control that draws a list of them. Tab titles, list items, tree rows, menu titles and
|
|
33
|
+
## the items of an open menu are held by the control rather than in any `text`, so a screen read as
|
|
34
|
+
## every label on it and none of those, and a wait for a tab's title or a list item was never met.
|
|
35
|
+
static func lines_said_by(node: Node) -> Array[String]:
|
|
36
|
+
var lines: Array[String] = []
|
|
37
|
+
if node is TabBar:
|
|
38
|
+
var bar: TabBar = node
|
|
39
|
+
for tab: int in bar.tab_count:
|
|
40
|
+
if not bar.is_tab_hidden(tab):
|
|
41
|
+
_keep(node.atr(bar.get_tab_title(tab)), lines)
|
|
42
|
+
elif node is MenuBar:
|
|
43
|
+
var menus: MenuBar = node
|
|
44
|
+
for menu: int in menus.get_menu_count():
|
|
45
|
+
if not menus.is_menu_hidden(menu):
|
|
46
|
+
_keep(node.atr(menus.get_menu_title(menu)), lines)
|
|
47
|
+
elif node is ItemList:
|
|
48
|
+
var list: ItemList = node
|
|
49
|
+
for item: int in list.item_count:
|
|
50
|
+
_keep(_as_drawn(node, list.get_item_text(item), list.get_item_auto_translate_mode(item)), lines)
|
|
51
|
+
elif node is PopupMenu:
|
|
52
|
+
var menu: PopupMenu = node
|
|
53
|
+
for item: int in menu.item_count:
|
|
54
|
+
if not menu.is_item_separator(item):
|
|
55
|
+
_keep(item_says(menu, item), lines)
|
|
56
|
+
elif node is Tree:
|
|
57
|
+
var tree: Tree = node
|
|
58
|
+
if tree.column_titles_visible:
|
|
59
|
+
var titles: Array[String] = []
|
|
60
|
+
for column: int in tree.columns:
|
|
61
|
+
_keep(node.atr(tree.get_column_title(column)), titles)
|
|
62
|
+
_keep(" ".join(titles), lines)
|
|
63
|
+
_tree_rows(tree, tree.get_root(), tree.hide_root, lines)
|
|
64
|
+
else:
|
|
65
|
+
_keep(_text_of(node), lines)
|
|
66
|
+
return lines
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## What item [param index] of [param menu] shows: the translation where the item has one. A menu is
|
|
70
|
+
## chosen from by these words, since they are the ones a caller has read off the screen.
|
|
71
|
+
static func item_says(menu: PopupMenu, index: int) -> String:
|
|
72
|
+
return _as_drawn(menu, menu.get_item_text(index), menu.get_item_auto_translate_mode(index))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## What a secret [param field] draws: one mask character for each character it holds. Reading the
|
|
76
|
+
## words back would hand a password to whoever reads the answer, which the player cannot see either.
|
|
77
|
+
static func masked(field: LineEdit) -> String:
|
|
78
|
+
return field.secret_character.repeat(field.text.length())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
static func _draws_a_list(node: Node) -> bool:
|
|
82
|
+
return node is TabBar or node is MenuBar or node is ItemList or node is PopupMenu or node is Tree
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
static func _keep(line: String, into: Array[String]) -> void:
|
|
86
|
+
var trimmed: String = line.strip_edges()
|
|
87
|
+
if not trimmed.is_empty():
|
|
88
|
+
into.append(trimmed)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## [param words] as [param node] draws them, which for a game with translations is the translation
|
|
92
|
+
## rather than the key it holds: a control set to `UI_HIRE` shows "Hire", and a wait for "Hire" was
|
|
93
|
+
## never met. [param mode] is an item's own setting, which the engine reads before the node's.
|
|
94
|
+
static func _as_drawn(
|
|
95
|
+
node: Node, words: String, mode: Node.AutoTranslateMode = Node.AUTO_TRANSLATE_MODE_INHERIT
|
|
96
|
+
) -> String:
|
|
97
|
+
match mode:
|
|
98
|
+
Node.AUTO_TRANSLATE_MODE_ALWAYS:
|
|
99
|
+
return node.tr(words)
|
|
100
|
+
Node.AUTO_TRANSLATE_MODE_DISABLED:
|
|
101
|
+
return words
|
|
102
|
+
return node.atr(words)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## The rows of a tree from [param item] down, a row being its columns' text side by side, and
|
|
106
|
+
## nothing under a collapsed row or a hidden one, since neither is drawn. A hidden root is only the
|
|
107
|
+
## place its children hang from.
|
|
108
|
+
static func _tree_rows(tree: Tree, item: TreeItem, skip: bool, into: Array[String]) -> void:
|
|
109
|
+
if item == null or not item.visible:
|
|
110
|
+
return
|
|
111
|
+
if not skip:
|
|
112
|
+
var cells: Array[String] = []
|
|
113
|
+
for column: int in tree.columns:
|
|
114
|
+
_keep(_as_drawn(tree, item.get_text(column), item.get_auto_translate_mode(column)), cells)
|
|
115
|
+
_keep(" ".join(cells), into)
|
|
116
|
+
if item.collapsed:
|
|
117
|
+
return
|
|
118
|
+
var child: TreeItem = item.get_first_child()
|
|
119
|
+
while child != null:
|
|
120
|
+
_tree_rows(tree, child, false, into)
|
|
121
|
+
child = child.get_next()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## What a node with a `text` says, as it is drawn.
|
|
125
|
+
static func _text_of(node: Node) -> String:
|
|
126
|
+
var label: Label = node as Label
|
|
127
|
+
if label != null:
|
|
128
|
+
return _label_says(label)
|
|
129
|
+
# A RichTextLabel's text is its markup when it reads BBCode, and nobody reads the tags: a
|
|
130
|
+
# dossier line came back as "[b][color=#4fc2d4]Mollum Telken[/color], [/b]..." and a phrase
|
|
131
|
+
# running across a tag could not be found or waited for. The parsed text is what is drawn, and
|
|
132
|
+
# it also holds what a game added with append_text(), which the property never shows. It is
|
|
133
|
+
# parsed from the translation already.
|
|
134
|
+
if node is RichTextLabel:
|
|
135
|
+
var rich: RichTextLabel = node
|
|
136
|
+
return _shown_part(rich.get_parsed_text(), rich.visible_characters)
|
|
137
|
+
var text: Variant = node.get("text")
|
|
138
|
+
if not text is String:
|
|
139
|
+
return ""
|
|
140
|
+
var words: String = text
|
|
141
|
+
# What somebody typed is drawn as typed, never translated.
|
|
142
|
+
if node is LineEdit:
|
|
143
|
+
var field: LineEdit = node
|
|
144
|
+
return masked(field) if field.secret else words.strip_edges()
|
|
145
|
+
if node is TextEdit:
|
|
146
|
+
return words.strip_edges()
|
|
147
|
+
words = node.atr(words)
|
|
148
|
+
if node is Label3D:
|
|
149
|
+
var sign_text: Label3D = node
|
|
150
|
+
return (words.to_upper() if sign_text.uppercase else words).strip_edges()
|
|
151
|
+
return words.strip_edges()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
static func _label_says(label: Label) -> String:
|
|
155
|
+
var words: String = label.atr(label.text)
|
|
156
|
+
return _shown_part(words.to_upper() if label.uppercase else words, label.visible_characters)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## As much of [param words] as is drawn: a label typing a line out shows [param drawn] characters
|
|
160
|
+
## of it, and -1 is all of them. Read whole, a words wait was met on the first frame of a line being
|
|
161
|
+
## typed out, before the player could read any of it.
|
|
162
|
+
static func _shown_part(words: String, drawn: int) -> String:
|
|
163
|
+
return (words if drawn < 0 else words.substr(0, drawn)).strip_edges()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://u3ml7sk81frh
|
package/build/index.js
CHANGED
|
@@ -30545,7 +30545,7 @@ var TOOL_SPECS = [
|
|
|
30545
30545
|
says: {
|
|
30546
30546
|
type: "string",
|
|
30547
30547
|
ops: ["find"],
|
|
30548
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
30548
|
+
description: "find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
30549
30549
|
},
|
|
30550
30550
|
limit: {
|
|
30551
30551
|
type: "integer",
|
|
@@ -30574,7 +30574,7 @@ var TOOL_SPECS = [
|
|
|
30574
30574
|
operations: {
|
|
30575
30575
|
tree: { summary: "the live scene tree", requires: [] },
|
|
30576
30576
|
text: {
|
|
30577
|
-
summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it",
|
|
30577
|
+
summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without its tags, text only as far as it has been typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items read a line each",
|
|
30578
30578
|
requires: []
|
|
30579
30579
|
},
|
|
30580
30580
|
find: {
|
|
@@ -30723,7 +30723,7 @@ var TOOL_SPECS = [
|
|
|
30723
30723
|
type: "string",
|
|
30724
30724
|
ops: ["text", "choose"],
|
|
30725
30725
|
blank: true,
|
|
30726
|
-
description: 'text: what to type. A newline is Enter and a tab is Tab, a space is a space, and "" with replace empties the field. choose: the item to take, by what it says.'
|
|
30726
|
+
description: 'text: what to type. A newline is Enter and a tab is Tab, a space is a space, and "" with replace empties the field. choose: the item to take, by what it says as drawn, which in a game with translations is the translation; the key it holds is matched after that.'
|
|
30727
30727
|
},
|
|
30728
30728
|
replace: {
|
|
30729
30729
|
type: "boolean",
|
|
@@ -30785,7 +30785,7 @@ var TOOL_SPECS = [
|
|
|
30785
30785
|
requires: ["keycode"]
|
|
30786
30786
|
},
|
|
30787
30787
|
text: {
|
|
30788
|
-
summary: "type a string into the field being edited, a character at a time, and say what it landed in",
|
|
30788
|
+
summary: "type a string into the field being edited, a character at a time, and say what it landed in and what it holds afterwards, a secret field as its mask",
|
|
30789
30789
|
requires: ["text"]
|
|
30790
30790
|
},
|
|
30791
30791
|
mouse_click: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|