@mudlet/mudlet-web 0.3.0 → 0.4.0

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 (111) hide show
  1. package/LICENSE +338 -338
  2. package/README.md +158 -150
  3. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  4. package/dist-lib/branding.d.ts +1 -1
  5. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  6. package/dist-lib/hooks/useOutput.d.ts +3 -1
  7. package/dist-lib/import/defaultPackages.d.ts +35 -2
  8. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  9. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  10. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  11. package/dist-lib/import/defaults/mudlet-mapper.xml +13023 -13023
  12. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  13. package/dist-lib/import/packageExport.d.ts +71 -0
  14. package/dist-lib/import/packageInstaller.d.ts +4 -1
  15. package/dist-lib/index.js +45910 -39931
  16. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  17. package/dist-lib/map/MapStore.d.ts +32 -4
  18. package/dist-lib/map/mapImageExport.d.ts +1 -1
  19. package/dist-lib/map/mapZoom.d.ts +71 -0
  20. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  21. package/dist-lib/mud/MudSession.d.ts +113 -11
  22. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  23. package/dist-lib/mud/commandSplit.d.ts +16 -0
  24. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  25. package/dist-lib/mud/connection/MudClient.d.ts +136 -19
  26. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  27. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  28. package/dist-lib/mud/events.d.ts +86 -4
  29. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  30. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  31. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  32. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  33. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  34. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  35. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  36. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  37. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  38. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  39. package/dist-lib/mud/protocol/index.d.ts +1 -1
  40. package/dist-lib/mud/protocol/msspTls.d.ts +54 -0
  41. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  42. package/dist-lib/mud/protocol/tlsCodes.d.ts +4 -0
  43. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  44. package/dist-lib/mud/text/Console.d.ts +64 -0
  45. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  46. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  47. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  48. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  49. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  50. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  51. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  52. package/dist-lib/scripting/IScriptingRuntime.d.ts +22 -0
  53. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  54. package/dist-lib/scripting/ScriptingAPI.d.ts +331 -54
  55. package/dist-lib/scripting/ScriptingEngine.d.ts +181 -24
  56. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  57. package/dist-lib/scripting/lua/LuaRuntime.d.ts +159 -2
  58. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  59. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  60. package/dist-lib/storage/index.d.ts +1 -1
  61. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  62. package/dist-lib/storage/schema.d.ts +166 -4
  63. package/dist-lib/styles.css +1 -1
  64. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  65. package/dist-lib/ui/CharLoginModal.d.ts +5 -1
  66. package/dist-lib/ui/CommandBar.d.ts +7 -1
  67. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  68. package/dist-lib/ui/FileBrowserModal.d.ts +1 -0
  69. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  70. package/dist-lib/ui/HelpModal.d.ts +11 -0
  71. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  72. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  73. package/dist-lib/ui/SettingsModal.d.ts +5 -1
  74. package/dist-lib/ui/TlsAlertBanner.d.ts +18 -0
  75. package/dist-lib/ui/TlsCertificateBox.d.ts +20 -0
  76. package/dist-lib/ui/TlsUpgradeModal.d.ts +16 -0
  77. package/dist-lib/ui/Toolbar.d.ts +2 -1
  78. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  79. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  80. package/dist-lib/ui/components/index.d.ts +1 -0
  81. package/dist-lib/ui/components/useModalFocus.d.ts +6 -0
  82. package/dist-lib/ui/helpTopics.d.ts +19 -0
  83. package/dist-lib/ui/labels/LabelManager.d.ts +32 -0
  84. package/dist-lib/ui/labels/labelLinks.d.ts +52 -0
  85. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  86. package/dist-lib/ui/markdown.d.ts +10 -1
  87. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  88. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  89. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  90. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  91. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  92. package/dist-lib/ui/output/linkNavigation.d.ts +22 -0
  93. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  94. package/dist-lib/ui/search/matcher.d.ts +15 -0
  95. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  96. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  97. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  98. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  99. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  100. package/dist-lib/ui/windows/WindowManager.d.ts +149 -2
  101. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  102. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  103. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  104. package/dist-lib/ui/windows/types.d.ts +35 -0
  105. package/dist-lib/utils/describeThrown.d.ts +22 -0
  106. package/dist-lib/utils/fontLoader.d.ts +5 -0
  107. package/dist-lib/vfs-sw.js +131 -131
  108. package/dist-lib/vite.js +22 -0
  109. package/package.json +97 -93
  110. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  111. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Undoing the game's own line wrapping — Mudlet's `undoServerWrap` /
3
+ * `undoServerWrapWidth` (`TBuffer.cpp`).
4
+ *
5
+ * Many MUDs word-wrap their output server-side at a fixed column. That makes
6
+ * triggers awkward to write (a phrase can be split across two "lines" that were
7
+ * never separate) and pins the display to the game's width instead of the
8
+ * window's. Turning the option on holds back any line that ends where the game's
9
+ * wrapper would have broken it and glues the continuation on, so triggers and
10
+ * rendering see one whole logical line.
11
+ *
12
+ * The heuristics live here as pure functions over *visible* text so they can be
13
+ * tested directly, and so the pipeline glue in `ScriptingEngine` stays readable.
14
+ *
15
+ * **Visible text, not raw.** Mudlet judges `mMudLine`, which is plain characters
16
+ * with the styling held alongside in a `TChar` buffer. mudix keeps styling
17
+ * inline as ANSI escapes, so every predicate here takes text that has been put
18
+ * through {@link visibleText} first — otherwise a colour-heavy line would be
19
+ * measured as far longer than the player sees, and its escape punctuation would
20
+ * drag the letter ratio below the prose threshold.
21
+ */
22
+ /** How far below the configured column a line may end and still count as
23
+ * ending *at* it. The game breaks at the last space that fits, so a wrapped
24
+ * segment falls up to a word short. Mudlet's `csmServerWrapSlack`. */
25
+ export declare const SERVER_WRAP_SLACK = 15;
26
+ /** Refuse to keep joining past this visible length — a runaway join would
27
+ * otherwise swallow a whole screen. Mudlet's `csmServerWrapMaxJoinedLength`. */
28
+ export declare const SERVER_WRAP_MAX_JOINED_LENGTH = 10000;
29
+ /** How much clear room the continuation's first word must have had on the line
30
+ * above before that line counts as one the game ended itself. Mudlet's
31
+ * `csmServerWrapFitTolerance` — only *clear* room disproves a wrap, because a
32
+ * segment inside the join band already sits up to {@link SERVER_WRAP_SLACK}
33
+ * short of the column. */
34
+ export declare const SERVER_WRAP_FIT_TOLERANCE = 8;
35
+ /** Longest run of digits a bare list label may carry (`1.`, `12)`, `999.`).
36
+ * Mudlet's `csmMaxListNumberDigits`. Bracketed labels are exempt — `[1366]` is
37
+ * a label however long it runs, where a bare `1364.` is as likely a price. */
38
+ export declare const MAX_LIST_NUMBER_DIGITS = 3;
39
+ /** How much of a held game line's opening is kept for comparing a continuation
40
+ * against. Mudlet's `csmServerWrapSegmentStartChars` — only the first
41
+ * {@link SERVER_WRAP_REPEATED_WORDS} shared words and the spaces after them
42
+ * have to fall inside it, and that leaves room for it even where a long name
43
+ * is one of them. */
44
+ export declare const SERVER_WRAP_SEGMENT_START_CHARS = 48;
45
+ /** How many opening words a continuation has to share with the held segment
46
+ * before it is a message the game re-prefixed rather than a wrap. Mudlet's
47
+ * `csmServerWrapRepeatedWords` — one is far too common in prose to prove
48
+ * anything. */
49
+ export declare const SERVER_WRAP_REPEATED_WORDS = 2;
50
+ /** How long a held-back line waits for its continuation before being committed
51
+ * on its own. Mudlet's `csmServerWrapFlushDelayMs`. */
52
+ export declare const SERVER_WRAP_FLUSH_DELAY_MS = 300;
53
+ /** Bounds on `undoServerWrapWidth`, matching Mudlet's `setConfig` validation
54
+ * (and the `qBound` its profile-XML import applies). */
55
+ export declare const SERVER_WRAP_WIDTH_MIN = 20;
56
+ export declare const SERVER_WRAP_WIDTH_MAX = 500;
57
+ /** Mudlet's `Host::mUndoServerWrapWidth` default. */
58
+ export declare const SERVER_WRAP_WIDTH_DEFAULT = 80;
59
+ /** Strip ANSI/OSC escapes so a line can be measured and classified the way the
60
+ * player sees it. Uses the shared scanner, so it skips CSI, OSC, DCS-family and
61
+ * short escapes alike rather than guessing at a regex. */
62
+ export declare function visibleText(line: string): string;
63
+ /**
64
+ * Mudlet `TBuffer::endsAtServerWrapColumn`. True when a segment's visible length
65
+ * lands in the `[width - slack, width]` band — i.e. where the game's own word
66
+ * wrap would have broken it.
67
+ */
68
+ export declare function endsAtServerWrapColumn(visibleLength: number, width: number): boolean;
69
+ /**
70
+ * Mudlet `TBuffer::looksLikeWrappedProse`. Word-wrapped prose breaks *between
71
+ * words*, so a wrapped segment ends in a word (or a sentence mark that happened
72
+ * to land on the wrap column) and is mostly letters. ASCII art, dividers and
73
+ * table borders also run right up to the screen width but end in — and are full
74
+ * of — symbols; gluing those back together would mangle them.
75
+ *
76
+ * `visible` must already be ANSI-stripped.
77
+ */
78
+ export declare function looksLikeWrappedProse(visible: string): boolean;
79
+ /**
80
+ * Mudlet `TBuffer::segmentEndsSettledSentence`. Where a game swallows the space
81
+ * it breaks at, a segment that finishes a sentence right on the wrap column
82
+ * looks exactly like one the game ended there itself — and games end lines on
83
+ * sentences all the time: a room description, the exits line below it, the
84
+ * object line below that. Joining those runs separate lines into one and breaks
85
+ * triggers anchored to their starts.
86
+ *
87
+ * A kept break space is the one piece of evidence that settles it, so only a
88
+ * sentence with nothing at all after it is refused — the check reads the very
89
+ * last character rather than the last non-space one. What that costs is a
90
+ * genuine wrap landing exactly after a sentence staying split, which reads as a
91
+ * break at a sentence boundary; far cheaper than the joins it prevents.
92
+ *
93
+ * This belongs at the *hold* decision only: continuations legitimately end
94
+ * sentences, so {@link looksLikeWrappedProse} must keep accepting them.
95
+ *
96
+ * `visible` must already be ANSI-stripped.
97
+ */
98
+ export declare function segmentEndsSettledSentence(visible: string): boolean;
99
+ /**
100
+ * Mudlet `TBuffer::continuationRepeatsSegmentOpening`. Games that wrap a tell,
101
+ * say or channel message themselves put the whole prefix back on every physical
102
+ * line of it (`Anne teilt Dir mit: …`). Those lines break mid-sentence at the
103
+ * wrap column and leave no room for the next word, so every other check reads
104
+ * them as one wrapped paragraph and the join buries the prefix in the middle of
105
+ * it. The repeated opening is what gives them away — two words of it, because
106
+ * prose starts line after line with the same article or pronoun and one proves
107
+ * nothing.
108
+ *
109
+ * `segmentStart` is the held *game line's* opening (see
110
+ * {@link SERVER_WRAP_SEGMENT_START_CHARS}), not the joined paragraph's: a
111
+ * continuation is compared against the line it actually follows.
112
+ *
113
+ * Both strings must already be ANSI-stripped.
114
+ */
115
+ export declare function continuationRepeatsSegmentOpening(segmentStart: string, visibleNext: string): boolean;
116
+ /**
117
+ * Mudlet `TBuffer::startsWithListMarker`. A list entry reads exactly like
118
+ * wrapped prose — a sentence that can end right at the wrap column — so only its
119
+ * marker tells a list apart from a paragraph.
120
+ *
121
+ * Every form accepted here has to be one the game's own word wrap could not
122
+ * produce at the start of a continuation. The ambiguous ones are deliberately
123
+ * left out: a spaced dash opens a continuation whenever the wrap lands on it,
124
+ * and a parenthesised number is as often an aside as a label, so it is capped at
125
+ * {@link MAX_LIST_NUMBER_DIGITS} like a bare one. Only `[...]` is exempt from
126
+ * the cap — nothing writes `[2500]` mid-sentence.
127
+ *
128
+ * `visible` must already be ANSI-stripped.
129
+ */
130
+ export declare function startsWithListMarker(visible: string): boolean;
131
+ /**
132
+ * Mudlet `TBuffer::pendingLineHadRoomForNextWord`. Word wrap breaks a line for
133
+ * one reason only: the next word did not fit. So a continuation whose first word
134
+ * would have gone on the game line above it is not a continuation at all — the
135
+ * game ended that line itself. Without this, two sentences that each stop short
136
+ * of the wrap column read exactly like one wrapped paragraph.
137
+ *
138
+ * `heldSegmentLength` is the length of the last *game line* joined into the held
139
+ * text, not of the held text itself: once a paragraph has been joined the held
140
+ * text is longer than the wrap column, and the line the next word would have
141
+ * landed on is the one the game last sent.
142
+ *
143
+ * Both strings must already be ANSI-stripped.
144
+ */
145
+ export declare function pendingLineHadRoomForNextWord(visiblePending: string, heldSegmentLength: number, visibleNext: string, width: number): boolean;
146
+ /**
147
+ * Mudlet's guard before joining: a genuine continuation of wrapped prose starts
148
+ * with a word — or with the single space some games move the break to instead of
149
+ * swallowing it. *Deeper* indentation, a segment that isn't prose at all, a list
150
+ * entry, or a first word that would have fitted on the line above all mean the
151
+ * held line was complete after all and must be committed on its own first.
152
+ *
153
+ * `visibleNext` must already be ANSI-stripped.
154
+ */
155
+ export declare function shouldCommitPendingBeforeJoin(visibleNext: string, nextIsProse: boolean, fit?: {
156
+ visiblePending: string;
157
+ heldSegmentLength: number;
158
+ segmentStart: string;
159
+ width: number;
160
+ }): boolean;
161
+ /**
162
+ * Mudlet `TBuffer::joinPendingServerWrapOntoCurrent`. Concatenates the held line
163
+ * and its continuation, restoring the space the game's wrapper swallowed when
164
+ * neither side already carries one.
165
+ *
166
+ * Operates on the **raw** lines (escapes and all): mudix's styling is inline, so
167
+ * a plain concatenation carries each half's colour across untouched — the
168
+ * `TChar`-buffer splice Mudlet needs has no equivalent here. The space-insertion
169
+ * test looks at the visible text so a trailing colour reset can't hide the space
170
+ * that is already there.
171
+ */
172
+ export declare function joinWrappedLines(pending: string, next: string): string;
173
+ /** Lines shorter than this carry no signal about a wrap column. */
174
+ export declare const WRAP_DETECT_MIN_LENGTH = 40;
175
+ /** Evaluate the collected sample only once every this many recorded lines. */
176
+ export declare const WRAP_DETECT_SAMPLE_INTERVAL = 100;
177
+ /** How many lines must sit at the ceiling before it's believable. */
178
+ export declare const WRAP_DETECT_THRESHOLD = 40;
179
+ /** A plausible wrap column has to fall in this range. */
180
+ export declare const WRAP_DETECT_CEILING_MIN = 60;
181
+ export declare const WRAP_DETECT_CEILING_MAX = 160;
182
+ /**
183
+ * Given the tally of observed line lengths, report the game's apparent wrap
184
+ * column, or null when the evidence doesn't support one.
185
+ *
186
+ * The ceiling is the longest length seen more than incidentally (≥3 times); it
187
+ * has to be a plausible screen width, carry enough lines just below it, and have
188
+ * almost nothing beyond it — a game that regularly emits longer lines is not
189
+ * wrapping at that column.
190
+ */
191
+ export declare function detectWrapCeiling(counts: ReadonlyMap<number, number>): number | null;
@@ -1,3 +1,4 @@
1
+ import { ItemIdSequence } from '../ItemIdSequence';
1
2
  import type { TimerNode } from '../../storage/schema';
2
3
  export type { TimerNode };
3
4
  type TempFn = () => void;
@@ -8,15 +9,23 @@ export declare class TimerEngine {
8
9
  * share a name; we keep id as the canonical handle and build a separate
9
10
  * name → id index for Mudlet's name-based lookups. */
10
11
  private readonly perm;
11
- /** Name → first matching id, used by remainingTime / kill-by-name. */
12
+ /** Name → first matching id, used by remainingTime / kill-by-name. Only
13
+ * holds *armed* timers, since that is what loadPerm builds handles for. */
12
14
  private readonly permNameToId;
13
- private nextId;
14
- /** Number of live session-scoped temp timers (Mudlet `getProfileStats` temp count). */
15
+ /** Every stored permanent timer name, armed or not. remainingTime needs it
16
+ * to tell "exists but inactive" (-1) from "no such timer" (-2): a disabled
17
+ * timer has no handle, so its absence from `perm` proves nothing. */
18
+ private readonly knownPermNames;
19
+ /** Shared with every other engine in the profile — see ItemIdSequence. */
20
+ private idSeq;
21
+ setIdSequence(seq: ItemIdSequence): void;
22
+ /** Number of live session-scoped temp timers (Mudlet `getProfileStats` temp
23
+ * count). Killed-but-unreaped timers are not live and don't count. */
15
24
  get tempCount(): number;
16
25
  addTemp(seconds: number, fn: TempFn, repeat?: boolean): number;
17
26
  /**
18
- * Fire every one-shot timer that has come due, without waiting for the
19
- * event loop to deliver its setTimeout.
27
+ * Fire every timer that has come due, without waiting for the event loop to
28
+ * deliver its setTimeout.
20
29
  *
21
30
  * This exists for `waitForEvent`, the busted-only helper Mudlet implements
22
31
  * by spinning a nested QEventLoop. A browser can't re-enter its event loop,
@@ -24,15 +33,33 @@ export declare class TimerEngine {
24
33
  * deadlock; pumping the due timers by hand is the equivalent of Qt draining
25
34
  * its timer queue inside that nested loop.
26
35
  *
27
- * Repeating timers are deliberately skipped: their setInterval is still
28
- * armed, and firing them here would double-fire each tick. A repeat timer
29
- * therefore misses ticks across a synchronous wait the same thing that
30
- * happens whenever the main thread is busy.
36
+ * Repeating timers are pumped too Mudlet's own specs wait on a repeat
37
+ * tick, so skipping them made every such wait time out. That works because
38
+ * a repeat is a self-rescheduling setTimeout (see addTemp): the pending
39
+ * tick is cancelled and re-armed rather than left to land a second time.
31
40
  *
32
41
  * Returns the number of timers fired.
33
42
  */
34
43
  pumpDue(now?: number): number;
44
+ /**
45
+ * Whether a temporary timer with this id is still present.
46
+ *
47
+ * Backs `exists(id, "timer")`. Temp timers aren't in the store and — unlike
48
+ * temp triggers and aliases — aren't tracked in LuaRuntime's tempIds either,
49
+ * so this map is the only thing that knows.
50
+ *
51
+ * There is no enable/disable for a temporary timer, so this used to answer
52
+ * `isActive` too. A killed one splits the two apart: it is still present
53
+ * here until the reap, but no longer running — see {@link tempIsActive}.
54
+ */
55
+ hasTemp(id: number): boolean;
56
+ /** Whether a temporary timer is present *and* still running — `isActive(id,
57
+ * "timer")`. False for one killed since the last reap. */
58
+ tempIsActive(id: number): boolean;
35
59
  killTimer(id: number): boolean;
60
+ /** Free every timer killed since the last call. Runs once per processed line
61
+ * batch, mirroring the deferred cleanup Mudlet's TTimerUnit does. */
62
+ reapKilled(): void;
36
63
  /**
37
64
  * Cached previous load. `nodes` is the TimerNode keyed by id, `desc` is the
38
65
  * shape that determines whether the running setTimeout/setInterval is still
@@ -50,10 +77,14 @@ export declare class TimerEngine {
50
77
  * Mudlet `remainingTime(idOrName)` — seconds until the next fire. For
51
78
  * non-repeating timers, returns the time left before the one and only
52
79
  * fire. For repeating timers, returns the time until the next tick.
53
- * Returns -1 if no live timer matches (Mudlet's miss sentinel).
80
+ * Mirrors Mudlet's two miss sentinels, which the Lua wrapper turns into
81
+ * different messages: **-1** the timer exists but isn't running (a perm
82
+ * timer starts inactive), **-2** nothing of that id or name exists at all.
54
83
  * - Numeric arg: looks up tempTimer ids only.
55
- * - String arg: looks up permanent timer names; falls back to the stored
56
- * TimerNode id when the string is a uuid that no name matched.
84
+ * - String arg: permanent timer names (or a raw uuid), then temp ids —
85
+ * a temporary timer's "name" is the number tempTimer handed back, so
86
+ * remainingTime(tostring(id)) has to resolve the same timer as
87
+ * remainingTime(id).
57
88
  */
58
89
  remainingTime(idOrName: number | string): number;
59
90
  private stopPerm;
@@ -35,9 +35,24 @@ export type TriggerMatch = {
35
35
  namedSpans?: Record<string, CaptureSpan>;
36
36
  matchStart?: number;
37
37
  };
38
+ /** The lineage a trigger created mid-pass belongs to, and how many generations
39
+ * deep it sits in it. Absent on a trigger that was not created while a line was
40
+ * being processed. See TriggerEngine.addedWhileProcessing. */
41
+ interface SameLineChain {
42
+ chainId: number;
43
+ generation: number;
44
+ }
45
+ /** What a caller can tell the engine about a temp trigger beyond its pattern. */
46
+ export interface TempOptions {
47
+ /** As the runaway report names it. Mudlet uses the trigger's id when the
48
+ * call carried no name of its own. */
49
+ name?: string;
50
+ onStopped?: () => void;
51
+ }
38
52
  export declare class TriggerEngine {
39
53
  private readonly temp;
40
- private nextId;
54
+ /** Key for this engine's own temp map — see PatternEngine.nextInternalId. */
55
+ private nextInternalId;
41
56
  private inProcessTemp;
42
57
  private permCompiled;
43
58
  private allById;
@@ -45,12 +60,32 @@ export declare class TriggerEngine {
45
60
  private permReg;
46
61
  private unified;
47
62
  private orderDirty;
63
+ private processingDepth;
64
+ private addedWhileProcessing;
65
+ /** Lineage of each permanent ROOT trigger enrolled for the current line. */
66
+ private permSameLine;
67
+ /** Lineage id → name of the trigger whose script started it, for the report. */
68
+ private sameLineChainStarters;
69
+ private lastSameLineChainId;
70
+ private currentSameLineChainId;
71
+ private currentSameLineGeneration;
72
+ /** The name of the trigger whose script is running, for the abort report. */
73
+ private currentExecutingTriggerName;
74
+ /** When the runaway report was last posted, so a runaway whose creator
75
+ * outlives its line cannot bury the game text. */
76
+ private lastRunawayReportAt;
48
77
  private cache;
49
78
  private lineCounter;
50
79
  private readonly chainOpenUntil;
51
80
  private hasChildren;
52
81
  private andStates;
82
+ /** Triggers still firing on their own after completing, and the match they
83
+ * re-report while they do. See the fire-length branch in matchPermEntryOnce. */
84
+ private keepFiring;
53
85
  private filterActiveText;
86
+ /** Every offering a filter makes to its children this line: one per
87
+ * capture group, or the whole match when it has none. See openChain. */
88
+ private filterCaptures;
54
89
  private filterActiveOffset;
55
90
  /** Resolves once PCRE wasm is initialized and patterns can be compiled. */
56
91
  static ready(): Promise<void>;
@@ -78,7 +113,7 @@ export declare class TriggerEngine {
78
113
  * Invalid regex patterns return a no-op disposer so callers don't need
79
114
  * to special-case compile failures.
80
115
  */
81
- addTemp(pattern: string, fn: TempFn, kind?: 'regex' | 'substring' | 'startOfLine' | 'exactMatch' | 'prompt'): () => void;
116
+ addTemp(pattern: string, fn: TempFn, kind?: 'regex' | 'substring' | 'startOfLine' | 'exactMatch' | 'prompt', opts?: TempOptions): () => void;
82
117
  /**
83
118
  * Mudlet `tempLineTrigger(from, howMany, fn)`. A position-based trigger with
84
119
  * no pattern: it fires `fn([lineText])` on `howMany` consecutive lines,
@@ -95,9 +130,15 @@ export declare class TriggerEngine {
95
130
  * negative result is cached so we don't retry on every loadPerm. */
96
131
  private compileItem;
97
132
  processTemp(line: string, isPrompt?: boolean): void;
133
+ /** Leave a processing pass, and once the outermost one is over, forget the
134
+ * lineages it tracked: a trigger that outlives the line it was created on
135
+ * is no longer part of one, so what IT creates later starts counting
136
+ * afresh. */
137
+ private endPass;
98
138
  /** Match + fire a single temp trigger against `line`. Self-expiring `line`
99
139
  * triggers delete themselves (and dirty the unified order) when spent. */
100
140
  private fireTempEntry;
141
+ private fireTempEntryInner;
101
142
  matchPerm(line: string, isPrompt?: boolean): TriggerMatch[];
102
143
  /**
103
144
  * Match one permanent compiled entry against `line`, appending any matches
@@ -106,6 +147,9 @@ export declare class TriggerEngine {
106
147
  * shared across all entries processed for that line.
107
148
  */
108
149
  private matchPermEntry;
150
+ /** The nearest filter ancestor of `item`, or null when it has none. */
151
+ private innermostFilterId;
152
+ private matchPermEntryOnce;
109
153
  /**
110
154
  * Mudlet-faithful single pass: walk the one ordered list of permanent and
111
155
  * temporary triggers (see the ordering notes on the fields) and, for each
@@ -122,6 +166,53 @@ export declare class TriggerEngine {
122
166
  * re-entrancy a handler can cause via `feedTriggers`.
123
167
  */
124
168
  process(line: string, isPrompt: boolean, exec: (match: TriggerMatch) => void): void;
169
+ /**
170
+ * Enrol a trigger armed while a line was being processed, so the pass can
171
+ * offer it that line, and put it in a lineage: the one belonging to the
172
+ * trigger whose script armed it, a generation on, or a new one when the
173
+ * script predates the line.
174
+ */
175
+ private registerSameLineCreation;
176
+ /**
177
+ * The same, for a permanent trigger a script created mid-pass. Only ROOT
178
+ * triggers carry a lineage: one sitting in a folder creates on the folder's
179
+ * behalf, and reading the child's own (always empty) lineage instead would
180
+ * start a fresh one every round — which never deepens and so never trips.
181
+ */
182
+ notePermCreated(node: TriggerNode, root: TriggerNode): void;
183
+ /** Join the lineage of the trigger whose script is running, a generation on;
184
+ * or start one when that script predates the line. */
185
+ private nextSameLineChain;
186
+ /** The lineage of something enrolled for this line, whichever kind it is. */
187
+ private sameLineOf;
188
+ /** The root ancestor's id — the node a lineage is recorded against. */
189
+ private rootIdOf;
190
+ /** Run a permanent trigger's script inside its lineage, so what it creates
191
+ * is enrolled behind it rather than starting a lineage of its own. */
192
+ private execPerm;
193
+ /**
194
+ * Run `fn` as the script of the trigger named `name`, so anything it arms
195
+ * joins that trigger's lineage rather than starting one of its own.
196
+ */
197
+ runAsTrigger<T>(name: string, sameLine: SameLineChain | undefined, fn: () => T): T;
198
+ /**
199
+ * End a lineage that keeps arming triggers which match the line being
200
+ * processed. Stopping the pass alone would not do: what it created is still
201
+ * live, so the next line would start with a budget's worth of them and each
202
+ * would spawn a budget's worth again. Only this lineage is disowned — a
203
+ * capture trigger an unrelated script armed on the same line belongs to a
204
+ * lineage of its own and is left alone.
205
+ */
206
+ private stopSameLineCreationLoop;
207
+ /** Tell the player, at most once every REPORT interval — a runaway whose
208
+ * creator outlives the line trips on every matching line, and an unthrottled
209
+ * report would bury the game text it is trying to explain. */
210
+ private reportRunaway;
211
+ /** Where the runaway report is printed. ScriptingEngine wires this to the
212
+ * main console; unset (tests, teardown) the report is dropped. */
213
+ setRunawayReporter(fn: ((text: string) => void) | null): void;
214
+ /** How the engine switches off a permanent trigger it stopped. */
215
+ setPermDisabler(fn: ((nodeId: string) => void) | null): void;
125
216
  /** Rebuild the merged, path-sorted processing list from the current
126
217
  * permanent entries and temporary triggers. Called lazily from process()
127
218
  * when either source changed. */
@@ -137,9 +228,27 @@ export declare class TriggerEngine {
137
228
  * `ScriptingAPI.currentLineMatchesColor`. Passing `null` disables every
138
229
  * colour trigger (e.g. during runtime teardown).
139
230
  */
140
- setColorMatcher(fn: ((fg: number, bg: number) => boolean) | null): void;
231
+ setColorMatcher(fn: ((fg: number, bg: number, window: {
232
+ start: number;
233
+ length: number;
234
+ } | null) => boolean) | null): void;
141
235
  destroy(): void;
236
+ /**
237
+ * Advance every state an AND trigger has in flight against this line, and
238
+ * report the ones that completed on it.
239
+ *
240
+ * States are a LIST, not one: a trigger whose first condition matches twice
241
+ * before its second ever does has two matches part-way through, and both
242
+ * complete when that second condition finally arrives. Keeping a single
243
+ * state let the newer one overwrite the older, so only one ever fired.
244
+ */
142
245
  private processAndTrigger;
246
+ /** Carry one in-flight state as far through the remaining conditions as this
247
+ * line takes it. Several can be satisfied by the same line. */
248
+ private advanceAndState;
249
+ /** The match a completed AND state reports: every line's captures, in order,
250
+ * as `multimatches`. */
251
+ private andMatch;
143
252
  /**
144
253
  * Record a chain-head match: open the chain for `fireLength` more lines and,
145
254
  * if the trigger is also a filter, stash the captured/matched text so
@@ -1,4 +1,14 @@
1
+ import type { TriggerPattern } from '../storage/schema';
1
2
  import type { InstallOutcome } from './ScriptingAPI';
3
+ /**
4
+ * The item families the tree-walking APIs accept, lowercased — Mudlet's
5
+ * "'alias', 'button', 'script', 'keybind', 'timer' or 'trigger'", plus the
6
+ * 'key' spelling the engine already treats as 'keybind'. Lives here rather than
7
+ * on the engine so the no-op host below answers the same list: a runtime with no
8
+ * engine wired has no items, but it still knows what an item type IS, and
9
+ * reporting a valid type as invalid would send a caller looking for a typo.
10
+ */
11
+ export declare const KNOWN_ITEM_TYPES: ReadonlySet<string>;
2
12
  /**
3
13
  * Everything {@link ScriptingAPI} needs from {@link ScriptingEngine}.
4
14
  *
@@ -15,6 +25,22 @@ import type { InstallOutcome } from './ScriptingAPI';
15
25
  *
16
26
  * Members are grouped the way the engine groups them, not alphabetically.
17
27
  */
28
+ /** What tempComplexRegexTrigger asks for: a session-scoped trigger carrying
29
+ * everything a permanent one can. See ScriptingEngine.createTempComplexTrigger. */
30
+ export interface TempComplexTriggerSpec {
31
+ name: string;
32
+ patterns: TriggerPattern[];
33
+ code: string;
34
+ multiline: boolean;
35
+ isFilter: boolean;
36
+ multipleMatches: boolean;
37
+ fireLength: number;
38
+ delta: number;
39
+ highlight?: {
40
+ fg?: string;
41
+ bg?: string;
42
+ };
43
+ }
18
44
  export interface EngineHost {
19
45
  /** Run a chunk of Lua on behalf of a clicked hyperlink. Errors are reported
20
46
  * through the API's error channel rather than thrown. */
@@ -34,9 +60,12 @@ export interface EngineHost {
34
60
  requestReconnect(): boolean;
35
61
  /** Run a synthetic batch of lines through the same pipeline as network
36
62
  * output, so `feedTriggers` shares ordering semantics with real data. */
63
+ /** `fromServer` distinguishes a batch that came off the socket from one a
64
+ * script synthesised (feedTriggers). Absent means the socket. */
37
65
  processFlushBatch(groups: {
38
66
  text: string;
39
67
  type: string;
68
+ fromServer?: boolean;
40
69
  }[]): void;
41
70
  /** Apply any coalesced trigger/alias reloads immediately instead of on the
42
71
  * scheduled microtask. The Mudlet `perm…`, `enableTrigger` and
@@ -54,6 +83,8 @@ export interface EngineHost {
54
83
  installModuleFromPath(path: string): InstallOutcome;
55
84
  uninstallModuleByName(name: string): boolean;
56
85
  syncModuleToFile(name: string): Promise<void>;
86
+ /** Write every module flagged to sync back out to its own file. */
87
+ saveSyncedModules(): void;
57
88
  reloadModuleFromFile(name: string): boolean;
58
89
  setModuleSync(name: string, sync: boolean): void;
59
90
  getModuleSync(name: string): boolean;
@@ -75,12 +106,22 @@ export interface EngineHost {
75
106
  /** Read raw bytes from the profile VFS, or null if missing/unreadable.
76
107
  * Backs synchronous binary consumers such as setMovie's GIF decoder. */
77
108
  readFileBytes(path: string): Uint8Array | null;
109
+ /** Write raw bytes into the VFS, creating parent directories. False with no
110
+ * VFS or when the write throws. Paths are absolute, so this also reaches
111
+ * outside the profile — the shared window layout lives beside the profiles
112
+ * folder, not inside one. */
113
+ writeFileBytes(path: string, bytes: Uint8Array): boolean;
114
+ /** Absolute VFS path of the directory that holds every profile folder —
115
+ * Mudlet's configuration directory, where files shared between profiles
116
+ * (the window layout) live. Null with no VFS. */
117
+ configDirectory(): string | null;
78
118
  toggleScriptByName(name: string, enabled: boolean): boolean;
79
119
  toggleTriggerByName(name: string, enabled: boolean): boolean;
80
120
  toggleTimerByName(name: string, enabled: boolean): boolean;
81
121
  toggleAliasByName(name: string, enabled: boolean): boolean;
82
122
  toggleKeyByName(name: string, enabled: boolean): boolean;
83
- toggleToolBarByName(name: string, show: boolean): boolean;
123
+ /** Null when the toolbar moved, otherwise why it did not. */
124
+ toggleToolBarByName(name: string, show: boolean): string | null;
84
125
  setTriggerStayOpenByName(name: string, lines: number): boolean;
85
126
  /** The saved keybinding carrying this numeric id, or null — getKeyCode
86
127
  * resolves a permanent key by the id permKey returned, not only by name. */
@@ -104,12 +145,19 @@ export interface EngineHost {
104
145
  node: string;
105
146
  isActive: boolean;
106
147
  }> | null;
107
- findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[];
148
+ findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[] | null;
149
+ /** Whether `type` names an item family at all. */
150
+ isKnownItemType(type: string): boolean;
151
+ /** Next id from the profile's single item-id sequence. Temporary items draw
152
+ * from it too, so a temp and a permanent item can never collide. */
153
+ allocateItemId(): number;
108
154
  isAncestorsActiveById(id: number, type: string): boolean | null;
109
155
  getProfileStats(): Record<string, unknown>;
110
156
  createPermScript(name: string, parent: string, code: string): number;
111
157
  createPermRegexTrigger(name: string, parent: string, regexes: string[], code: string): number;
112
158
  createPermSubstringTrigger(name: string, parent: string, patterns: string[], code: string): number;
159
+ createTempComplexTrigger(spec: TempComplexTriggerSpec): number;
160
+ removeTemporaryTriggerById(id: number): boolean;
113
161
  createPermBeginOfLineStringTrigger(name: string, parent: string, patterns: string[], code: string): number;
114
162
  createPermExactMatchTrigger(name: string, parent: string, patterns: string[], code: string): number;
115
163
  createPermPromptTrigger(name: string, parent: string, code: string): number;
@@ -120,14 +168,19 @@ export interface EngineHost {
120
168
  createPermKey(name: string, parent: string, modifier: number, key: string | number, code: string): number;
121
169
  createTempButton(toolbar: string, name: string, code: string, orientation: number): number;
122
170
  createTempButtonToolbar(name: string, orientation: number, location: number): number;
171
+ /** True when the state actually changed; false when it already was that. */
123
172
  setButtonStateByName(name: string, state: boolean): boolean;
124
173
  getButtonStateByName(name: string): boolean | null;
174
+ /** Which of Mudlet's button refusals applies to `name`. */
175
+ buttonKindByName(name: string): 'missing' | 'plain' | 'pushdown';
125
176
  setButtonStyleSheetByName(name: string, css: string): boolean;
126
177
  setScriptByName(name: string, code: string, pos: number): number;
127
178
  getScriptByName(name: string, pos: number): {
128
179
  code: string;
129
- count: number;
180
+ id: number;
130
181
  } | null;
182
+ /** Remove the script with this numeric id; true when one was removed. */
183
+ removeScriptById(id: number): boolean;
131
184
  /** Mudlet's killTimer/killAlias/killTrigger/killKey by permanent-item name. */
132
185
  killByName(kind: 'timer' | 'alias' | 'trigger' | 'key', name: string): boolean;
133
186
  /** Mudlet `resetProfile()` — reload the profile with a fresh Lua VM. */
@@ -49,7 +49,15 @@ export interface IScriptingRuntime {
49
49
  setMsspValue(name: string, value: string): void;
50
50
  runWithMatches(code: string, name: string, matches: (string | undefined)[], multimatches?: (string | undefined)[][], namedGroups?: Record<string, string>, captureSpans?: CaptureSpan[], namedSpans?: Record<string, CaptureSpan>, fullMatchSpan?: CaptureSpan): void;
51
51
  destroy(): void;
52
+ /** Bytes behind a path in the runtime's read-only bundled namespace, or null
53
+ * when it has none / the path is not one of them. Optional because it is a
54
+ * property of how a runtime ships its own library, not of running scripts:
55
+ * the Lua runtime serves `/lua/...` this way. */
56
+ readBuiltinBytes?(path: string): Uint8Array | null;
52
57
  setCurrentLine(line: string, isPrompt: boolean): void;
58
+ /** The line {@link setCurrentLine} last recorded. The engine reads it back
59
+ * to restore the outer line after a nested feedTriggers. */
60
+ getCurrentLine(): string;
53
61
  /**
54
62
  * Mirror the last command-bar input into the Lua `command` global, matching
55
63
  * Mudlet's AliasUnit::processDataStream. Read by scripts/keys such as the
@@ -66,6 +74,12 @@ export interface IScriptingRuntime {
66
74
  * denyCurrentSend().
67
75
  */
68
76
  dispatchSendRequest(text: string): boolean;
77
+ /**
78
+ * Start a speedwalk between two rooms (Mudlet Host::startSpeedWalk): find
79
+ * the path and hand it to the mapper package's `doSpeedWalk`. Driven by the
80
+ * map's double-click-to-walk gesture via WindowManager.startSpeedWalk.
81
+ */
82
+ startSpeedWalk(from: number, to: number): void;
69
83
  /**
70
84
  * Kill every event handler registered by `wrapScript` for the given
71
85
  * script id. Called when a script is removed or disabled so its handlers
@@ -78,6 +92,9 @@ export interface IScriptingRuntime {
78
92
  * (unlike permanent items) don't live in the persisted store.
79
93
  */
80
94
  tempItemExists(id: number, type: string): boolean;
95
+ /** A live temp item of `type` carrying this script-supplied name (Mudlet's
96
+ * tempComplexRegexTrigger is the one temp API that takes one), or null. */
97
+ tempItemIdByName(name: string, type: string): number | null;
81
98
  /** Publish one use of a server-defined MXP element as `mxp.<element>`. */
82
99
  setMxpElement(name: string, attrs: Record<string, string>): void;
83
100
  /** Whether a live temp item is enabled — backs `isActive(id, type)`. */
@@ -85,6 +102,11 @@ export interface IScriptingRuntime {
85
102
  /** enable/disable a live temp item by id — backs enableTrigger/disableTrigger
86
103
  * (and the alias pair) when handed the numeric id rather than a name. */
87
104
  setTempItemEnabled(id: number, enabled: boolean): boolean;
105
+ /** Free every temp item killed since the last call, or only those of `type`.
106
+ * A killed item stops firing immediately but stays findable until here,
107
+ * which is Mudlet's deferred per-unit cleanup: aliases are reaped when
108
+ * nested alias processing unwinds, triggers when the line batch finishes. */
109
+ reapKilledTempItems(type?: 'alias' | 'trigger'): void;
88
110
  /** Rebuild saved Lua globals (a Mudlet `<VariablePackage>` tree) into `_G`. */
89
111
  restoreVariables(vars: MudletVariable[]): void;
90
112
  /** Snapshot the save-listed globals out of `_G` into a variable tree. */