@patterkit/runtime 0.24.0 → 0.26.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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/dist/index.cjs +57 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +58 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -22,6 +22,7 @@ type StepResult = {
|
|
|
22
22
|
direction?: string;
|
|
23
23
|
qualifier?: string;
|
|
24
24
|
qualifierName?: string;
|
|
25
|
+
speakerLabel?: string;
|
|
25
26
|
padAfter: number;
|
|
26
27
|
gameData?: GameData;
|
|
27
28
|
tags?: string[];
|
|
@@ -58,6 +59,8 @@ interface BeatInfo {
|
|
|
58
59
|
qualifier?: string;
|
|
59
60
|
/** The qualifier's shown name (source locale), e.g. `V.O.`. */
|
|
60
61
|
qualifierName?: string;
|
|
62
|
+
/** The speaker's label (source locale): `You (V.O.)`, a pair's own label, or with no qualifier the name. */
|
|
63
|
+
speakerLabel?: string;
|
|
61
64
|
/** The pause after this beat, in seconds, resolved from its own value and the defaults above it (line and
|
|
62
65
|
* text only; see line padding). What a delivered step carries. */
|
|
63
66
|
padAfter?: number;
|
|
@@ -133,6 +136,8 @@ interface ChoicePrompt {
|
|
|
133
136
|
/** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
|
|
134
137
|
qualifier?: string;
|
|
135
138
|
qualifierName?: string;
|
|
139
|
+
/** The name to show for the speaker (locale-aware): see {@link speakerLabelOf}. */
|
|
140
|
+
speakerLabel?: string;
|
|
136
141
|
/** The prompt's resolved pause, in seconds: when it is spoken (by `replayPromptOnChoose`, or by a game that
|
|
137
142
|
* voices it), the wait before the reply, negative to cut in on the question (line padding). */
|
|
138
143
|
padAfter: number;
|
|
@@ -379,6 +384,11 @@ interface FlowHost {
|
|
|
379
384
|
castDisplay: Map<string, string>;
|
|
380
385
|
/** Speaker qualifier gameId -> authored name (the unlocalised fallback when no loc string exists). */
|
|
381
386
|
qualifierDisplay: Map<string, string>;
|
|
387
|
+
/** `<name>\0<gameId>` -> a character's own label with that qualifier, for the overridden pairs only (the
|
|
388
|
+
* unlocalised fallback). A pair not here follows the pattern, whatever a loc file holds for it. */
|
|
389
|
+
speakerLabels: Map<string, string>;
|
|
390
|
+
/** The project's speaker pattern (the unlocalised fallback for the `speaker:pattern` string). */
|
|
391
|
+
speakerPattern: string;
|
|
382
392
|
nodeIndex: Map<string, SelectableNode>;
|
|
383
393
|
blockIndex: Map<string, {
|
|
384
394
|
sceneId: string;
|
|
@@ -899,6 +909,15 @@ declare class Flow {
|
|
|
899
909
|
private emit;
|
|
900
910
|
/** How many times this flow has entered each node, by node id. */
|
|
901
911
|
getVisitCounts(): Record<string, number>;
|
|
912
|
+
/**
|
|
913
|
+
* The step this flow would deliver for beat `beatId` right now, as `advance` would give it: its text in the
|
|
914
|
+
* active locale, interpolated against this flow's values (unless the project is voiced), closed captions
|
|
915
|
+
* applied, and the speaker's names and label, its pause, Game Data, and tags. Built by the same function that
|
|
916
|
+
* builds a delivered step, so the two can't differ. It doesn't move the flow, count a visit, or run an effect.
|
|
917
|
+
* Undefined for an id that is neither a beat nor an option's prompt. For a game that keeps lines by id and
|
|
918
|
+
* shows them again (after a load, or a language switch).
|
|
919
|
+
*/
|
|
920
|
+
stepFor(beatId: string): StepResult | undefined;
|
|
902
921
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
903
922
|
getChoices(): ChoiceOption[];
|
|
904
923
|
/** Pick an eligible option by id; the next `advance()` runs it. */
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ type StepResult = {
|
|
|
22
22
|
direction?: string;
|
|
23
23
|
qualifier?: string;
|
|
24
24
|
qualifierName?: string;
|
|
25
|
+
speakerLabel?: string;
|
|
25
26
|
padAfter: number;
|
|
26
27
|
gameData?: GameData;
|
|
27
28
|
tags?: string[];
|
|
@@ -58,6 +59,8 @@ interface BeatInfo {
|
|
|
58
59
|
qualifier?: string;
|
|
59
60
|
/** The qualifier's shown name (source locale), e.g. `V.O.`. */
|
|
60
61
|
qualifierName?: string;
|
|
62
|
+
/** The speaker's label (source locale): `You (V.O.)`, a pair's own label, or with no qualifier the name. */
|
|
63
|
+
speakerLabel?: string;
|
|
61
64
|
/** The pause after this beat, in seconds, resolved from its own value and the defaults above it (line and
|
|
62
65
|
* text only; see line padding). What a delivered step carries. */
|
|
63
66
|
padAfter?: number;
|
|
@@ -133,6 +136,8 @@ interface ChoicePrompt {
|
|
|
133
136
|
/** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
|
|
134
137
|
qualifier?: string;
|
|
135
138
|
qualifierName?: string;
|
|
139
|
+
/** The name to show for the speaker (locale-aware): see {@link speakerLabelOf}. */
|
|
140
|
+
speakerLabel?: string;
|
|
136
141
|
/** The prompt's resolved pause, in seconds: when it is spoken (by `replayPromptOnChoose`, or by a game that
|
|
137
142
|
* voices it), the wait before the reply, negative to cut in on the question (line padding). */
|
|
138
143
|
padAfter: number;
|
|
@@ -379,6 +384,11 @@ interface FlowHost {
|
|
|
379
384
|
castDisplay: Map<string, string>;
|
|
380
385
|
/** Speaker qualifier gameId -> authored name (the unlocalised fallback when no loc string exists). */
|
|
381
386
|
qualifierDisplay: Map<string, string>;
|
|
387
|
+
/** `<name>\0<gameId>` -> a character's own label with that qualifier, for the overridden pairs only (the
|
|
388
|
+
* unlocalised fallback). A pair not here follows the pattern, whatever a loc file holds for it. */
|
|
389
|
+
speakerLabels: Map<string, string>;
|
|
390
|
+
/** The project's speaker pattern (the unlocalised fallback for the `speaker:pattern` string). */
|
|
391
|
+
speakerPattern: string;
|
|
382
392
|
nodeIndex: Map<string, SelectableNode>;
|
|
383
393
|
blockIndex: Map<string, {
|
|
384
394
|
sceneId: string;
|
|
@@ -899,6 +909,15 @@ declare class Flow {
|
|
|
899
909
|
private emit;
|
|
900
910
|
/** How many times this flow has entered each node, by node id. */
|
|
901
911
|
getVisitCounts(): Record<string, number>;
|
|
912
|
+
/**
|
|
913
|
+
* The step this flow would deliver for beat `beatId` right now, as `advance` would give it: its text in the
|
|
914
|
+
* active locale, interpolated against this flow's values (unless the project is voiced), closed captions
|
|
915
|
+
* applied, and the speaker's names and label, its pause, Game Data, and tags. Built by the same function that
|
|
916
|
+
* builds a delivered step, so the two can't differ. It doesn't move the flow, count a visit, or run an effect.
|
|
917
|
+
* Undefined for an id that is neither a beat nor an option's prompt. For a game that keeps lines by id and
|
|
918
|
+
* shows them again (after a load, or a language switch).
|
|
919
|
+
*/
|
|
920
|
+
stepFor(beatId: string): StepResult | undefined;
|
|
902
921
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
903
922
|
getChoices(): ChoiceOption[];
|
|
904
923
|
/** Pick an eligible option by id; the next `advance()` runs it. */
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { matchedSpecificity as scoreSpecificity } from "@wildwinter/expr-specifi
|
|
|
4
4
|
import { ScopeRegistry } from "@wildwinter/scoperegistry";
|
|
5
5
|
import { defaultFor, PropertyBag } from "@wildwinter/scoperegistry";
|
|
6
6
|
import { patterDialect, interpolate, splitRef, stripCaptions } from "@patterkit/dialect";
|
|
7
|
-
import { walkNodes, effectiveGameId, castStringKey, qualifierStringKey, DEFAULT_PAD_AFTER as DEFAULT_PAD_AFTER2, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from "@patterkit/model";
|
|
7
|
+
import { walkNodes, effectiveGameId, castStringKey, qualifierStringKey, speakerLabelKey, formatSpeakerLabel, SPEAKER_PATTERN_KEY, DEFAULT_SPEAKER_PATTERN, DEFAULT_PAD_AFTER as DEFAULT_PAD_AFTER2, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from "@patterkit/model";
|
|
8
8
|
|
|
9
9
|
// src/tags.ts
|
|
10
10
|
function dedupe(tags) {
|
|
@@ -100,6 +100,25 @@ function padIndexFor(bundle) {
|
|
|
100
100
|
}
|
|
101
101
|
return index;
|
|
102
102
|
}
|
|
103
|
+
var beatIndexCache = /* @__PURE__ */ new WeakMap();
|
|
104
|
+
function beatIndexFor(bundle) {
|
|
105
|
+
let index = beatIndexCache.get(bundle);
|
|
106
|
+
if (index) return index;
|
|
107
|
+
index = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
109
|
+
for (const block of scene.blocks) {
|
|
110
|
+
walkNodes(block.children, (n) => {
|
|
111
|
+
if (n.type === "group") {
|
|
112
|
+
if (n.prompt) index.set(n.prompt.id, n.prompt);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const beat of n.beats ?? []) index.set(beat.id, beat);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
beatIndexCache.set(bundle, index);
|
|
120
|
+
return index;
|
|
121
|
+
}
|
|
103
122
|
function tagsOf(host, id) {
|
|
104
123
|
return host.tagIndex.get(id)?.slice();
|
|
105
124
|
}
|
|
@@ -168,6 +187,9 @@ var Engine = class _Engine {
|
|
|
168
187
|
for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);
|
|
169
188
|
const qualifierDisplay = /* @__PURE__ */ new Map();
|
|
170
189
|
for (const q of bundle.qualifiers ?? []) if (q?.gameId) qualifierDisplay.set(q.gameId, q.name);
|
|
190
|
+
const speakerLabels = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const c of bundle.cast ?? []) for (const [q, label] of Object.entries(c.qualifierLabels ?? {})) if (typeof label === "string") speakerLabels.set(`${c.name}\0${q}`, label);
|
|
192
|
+
const speakerPattern = bundle.speakerPattern ?? DEFAULT_SPEAKER_PATTERN;
|
|
171
193
|
this.defaultSeed = (options.seed ?? 2654435769) >>> 0;
|
|
172
194
|
const nodeIndex = /* @__PURE__ */ new Map();
|
|
173
195
|
const blockIndex = /* @__PURE__ */ new Map();
|
|
@@ -229,6 +251,8 @@ var Engine = class _Engine {
|
|
|
229
251
|
defaultStrings,
|
|
230
252
|
castDisplay,
|
|
231
253
|
qualifierDisplay,
|
|
254
|
+
speakerLabels,
|
|
255
|
+
speakerPattern,
|
|
232
256
|
nodeIndex,
|
|
233
257
|
blockIndex,
|
|
234
258
|
blockById,
|
|
@@ -639,6 +663,8 @@ var Engine = class _Engine {
|
|
|
639
663
|
const name = this.host.defaultStrings[qualifierStringKey(beat.qualifier)] ?? this.host.qualifierDisplay.get(beat.qualifier);
|
|
640
664
|
if (name !== void 0) info.qualifierName = name;
|
|
641
665
|
}
|
|
666
|
+
const label = speakerLabelOf(this.host, beat.character, beat.qualifier, true);
|
|
667
|
+
if (label !== void 0) info.speakerLabel = label;
|
|
642
668
|
}
|
|
643
669
|
if (beat.kind === "line" || beat.kind === "text") {
|
|
644
670
|
const pad = this.host.padIndex.get(beat.id);
|
|
@@ -1345,6 +1371,18 @@ var Flow = class {
|
|
|
1345
1371
|
getVisitCounts() {
|
|
1346
1372
|
return Object.fromEntries(this.visitCounts);
|
|
1347
1373
|
}
|
|
1374
|
+
/**
|
|
1375
|
+
* The step this flow would deliver for beat `beatId` right now, as `advance` would give it: its text in the
|
|
1376
|
+
* active locale, interpolated against this flow's values (unless the project is voiced), closed captions
|
|
1377
|
+
* applied, and the speaker's names and label, its pause, Game Data, and tags. Built by the same function that
|
|
1378
|
+
* builds a delivered step, so the two can't differ. It doesn't move the flow, count a visit, or run an effect.
|
|
1379
|
+
* Undefined for an id that is neither a beat nor an option's prompt. For a game that keeps lines by id and
|
|
1380
|
+
* shows them again (after a load, or a language switch).
|
|
1381
|
+
*/
|
|
1382
|
+
stepFor(beatId) {
|
|
1383
|
+
const beat = beatIndexFor(this.host.bundle).get(beatId);
|
|
1384
|
+
return beat ? this.beatResult(beat) : void 0;
|
|
1385
|
+
}
|
|
1348
1386
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
1349
1387
|
getChoices() {
|
|
1350
1388
|
return this.pendingChoice?.options ?? [];
|
|
@@ -1477,7 +1515,8 @@ var Flow = class {
|
|
|
1477
1515
|
if (!node) continue;
|
|
1478
1516
|
byId.set(o.id, node);
|
|
1479
1517
|
const { prompt: saved, ...rest } = o;
|
|
1480
|
-
|
|
1518
|
+
const label = saved?.kind === "line" && saved.speakerLabel === void 0 ? speakerLabelOf(this.host, saved.character, saved.qualifier) : void 0;
|
|
1519
|
+
options.push({ ...rest, ...saved ? { prompt: { ...saved, padAfter: saved.padAfter ?? this.promptPadOf(node), ...label !== void 0 ? { speakerLabel: label } : {} } } : {} });
|
|
1481
1520
|
}
|
|
1482
1521
|
if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };
|
|
1483
1522
|
}
|
|
@@ -1486,7 +1525,9 @@ var Flow = class {
|
|
|
1486
1525
|
if (this.pendingPromptOwnerId) {
|
|
1487
1526
|
const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);
|
|
1488
1527
|
const beat = owner ? this.authoredPromptOf(owner) : void 0;
|
|
1489
|
-
|
|
1528
|
+
const saved = c.pendingPrompt;
|
|
1529
|
+
const label = saved?.kind === "line" && saved.speakerLabel === void 0 ? speakerLabelOf(this.host, saved.character, saved.qualifier) : void 0;
|
|
1530
|
+
if (beat) this.pendingPrompt = { beat, ...saved ? { shown: { ...saved, padAfter: saved.padAfter ?? this.padOf(beat.id), ...label !== void 0 ? { speakerLabel: label } : {} } } : {} };
|
|
1490
1531
|
else this.pendingPromptOwnerId = null;
|
|
1491
1532
|
}
|
|
1492
1533
|
}
|
|
@@ -1872,6 +1913,7 @@ var Flow = class {
|
|
|
1872
1913
|
direction: silent ? void 0 : beat.direction,
|
|
1873
1914
|
qualifier: silent ? void 0 : beat.qualifier,
|
|
1874
1915
|
qualifierName: silent ? void 0 : this.resolveQualifierName(beat.qualifier),
|
|
1916
|
+
speakerLabel: silent ? void 0 : speakerLabelOf(this.host, beat.character, beat.qualifier),
|
|
1875
1917
|
padAfter: this.padOf(beat.id),
|
|
1876
1918
|
// a silent line still fires, so it still carries its pause
|
|
1877
1919
|
gameData: beat.gameData,
|
|
@@ -1921,6 +1963,7 @@ var Flow = class {
|
|
|
1921
1963
|
direction: beat.direction,
|
|
1922
1964
|
qualifier: beat.qualifier,
|
|
1923
1965
|
qualifierName: this.resolveQualifierName(beat.qualifier),
|
|
1966
|
+
speakerLabel: speakerLabelOf(this.host, beat.character, beat.qualifier),
|
|
1924
1967
|
padAfter
|
|
1925
1968
|
} : { kind: "text", text, padAfter };
|
|
1926
1969
|
}
|
|
@@ -1943,6 +1986,7 @@ var Flow = class {
|
|
|
1943
1986
|
direction: shown.direction,
|
|
1944
1987
|
qualifier: shown.qualifier,
|
|
1945
1988
|
qualifierName: shown.qualifierName,
|
|
1989
|
+
speakerLabel: shown.speakerLabel,
|
|
1946
1990
|
padAfter: this.padOf(beat.id),
|
|
1947
1991
|
gameData: beat.gameData,
|
|
1948
1992
|
...withTags
|
|
@@ -2088,6 +2132,16 @@ function collectCast(nodes, out) {
|
|
|
2088
2132
|
for (const beat of n.beats ?? []) if (beat.kind === "line" && beat.character) out.add(beat.character);
|
|
2089
2133
|
});
|
|
2090
2134
|
}
|
|
2135
|
+
function speakerLabelOf(host, character, qualifier, source = false) {
|
|
2136
|
+
if (!character || host.emitIds) return void 0;
|
|
2137
|
+
const lookup = (key) => (source ? void 0 : host.strings[key]) ?? host.defaultStrings[key];
|
|
2138
|
+
const name = lookup(castStringKey(character)) ?? host.castDisplay.get(character) ?? character;
|
|
2139
|
+
if (qualifier === void 0) return name;
|
|
2140
|
+
const own = host.speakerLabels.get(`${character}\0${qualifier}`);
|
|
2141
|
+
if (own !== void 0) return lookup(speakerLabelKey(character, qualifier)) ?? own;
|
|
2142
|
+
const shown = lookup(qualifierStringKey(qualifier)) ?? host.qualifierDisplay.get(qualifier) ?? qualifier;
|
|
2143
|
+
return formatSpeakerLabel(lookup(SPEAKER_PATTERN_KEY) ?? host.speakerPattern, name, shown);
|
|
2144
|
+
}
|
|
2091
2145
|
function gameDataField(gameData) {
|
|
2092
2146
|
return gameData && Object.keys(gameData).length ? { gameData } : {};
|
|
2093
2147
|
}
|
|
@@ -2098,6 +2152,7 @@ function savedPrompt(p) {
|
|
|
2098
2152
|
if (p.direction !== void 0) out.direction = p.direction;
|
|
2099
2153
|
if (p.qualifier !== void 0) out.qualifier = p.qualifier;
|
|
2100
2154
|
if (p.qualifierName !== void 0) out.qualifierName = p.qualifierName;
|
|
2155
|
+
if (p.speakerLabel !== void 0) out.speakerLabel = p.speakerLabel;
|
|
2101
2156
|
if (typeof p.padAfter === "number") out.padAfter = p.padAfter;
|
|
2102
2157
|
return out;
|
|
2103
2158
|
}
|