@yagejs-addons/dialogue 0.1.0 → 0.2.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.
- package/dist/DialogueController-BMeNLi0v.d.cts +1204 -0
- package/dist/DialogueController-Cs5IUc-u.d.ts +1204 -0
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/chunk-7QVYU63E.js.map +1 -0
- package/dist/chunk-CU47RPEB.js +410 -0
- package/dist/chunk-CU47RPEB.js.map +1 -0
- package/dist/chunk-GJQKZCOL.js +983 -0
- package/dist/chunk-GJQKZCOL.js.map +1 -0
- package/dist/index.cjs +3441 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +591 -0
- package/dist/index.d.ts +591 -0
- package/dist/index.js +2048 -0
- package/dist/index.js.map +1 -0
- package/dist/presenters.cjs +3149 -0
- package/dist/presenters.cjs.map +1 -0
- package/dist/presenters.d.cts +1817 -0
- package/dist/presenters.d.ts +1817 -0
- package/dist/presenters.js +2920 -0
- package/dist/presenters.js.map +1 -0
- package/dist/types-DSbBSlh7.d.cts +375 -0
- package/dist/types-DSbBSlh7.d.ts +375 -0
- package/dist/yaml.cjs +726 -0
- package/dist/yaml.cjs.map +1 -0
- package/dist/yaml.d.cts +23 -0
- package/dist/yaml.d.ts +23 -0
- package/dist/yaml.js +37 -0
- package/dist/yaml.js.map +1 -0
- package/package.json +4 -4
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { P as ParsedText, D as DialogueScript, V as VarValue, a as VariableStorage, b as VarMap, C as Condition, E as Expr, c as DialogueFunction, S as SayStep, d as SpeakerDef, e as ChoiceStep, f as ChoiceOption, g as Command, h as CommandContext, R as RunMode, M as MarkerToken } from './types-DSbBSlh7.cjs';
|
|
2
|
+
export { A as ArithmeticOp, i as AvatarRef, B as BinaryOp, j as BuiltinEffectId, k as CommandHandler, l as CommandStep, m as CommandTiming, n as CompareOp, o as ComparisonOp, p as DialogueHandle, q as DialogueNode, r as DialoguePlayOptions, s as EndStep, G as GotoStep, L as LogicalOp, N as NodeId, t as PauseToken, u as RevealToken, v as RunStyle, w as SpeakerId, x as Step, T as TextRun, U as UnaryOp } from './types-DSbBSlh7.cjs';
|
|
3
|
+
import { D as DialogueExtraChannel } from './DialogueController-BMeNLi0v.cjs';
|
|
4
|
+
export { A as AvatarChannel, C as ChoiceChannel, a as ChoiceContext, b as ChromeChannel, c as CompositeInputBinding, d as DEFAULT_ACTIONS, e as DialogueActions, f as DialogueBundle, g as DialogueChannels, h as DialogueController, i as DialogueControllerOptions, j as DialogueSession, k as DialogueSessionOptions, F as FULL_ACTIONS, I as I18nAdapter, l as IdentityI18n, m as InputBinding, K as KeyboardInputBinding, L as LineReveal, M as Mountable, P as PointerChoiceTarget, n as PointerInputBinding, o as PresentedChoice, p as PresentedLine, q as PreviewedLine, R as RevealBeat, S as SpeakerView, T as TextChannel, r as TypedScript, V as VarsOf, s as defineScript, t as fullControls, u as interpolate } from './DialogueController-BMeNLi0v.cjs';
|
|
5
|
+
import * as _yagejs_core from '@yagejs/core';
|
|
6
|
+
import '@yagejs/input';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Inline-markup parser. Turns an authored string into styled {@link TextRun}s
|
|
10
|
+
* plus {@link RevealToken}s, using a small BBCode-ish tag syntax that survives
|
|
11
|
+
* translation (translators keep the tags, reorder the words):
|
|
12
|
+
*
|
|
13
|
+
* plain text
|
|
14
|
+
* [b]bold[/b] [i]italic[/i]
|
|
15
|
+
* [color=#ffcc00]hex[/color] [color=gold]named[/color]
|
|
16
|
+
* [wave]animated[/wave] (effect span — OPEN vocabulary: any [name]..[/name];
|
|
17
|
+
* the bundled view animates wave/shake/pulse/rainbow)
|
|
18
|
+
* [speed=2]faster[/speed] [speed=0.5]slower[/speed]
|
|
19
|
+
* [pause=400/] (self-closing reveal PAUSE — holds at its offset, in ms)
|
|
20
|
+
* [sfx=ding/] (self-closing reveal MARKER — fires at its offset)
|
|
21
|
+
* [expression=happy/] (self-named shortcut → props { expression: happy })
|
|
22
|
+
* [shake amount=3/] (marker with explicit key=value props)
|
|
23
|
+
* [shake=500 amount=3/] (shortcut + props compose → { shake: 500, amount: 3 })
|
|
24
|
+
* \[literal bracket]
|
|
25
|
+
*
|
|
26
|
+
* Tags nest; styles inherit down the stack (so [b][color=red]X[/color][/b]
|
|
27
|
+
* is bold+red). A trailing `/` makes a tag **self-closing** — a zero-width
|
|
28
|
+
* {@link RevealToken} (a `[pause=600/]` hold or a `[name k=v/]` marker) that the
|
|
29
|
+
* reveal drains at its char offset, distinct from the styling tags (which never
|
|
30
|
+
* end in `/`). Pause + markers share one ordered stream, so **source order is
|
|
31
|
+
* drain order**: `[pause=600/][shake/]` holds then fires; `[shake/][pause=600/]`
|
|
32
|
+
* fires then holds. A non-self-closing tag that isn't a built-in text attribute
|
|
33
|
+
* opens an EFFECT span named after the tag (an open vocabulary the presenter
|
|
34
|
+
* interprets); translators MUST keep a marker/pause's self-closing `/` so the
|
|
35
|
+
* token survives a re-order.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** The empty parse result (no runs / tokens, length 0). Shared so a presenter or
|
|
39
|
+
* the session can present a contentless line (an empty choice prompt) without
|
|
40
|
+
* re-constructing the shape — and without forgetting the required `tokens`
|
|
41
|
+
* field. */
|
|
42
|
+
declare const EMPTY_PARSED: ParsedText;
|
|
43
|
+
/**
|
|
44
|
+
* Split a string into graphemes (user-perceived characters) — the unit the
|
|
45
|
+
* renderer creates one glyph node per, and the unit every reveal-side count
|
|
46
|
+
* (`ParsedText.length`, `TextRun.graphemeCount`, `PauseToken.atChar`) uses.
|
|
47
|
+
*/
|
|
48
|
+
declare function splitGraphemes(text: string): string[];
|
|
49
|
+
declare function parseMarkup(input: string): ParsedText;
|
|
50
|
+
/** Strip every tag, returning plain text (useful for measuring / a11y / logs). */
|
|
51
|
+
declare function stripMarkup(input: string): string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Two-stage validation for the storage model.
|
|
55
|
+
*
|
|
56
|
+
* • **Load-time** ({@link analyzeScript}, environment-free): walk the script
|
|
57
|
+
* once, collecting the names it **reads** (conditions, `{token}`s, `set`
|
|
58
|
+
* values), the names it **writes** (`set` targets), the **functions** it
|
|
59
|
+
* calls, and the **command types** it fires. Type-check what's statically
|
|
60
|
+
* knowable — an atomic numeric comparison against a declared non-number, a
|
|
61
|
+
* literal `set` value whose type conflicts with the target's declared
|
|
62
|
+
* default. Undeclared *references* are NOT rejected here: the installed
|
|
63
|
+
* storage / functions may provide them, which is only known at play-time.
|
|
64
|
+
* • **Play-time** ({@link validatePlay}): given the installed storage,
|
|
65
|
+
* functions, and commands, throw on a *significant* mismatch — a read name
|
|
66
|
+
* nothing provides, a called function with no implementation, a `set` target
|
|
67
|
+
* that's a function (read-only), a command type with no handler/fallback, a
|
|
68
|
+
* declared default whose type conflicts with the value the storage already
|
|
69
|
+
* holds.
|
|
70
|
+
*
|
|
71
|
+
* Both throw hard — a dangling reference or an environment that can't satisfy the
|
|
72
|
+
* script is a programming error, not a recoverable runtime condition.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/** A script reference is broken (load-time). */
|
|
76
|
+
declare class DialogueScriptError extends Error {
|
|
77
|
+
}
|
|
78
|
+
/** The installed storage/functions/commands don't satisfy the script (play-time). */
|
|
79
|
+
declare class DialoguePlayError extends Error {
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Canonical loader: validates + normalises a hand-authored / JSON
|
|
84
|
+
* {@link DialogueScript} into a frozen, structurally-checked script the runner
|
|
85
|
+
* can trust. Other "common formats" (a Yarn/ink-style screenplay parser) are
|
|
86
|
+
* additional modules in this folder that emit the same canonical shape — the
|
|
87
|
+
* runner only ever sees the canonical model.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
declare function loadScript(raw: DialogueScript): DialogueScript;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compact authoring front-end — a small, line-oriented DSL for RPG-style
|
|
94
|
+
* dialogue that compiles to the same {@link DialogueScript} IR as JSON / YAML.
|
|
95
|
+
* `parseCompact(text)` produces the IR; `loadCompact(text)` runs it through
|
|
96
|
+
* {@link loadScript}, so validation and the frozen model are identical to every
|
|
97
|
+
* other loader. Pixi-free: it imports only the headless core (`parseExpr`,
|
|
98
|
+
* `loadScript`, the markup tag guard).
|
|
99
|
+
*
|
|
100
|
+
* One statement per line. Leading whitespace is insignificant (indent nodes for
|
|
101
|
+
* readability). Blank lines and `// comment` lines are ignored. Each non-blank
|
|
102
|
+
* line is one of:
|
|
103
|
+
*
|
|
104
|
+
* # id script id (required, once) — the start node is the
|
|
105
|
+
* first `::` node defined.
|
|
106
|
+
* @ id Name [#hex] a speaker: an opaque id, a display name (may have
|
|
107
|
+
* spaces), an optional nameplate colour (`#ffcc00` /
|
|
108
|
+
* `#fc0`). `@` lines may appear before or after their use.
|
|
109
|
+
* :: nodeId opens a node; following step lines belong to it.
|
|
110
|
+
* speaker[ face]: text a spoken line — ONLY when the first token is a declared
|
|
111
|
+
* `@`-speaker. `face` (a 2nd header token) becomes the
|
|
112
|
+
* line's avatar `expression`. Otherwise the WHOLE line,
|
|
113
|
+
* colons and all, is a narrator line.
|
|
114
|
+
* text a narrator line (no declared speaker prefix).
|
|
115
|
+
* ? text … a choice option; consecutive `?` lines coalesce into
|
|
116
|
+
* one choice step (see below).
|
|
117
|
+
* -> nodeId [if: cond] a jump — unconditional, or conditional (taken only if
|
|
118
|
+
* `cond` holds, else fall through to the next step).
|
|
119
|
+
* declare v = value a script-level variable default (a literal value).
|
|
120
|
+
* set v = rhs write a variable. A bare number / `true` / `false` /
|
|
121
|
+
* `null` stays a literal; anything else is parsed as an
|
|
122
|
+
* expression (`set hp = hp - 1`), so the host reads it
|
|
123
|
+
* back through the same evaluator JSON uses.
|
|
124
|
+
* do type k=v … #flag a host command: `type` then `key=value` data and
|
|
125
|
+
* `#flag` booleans (`do give-item id=key count=1 #blocking`).
|
|
126
|
+
* end ends the conversation.
|
|
127
|
+
*
|
|
128
|
+
* **Per-line hints** ride the end of a `say` line: `view=` / `voice=` / `speed=`
|
|
129
|
+
* / `auto=` set the first-class {@link SayStep} fields, and trailing `#key:value`
|
|
130
|
+
* / bare `#flag` hashtags become {@link SayStep.meta} (Yarn-aligned — metadata is
|
|
131
|
+
* trailing). A `say` line's text is otherwise passed to the markup parser
|
|
132
|
+
* **verbatim**, so inline `[..]` markup (and any markup tokens a later release
|
|
133
|
+
* adds) survives untouched.
|
|
134
|
+
*
|
|
135
|
+
* **Choices** carry their attributes as non-bracket sigils, in this order after
|
|
136
|
+
* the text: `if: cond`, then `-> target` (or `target=node`), then `#once` /
|
|
137
|
+
* `#disabled` / `#key:value` hashtags. They are lexed off and stripped before
|
|
138
|
+
* the remaining choice text reaches markup — `[..]` is reserved for inline
|
|
139
|
+
* markup there, so a bracketed token that markup doesn't recognize is reported
|
|
140
|
+
* as an error (it is almost always a mistyped attribute that would otherwise be
|
|
141
|
+
* dropped silently).
|
|
142
|
+
*
|
|
143
|
+
* Conditions and non-literal `set` values are parsed with the shared
|
|
144
|
+
* {@link parseExpr}, so a malformed expression throws {@link DialogueExprError}
|
|
145
|
+
* (a {@link DialogueScriptError} subtype) with its position.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse compact-DSL source into a (mutable) {@link DialogueScript}. Throws
|
|
150
|
+
* {@link DialogueScriptError} on a structural problem (with the 1-based line) and
|
|
151
|
+
* {@link DialogueExprError} on a malformed condition / `set` expression.
|
|
152
|
+
*/
|
|
153
|
+
declare function parseCompact(text: string): DialogueScript;
|
|
154
|
+
/** Parse compact-DSL source and run it through {@link loadScript} — same
|
|
155
|
+
* validated, frozen IR as the JSON and YAML loaders. */
|
|
156
|
+
declare function loadCompact(text: string): DialogueScript;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* {@link VariableStorage} implementations — the read/write bridge between a
|
|
160
|
+
* conversation and game state. One **opaque** name namespace; scoping is
|
|
161
|
+
* the host's policy. Three building blocks:
|
|
162
|
+
*
|
|
163
|
+
* • {@link MemoryVariableStorage} — the zero-config default. A plain Map; holds
|
|
164
|
+
* dialogue-locals and seeded defaults, persists across plays.
|
|
165
|
+
* • {@link cells} — first-class **two-way binding**: `{ gold: { get, set } }`
|
|
166
|
+
* drives a value the *script* owns the arithmetic of (a read-only getter
|
|
167
|
+
* throws on `set`). A bare `() => value` is the read-only shorthand.
|
|
168
|
+
* • {@link compose} — layer several storages into one (reads/writes route to
|
|
169
|
+
* the first that `has` the name; a brand-new name lands in the last —
|
|
170
|
+
* so put a writable store last to catch seeds + locals).
|
|
171
|
+
*
|
|
172
|
+
* The interface lives in `types.ts`; this file is the concrete kit. Seed-if-
|
|
173
|
+
* absent + persistence are policy of the *caller* (`session.play`), not the
|
|
174
|
+
* storage — these just hold values.
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
/** Materialize a storage's enumerable variables into a plain map — backs
|
|
178
|
+
* `{token}` interpolation params and `handle.getVars()`. */
|
|
179
|
+
declare function materialize(storage: VariableStorage): VarMap;
|
|
180
|
+
/** The zero-config default storage: a Map-backed, fully-enumerable store. */
|
|
181
|
+
declare class MemoryVariableStorage implements VariableStorage {
|
|
182
|
+
private readonly map;
|
|
183
|
+
constructor(initial?: Readonly<VarMap>);
|
|
184
|
+
get(name: string): VarValue | undefined;
|
|
185
|
+
set(name: string, value: VarValue): void;
|
|
186
|
+
has(name: string): boolean;
|
|
187
|
+
entries(): Iterable<readonly [string, VarValue]>;
|
|
188
|
+
/** Drop everything — host-controlled reset (variables persist across plays by default). */
|
|
189
|
+
clear(): void;
|
|
190
|
+
}
|
|
191
|
+
/** A two-way (or read-only) binding for one game-owned value. A bare function is
|
|
192
|
+
* the read-only shorthand for `{ get }`. */
|
|
193
|
+
type Cell = {
|
|
194
|
+
get(): VarValue;
|
|
195
|
+
set?(value: VarValue): void;
|
|
196
|
+
} | (() => VarValue);
|
|
197
|
+
/**
|
|
198
|
+
* A {@link VariableStorage} over named accessors into game state. `has` is true
|
|
199
|
+
* for exactly the declared names; `get` invokes the getter live; `set` writes
|
|
200
|
+
* through the setter, or throws if the cell is read-only (a getter with no
|
|
201
|
+
* setter). This is the seam for a value whose arithmetic the *script* owns
|
|
202
|
+
* (`set gold = gold - 50`).
|
|
203
|
+
*/
|
|
204
|
+
declare function cells(defs: Readonly<Record<string, Cell>>): VariableStorage;
|
|
205
|
+
/**
|
|
206
|
+
* Layer storages into one. `get`/`has` consult them in order (first that `has`
|
|
207
|
+
* the name wins); `set` writes through the first that `has` it, else the **last**
|
|
208
|
+
* storage — so a brand-new name (a dialogue-local or a seeded default) lands in
|
|
209
|
+
* whatever writable store you put last. Typical: `compose(cells(...game), new
|
|
210
|
+
* MemoryVariableStorage())`.
|
|
211
|
+
*/
|
|
212
|
+
declare function compose(...storages: readonly VariableStorage[]): VariableStorage;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Expression evaluator. `Condition`s and `set` values are expression
|
|
216
|
+
* *trees* — `literal | varRef | call | unary | binary | group` — evaluated
|
|
217
|
+
* against an {@link EvalScope} (variable reads + installed functions). The
|
|
218
|
+
* operator set mirrors Yarn Spinner so a future Yarn parser maps onto this IR
|
|
219
|
+
* 1:1; the atomic `{ var, op, value }` comparison evaluates as the degenerate
|
|
220
|
+
* one-level tree (lowered into a `binary` node in {@link evalCondition}).
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
/** What an expression evaluates against: per-name reads + function calls, plus
|
|
224
|
+
* a materialized snapshot for the `(vars) => boolean` predicate escape hatch. */
|
|
225
|
+
interface EvalScope {
|
|
226
|
+
/** Read a variable (absent → `null`). */
|
|
227
|
+
get(name: string): VarValue;
|
|
228
|
+
/** Invoke an installed function with already-evaluated args. */
|
|
229
|
+
call(fn: string, args: readonly VarValue[]): VarValue;
|
|
230
|
+
/** Materialize the readable variables (for a predicate condition). */
|
|
231
|
+
vars(): VarMap;
|
|
232
|
+
}
|
|
233
|
+
/** A condition holds when its value is truthy. */
|
|
234
|
+
declare function evalCondition(condition: Condition, scope: EvalScope): boolean;
|
|
235
|
+
/** Evaluate an expression tree to a single value. */
|
|
236
|
+
declare function evaluate(expr: Expr, scope: EvalScope): VarValue;
|
|
237
|
+
/** True for an {@link Expr} node (discriminated by `kind`), so `Condition` can
|
|
238
|
+
* tell a tree apart from the atomic `{ var, op, value }` shape. */
|
|
239
|
+
declare function isExpr(value: unknown): value is Expr;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* String → expression front-end. `parseExpr("str >= 8 and has_item('key')")`
|
|
243
|
+
* produces the same {@link Expr} tree a hand-authored JSON condition / `set`
|
|
244
|
+
* value would — `literal | varRef | call | unary | binary | group`, no new node
|
|
245
|
+
* kinds — so the evaluator (`expr.ts`) and the load-time walk (`validate.ts`)
|
|
246
|
+
* are reused unchanged. This is purely a parser: it does no type-checking and
|
|
247
|
+
* no name resolution (that stays in `validate.ts`), which keeps it reusable 1:1
|
|
248
|
+
* for a future Yarn front-end.
|
|
249
|
+
*
|
|
250
|
+
* The operator set mirrors Yarn Spinner. v1 wires what the authoring examples
|
|
251
|
+
* exercise: `or`/`||`, `and`/`&&`, `not`/`!`, the comparisons (`== != > < >= <=`
|
|
252
|
+
* plus the word forms `eq neq gt lt gte lte is`), unary `-`, binary `+ -`, calls
|
|
253
|
+
* `f(a, b)`, and parentheses. `xor`/`^` and `* / %` are reserved but not yet
|
|
254
|
+
* wired (the IR + evaluator already accept them, so adding them later is purely
|
|
255
|
+
* additive). Word-form operators normalise to their symbol equivalents in the IR
|
|
256
|
+
* (`and` → `&&`, `eq` → `==`, `gt` → `>`, …), so `a and b` and `a && b` parse to
|
|
257
|
+
* the identical tree.
|
|
258
|
+
*
|
|
259
|
+
* An identifier is `[A-Za-z_$]` followed by `[A-Za-z0-9_.$]` repeats — `.` and
|
|
260
|
+
* `$` are included (so `$gold` and `quest.stage` each read as ONE name,
|
|
261
|
+
* Yarn-forward) but `-` is excluded, so `hp-1` is `hp` minus `1` and an item id
|
|
262
|
+
* like `'rusty-key'` must live in a quoted string literal.
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* A string expression failed to parse. Carries the 1-based source position.
|
|
267
|
+
* Extends {@link DialogueScriptError} so the loaders' contract holds: a malformed
|
|
268
|
+
* string condition / `set` value surfaced by `loadScript` / `loadYaml` is caught
|
|
269
|
+
* by a single `catch (e instanceof DialogueScriptError)`, while `instanceof
|
|
270
|
+
* DialogueExprError` (and `line` / `col`) still distinguish a parse error.
|
|
271
|
+
*/
|
|
272
|
+
declare class DialogueExprError extends DialogueScriptError {
|
|
273
|
+
readonly line: number;
|
|
274
|
+
readonly col: number;
|
|
275
|
+
constructor(message: string, line: number, col: number);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Parse a string into an {@link Expr} tree. Throws {@link DialogueExprError}
|
|
279
|
+
* (with line/col) on an empty/blank source, a leftover trailing token, or a
|
|
280
|
+
* dangling operator.
|
|
281
|
+
*/
|
|
282
|
+
declare function parseExpr(src: string): Expr;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* DialogueRunner — the engine-agnostic state machine. It walks a normalised
|
|
286
|
+
* {@link DialogueScript}, pausing on `say`/`choice` steps (which need player
|
|
287
|
+
* input) and running `command`/`goto`/`end` steps straight through. All
|
|
288
|
+
* presentation is delegated via callbacks, so the same runner drives a
|
|
289
|
+
* renderer-based box, a ui-react box, or a headless test.
|
|
290
|
+
*
|
|
291
|
+
* Branching reads one {@link VariableStorage} namespace through an
|
|
292
|
+
* {@link EvalScope} (per-name reads + installed functions): `set` / `ctx.setVar`
|
|
293
|
+
* write storage, conditions and `set` values are evaluated as expression trees.
|
|
294
|
+
* The runner resolves built-in commands (`set`) itself and surfaces every other
|
|
295
|
+
* command to the host through `onCommand` — that's the seam where the game turns
|
|
296
|
+
* `{ type: "give-item", id: "key" }` into an actual effect.
|
|
297
|
+
*/
|
|
298
|
+
|
|
299
|
+
/** The runtime environment the session installs behind a running conversation:
|
|
300
|
+
* the variable storage (read + guarded write) + the callable functions. */
|
|
301
|
+
interface RunnerEnv {
|
|
302
|
+
readonly storage: VariableStorage;
|
|
303
|
+
readonly functions: Readonly<Record<string, DialogueFunction>>;
|
|
304
|
+
/**
|
|
305
|
+
* Surfaces a non-fatal runtime diagnostic — currently a `set` whose write the
|
|
306
|
+
* storage rejected (a getter-only `cells` accessor). The runner ignores the
|
|
307
|
+
* write and keeps the conversation running; the host (via the session →
|
|
308
|
+
* controller) routes the message to the engine logger. Engine-agnostic: the
|
|
309
|
+
* core never reaches for `console`/a logger directly.
|
|
310
|
+
*/
|
|
311
|
+
readonly onError?: ((message: string, error: unknown) => void) | undefined;
|
|
312
|
+
}
|
|
313
|
+
interface ResolvedChoice {
|
|
314
|
+
readonly index: number;
|
|
315
|
+
readonly option: ChoiceOption;
|
|
316
|
+
/**
|
|
317
|
+
* A visible-but-disabled row: the option's condition currently fails AND its
|
|
318
|
+
* `presentation` is `"disabled"`, so it's shown greyed-out and non-selectable
|
|
319
|
+
* instead of filtered. Omitted (falsy) for a normal, selectable option.
|
|
320
|
+
* Default-`"hidden"` condition failures and spent `once` options aren't
|
|
321
|
+
* returned at all.
|
|
322
|
+
*/
|
|
323
|
+
readonly disabled?: boolean;
|
|
324
|
+
}
|
|
325
|
+
interface RunnerHandlers {
|
|
326
|
+
/** A line is ready to display. Runner waits for `advance()`. */
|
|
327
|
+
onSay(step: SayStep, speaker: SpeakerDef | undefined): void;
|
|
328
|
+
/** Choices are ready. Runner waits for `choose(index)`. `prompt` pre-resolved by host. */
|
|
329
|
+
onChoice(step: ChoiceStep, choices: readonly ResolvedChoice[], speaker: SpeakerDef | undefined): void;
|
|
330
|
+
/**
|
|
331
|
+
* A non-built-in command fired (give-item, play-sfx, …). May return a promise;
|
|
332
|
+
* if the command is `blocking`, the runner waits for it.
|
|
333
|
+
*/
|
|
334
|
+
onCommand(command: Command, ctx: CommandContext): void | Promise<void>;
|
|
335
|
+
/** Conversation finished (ran off the end or hit an `end` step). */
|
|
336
|
+
onEnd(): void;
|
|
337
|
+
}
|
|
338
|
+
declare class DialogueRunner {
|
|
339
|
+
private readonly script;
|
|
340
|
+
private readonly handlers;
|
|
341
|
+
/** `option.once` keys already picked — per-conversation **cursor** state, NOT
|
|
342
|
+
* the variable storage. Fresh per runner, so a new `play()` starts it empty
|
|
343
|
+
* (a re-played conversation re-shows its `once` options; {@link getChosenOnce}
|
|
344
|
+
* exposes the set so a save cursor could capture/restore it). */
|
|
345
|
+
private readonly chosenOnce;
|
|
346
|
+
private nodeId;
|
|
347
|
+
private stepIndex;
|
|
348
|
+
private state;
|
|
349
|
+
/** "play" normally; `skip()` flips it to "skip" to fast-forward the section. */
|
|
350
|
+
private runMode;
|
|
351
|
+
/** Storage (write through this so a read-only `cells` accessor throws) +
|
|
352
|
+
* functions, wrapped once as the condition/`set`-value eval scope. */
|
|
353
|
+
private readonly storage;
|
|
354
|
+
private readonly scope;
|
|
355
|
+
private readonly onError;
|
|
356
|
+
constructor(script: DialogueScript,
|
|
357
|
+
/** The variable storage + functions (built by the session per play()). */
|
|
358
|
+
env: RunnerEnv, handlers: RunnerHandlers);
|
|
359
|
+
/** Snapshot of the storage's variables — the `handle.getVars()` /
|
|
360
|
+
* future save-cursor view. */
|
|
361
|
+
getVars(): Readonly<VarMap>;
|
|
362
|
+
/** Current node id (durable cursor; save seam). */
|
|
363
|
+
getNodeId(): string;
|
|
364
|
+
/** Current step index within the node (durable cursor; save seam). */
|
|
365
|
+
getStepIndex(): number;
|
|
366
|
+
/** One-shot choice keys already picked (`option.once`); save seam. */
|
|
367
|
+
getChosenOnce(): ReadonlySet<string>;
|
|
368
|
+
isEnded(): boolean;
|
|
369
|
+
/** Begin at the start node. Idempotent guard against double-start. The cursor
|
|
370
|
+
* (`nodeId`/`stepIndex`) is already at the start from the ctor + field init. */
|
|
371
|
+
start(): void;
|
|
372
|
+
/** Advance past the current `say` line. No-op unless we're awaiting it. */
|
|
373
|
+
advance(): void;
|
|
374
|
+
/**
|
|
375
|
+
* Fast-forward from the current line: run intervening commands in `skip` mode
|
|
376
|
+
* (so the game can reconstruct world state idempotently) without presenting
|
|
377
|
+
* any lines, stopping at the next choice or the end. No-op unless on a line.
|
|
378
|
+
*/
|
|
379
|
+
skip(): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Public, **wait-state-free** entry the Session uses to fire a `say` line's
|
|
382
|
+
* commands at show / after-reveal / advance time. Handles built-in `set`,
|
|
383
|
+
* surfaces the rest with the current mode (or `mode`, when the Session fires the
|
|
384
|
+
* displayed line's batches as part of its own skip), and awaits `blocking` ones.
|
|
385
|
+
* Delegates to {@link executeBatch}; the runner's wait-state is untouched (the
|
|
386
|
+
* Session gates its own input).
|
|
387
|
+
*/
|
|
388
|
+
runCommands(commands: readonly Command[] | undefined, mode?: RunMode): Promise<void>;
|
|
389
|
+
/** Pick choice `index` (the original option index). */
|
|
390
|
+
choose(index: number): Promise<void>;
|
|
391
|
+
/** Run non-blocking steps until we hit one that needs input, or the end. */
|
|
392
|
+
private run;
|
|
393
|
+
/** @returns true if the step blocks (waiting for advance/choose/command/end). */
|
|
394
|
+
private handleStep;
|
|
395
|
+
private jump;
|
|
396
|
+
private end;
|
|
397
|
+
private currentStep;
|
|
398
|
+
private speaker;
|
|
399
|
+
/**
|
|
400
|
+
* Resolve a choice step to its visible rows. A spent `once` option is always
|
|
401
|
+
* dropped (presentation governs condition failures only). A passing option is
|
|
402
|
+
* enabled; a failing one is returned as a `disabled` row when its
|
|
403
|
+
* `presentation` is `"disabled"`, else dropped (the default `"hidden"`).
|
|
404
|
+
*/
|
|
405
|
+
private resolveChoices;
|
|
406
|
+
/** Whether option `index` can actually be picked — the gate `choose()` uses.
|
|
407
|
+
* A spent `once` option or a failing condition refuses (a `"disabled"` row is
|
|
408
|
+
* shown but still unpickable, so this stays the single selection authority). */
|
|
409
|
+
private choiceEnabled;
|
|
410
|
+
/** A `once` option already chosen this run — always dropped from the menu
|
|
411
|
+
* regardless of `presentation`. Single source of truth for the once-gate,
|
|
412
|
+
* shared by `resolveChoices` and `choiceEnabled`. Reads the option from
|
|
413
|
+
* `step.options[index]`, so `(step, index)` is the only input. */
|
|
414
|
+
private isSpent;
|
|
415
|
+
private onceKey;
|
|
416
|
+
/**
|
|
417
|
+
* Fire an inline command batch (a `command` step or a chosen option). Manages
|
|
418
|
+
* wait-state: enters `awaiting-command` up front when the batch contains a
|
|
419
|
+
* blocking command, so a stray advance/confirm during the await is ignored; the
|
|
420
|
+
* caller transitions out of the state afterwards. The work itself goes through
|
|
421
|
+
* the wait-state-free {@link executeBatch}.
|
|
422
|
+
*/
|
|
423
|
+
private fireBatch;
|
|
424
|
+
/**
|
|
425
|
+
* The wait-state-free command executor, shared by {@link fireBatch} (inline
|
|
426
|
+
* firing) and {@link runCommands} (the Session's line-timed firing). Applies
|
|
427
|
+
* built-in `set`; surfaces the rest to the host with the current mode; awaits
|
|
428
|
+
* `blocking` handlers and fire-and-forgets the others. Touches no wait-state.
|
|
429
|
+
*/
|
|
430
|
+
private executeBatch;
|
|
431
|
+
/** The context handed to a command handler. `setVar` writes through the
|
|
432
|
+
* conversation's storage (guarded by the session for staleness), the same
|
|
433
|
+
* path as the `set` built-in — so the skill-check seam and `set` share one
|
|
434
|
+
* guarded write. */
|
|
435
|
+
private commandContext;
|
|
436
|
+
private test;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Voice-over as a registered {@link DialogueExtraChannel}. It plays a line's
|
|
441
|
+
* `voice` clip and gates auto-advance until the clip ends — so a line
|
|
442
|
+
* auto-advances at `max(clipEnd, revealEnd)` with no duration plumbing, and a
|
|
443
|
+
* short line never moves on while its long clip is still talking.
|
|
444
|
+
*
|
|
445
|
+
* The addon owns **no audio**: the host supplies `play` (wired over
|
|
446
|
+
* `@yagejs/audio` in the game), which starts a clip and returns a handle. This
|
|
447
|
+
* module imports neither audio nor a renderer, so it stays on the pixi-free root
|
|
448
|
+
* entry alongside the rest of the headless model.
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The host-owned playback handle returned by {@link VoiceChannelOptions.play}.
|
|
453
|
+
* `pause` / `resume` are optional — a host that can't pause a clip degrades to a
|
|
454
|
+
* no-op (the conversation still pauses; the clip just keeps playing).
|
|
455
|
+
*/
|
|
456
|
+
interface VoiceHandle {
|
|
457
|
+
/** Stop the clip immediately and release it. */
|
|
458
|
+
stop(): void;
|
|
459
|
+
/** Pause playback (optional). */
|
|
460
|
+
pause?(): void;
|
|
461
|
+
/** Resume playback (optional). */
|
|
462
|
+
resume?(): void;
|
|
463
|
+
}
|
|
464
|
+
interface VoiceChannelOptions {
|
|
465
|
+
/**
|
|
466
|
+
* Start the clip for `id` (the line's `voice`) and return a {@link VoiceHandle}.
|
|
467
|
+
* Call `onEnded` when the clip finishes **naturally** — that releases the
|
|
468
|
+
* auto-advance gate. The addon imports no audio; a YAGE host wires this over
|
|
469
|
+
* `@yagejs/audio`, e.g.
|
|
470
|
+
*
|
|
471
|
+
* play: (id, onEnded) => {
|
|
472
|
+
* const sound = audio.play(id, { onEnd: onEnded });
|
|
473
|
+
* return { stop: () => sound.stop(), pause: () => sound.pause(), resume: () => sound.resume() };
|
|
474
|
+
* }
|
|
475
|
+
*/
|
|
476
|
+
play(id: string, onEnded: () => void): VoiceHandle;
|
|
477
|
+
/**
|
|
478
|
+
* What a skip does to a still-playing clip. `"cut"` (default) stops it and
|
|
479
|
+
* releases the gate the moment the player completes the typewriter or
|
|
480
|
+
* fast-forwards the section; `"ring"` lets the clip play out — auto-advance
|
|
481
|
+
* keeps waiting for `onEnded`, a manual advance still works (it is never gated).
|
|
482
|
+
*/
|
|
483
|
+
onSkip?: "cut" | "ring";
|
|
484
|
+
/**
|
|
485
|
+
* Pause the clip when the conversation pauses ({@link DialogueSession.setPaused}).
|
|
486
|
+
* Default `true` — a paused conversation stops talking, the least-surprising
|
|
487
|
+
* default now that the channel knows a clip is mid-flight. Set `false` to let a
|
|
488
|
+
* clip play through a pause. `pause` / `resume` on the handle are optional, so
|
|
489
|
+
* this is a no-op when the host omits them.
|
|
490
|
+
*/
|
|
491
|
+
pauseWithConversation?: boolean;
|
|
492
|
+
/**
|
|
493
|
+
* Safety budget (ms). If a clip's `onEnded` never arrives within this many ms
|
|
494
|
+
* of starting, the gate is force-released and {@link onError} is called — so a
|
|
495
|
+
* wedged host (a clip that silently fails to report its end) can't soft-lock
|
|
496
|
+
* auto-advance. Omit (or `0`) to disable the cap.
|
|
497
|
+
*/
|
|
498
|
+
livenessMs?: number;
|
|
499
|
+
/** Diagnostics sink for the liveness cap — route it to the engine logger, the
|
|
500
|
+
* same seam as the session's `onError`. */
|
|
501
|
+
onError?: (message: string, error: unknown) => void;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Build a voice-over {@link DialogueExtraChannel}. Register it on a controller:
|
|
505
|
+
*
|
|
506
|
+
* const voice = createVoiceChannel({ play: (id, onEnded) => host.playClip(id, onEnded) });
|
|
507
|
+
* controller.addChannel(voice);
|
|
508
|
+
*
|
|
509
|
+
* Hardened against two real failure modes:
|
|
510
|
+
* - **generation guard** — a late `onEnded` from a clip that has since been
|
|
511
|
+
* superseded (the next line started) can't ungate the new line.
|
|
512
|
+
* - **liveness cap** — an optional budget force-releases the gate if a clip
|
|
513
|
+
* never reports its end, so the conversation can't soft-lock.
|
|
514
|
+
*
|
|
515
|
+
* On a mid-line save/restore the host re-presents the current line, so `present`
|
|
516
|
+
* fires again here — it stops any active clip first, so a restore restarts the
|
|
517
|
+
* line's clip cleanly (the restore-safety property).
|
|
518
|
+
*/
|
|
519
|
+
declare function createVoiceChannel(opts: VoiceChannelOptions): DialogueExtraChannel;
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Lifecycle + command events the {@link DialogueController} emits from its host
|
|
523
|
+
* entity. A scene listens with `this.on(DialogueEndedEvent, …)` (events bubble
|
|
524
|
+
* entity → scene). `DialogueCommandEvent` is the main game hook: every script
|
|
525
|
+
* command that isn't a built-in (`set`) arrives here for the game to interpret.
|
|
526
|
+
*/
|
|
527
|
+
declare const DialogueStartedEvent: _yagejs_core.EventToken<{
|
|
528
|
+
scriptId: string;
|
|
529
|
+
}>;
|
|
530
|
+
declare const DialogueLineEvent: _yagejs_core.EventToken<{
|
|
531
|
+
speaker?: string | undefined;
|
|
532
|
+
/** Plain (markup-stripped) text — handy for logs, a11y, history. */
|
|
533
|
+
text: string;
|
|
534
|
+
}>;
|
|
535
|
+
declare const DialogueChoiceShownEvent: _yagejs_core.EventToken<{
|
|
536
|
+
options: readonly string[];
|
|
537
|
+
}>;
|
|
538
|
+
declare const DialogueChoiceMadeEvent: _yagejs_core.EventToken<{
|
|
539
|
+
index: number;
|
|
540
|
+
text: string;
|
|
541
|
+
}>;
|
|
542
|
+
declare const DialogueCommandEvent: _yagejs_core.EventToken<{
|
|
543
|
+
command: Command;
|
|
544
|
+
mode: RunMode;
|
|
545
|
+
}>;
|
|
546
|
+
declare const DialogueEndedEvent: _yagejs_core.EventToken<{
|
|
547
|
+
scriptId: string;
|
|
548
|
+
}>;
|
|
549
|
+
/**
|
|
550
|
+
* Lifecycle observation events. These are the moments games
|
|
551
|
+
* actually hook — a "typing finished" blip, a choice-hover tick, skip-used
|
|
552
|
+
* analytics, an auto-advance beat — emitted by the controller from the session's
|
|
553
|
+
* observation callbacks (the one canonical observation path; there are no
|
|
554
|
+
* matching controller callback options).
|
|
555
|
+
*/
|
|
556
|
+
/** A line finished its typewriter reveal — the "typing finished" hook. Plain
|
|
557
|
+
* (markup-stripped) text, mirroring {@link DialogueLineEvent}. */
|
|
558
|
+
declare const DialogueRevealCompletedEvent: _yagejs_core.EventToken<{
|
|
559
|
+
speaker?: string | undefined;
|
|
560
|
+
text: string;
|
|
561
|
+
}>;
|
|
562
|
+
/** The choice cursor moved (keyboard nav OR pointer hover) — `index` is the
|
|
563
|
+
* original option index, `text` its plain label. */
|
|
564
|
+
declare const DialogueSelectionChangedEvent: _yagejs_core.EventToken<{
|
|
565
|
+
index: number;
|
|
566
|
+
text: string;
|
|
567
|
+
}>;
|
|
568
|
+
/** The player skipped the current section (skip-used analytics). */
|
|
569
|
+
declare const DialogueSkipUsedEvent: _yagejs_core.EventToken<{
|
|
570
|
+
scriptId: string;
|
|
571
|
+
}>;
|
|
572
|
+
/** A line advanced on its own via the auto-advance clock (vs a manual advance). */
|
|
573
|
+
declare const DialogueAutoAdvanceEvent: _yagejs_core.EventToken<{
|
|
574
|
+
scriptId: string;
|
|
575
|
+
}>;
|
|
576
|
+
/**
|
|
577
|
+
* An inline `[name k=v/]` reveal marker reached its char offset during the
|
|
578
|
+
* current line's typewriter — the game hook for positional effects
|
|
579
|
+
* (`[sfx=ding/]` → play a sound). `viaSkip` is true when a skip / complete
|
|
580
|
+
* drained it, so a loud one-shot can be suppressed. The avatar channel handles
|
|
581
|
+
* `[expression=…/]` itself; the addon name-matches no marker, so every other
|
|
582
|
+
* name flows here opaquely. Per-grapheme typewriter *ticks* are deliberately NOT
|
|
583
|
+
* an event (they fire hundreds of times per line) — wire `onRevealTick` on the
|
|
584
|
+
* controller instead.
|
|
585
|
+
*/
|
|
586
|
+
declare const DialogueRevealMarkerEvent: _yagejs_core.EventToken<{
|
|
587
|
+
marker: MarkerToken;
|
|
588
|
+
viaSkip: boolean;
|
|
589
|
+
}>;
|
|
590
|
+
|
|
591
|
+
export { type Cell, ChoiceOption, ChoiceStep, Command, CommandContext, Condition, DialogueAutoAdvanceEvent, DialogueChoiceMadeEvent, DialogueChoiceShownEvent, DialogueCommandEvent, DialogueEndedEvent, DialogueExprError, DialogueExtraChannel, DialogueFunction, DialogueLineEvent, DialoguePlayError, DialogueRevealCompletedEvent, DialogueRevealMarkerEvent, DialogueRunner, DialogueScript, DialogueScriptError, DialogueSelectionChangedEvent, DialogueSkipUsedEvent, DialogueStartedEvent, EMPTY_PARSED, type EvalScope, Expr, MarkerToken, MemoryVariableStorage, ParsedText, type ResolvedChoice, RunMode, type RunnerEnv, type RunnerHandlers, SayStep, SpeakerDef, VarMap, VarValue, VariableStorage, type VoiceChannelOptions, type VoiceHandle, cells, compose, createVoiceChannel, evalCondition, evaluate, isExpr, loadCompact, loadScript, materialize, parseCompact, parseExpr, parseMarkup, splitGraphemes, stripMarkup };
|