@patterkit/runtime 0.22.0 → 0.24.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 +26 -0
- package/README.md +13 -2
- package/dist/index.cjs +162 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -2
- package/dist/index.d.ts +40 -2
- package/dist/index.js +138 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ScalarValue } from '@wildwinter/expr';
|
|
2
2
|
import { ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, ScopeResolver, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
4
|
-
import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
|
|
5
|
-
export { Bundle, EngineSave, FlowCursor, FlowSnapshot, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
|
|
4
|
+
import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, FlowState, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
|
|
5
|
+
export { Bundle, DEFAULT_PAD_AFTER, EngineSave, FlowCursor, FlowSnapshot, FlowState, FlowValues, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
|
|
6
|
+
|
|
7
|
+
/** A beat's own `padAfter` (when it sets one) and its resolved one (always). */
|
|
8
|
+
interface BeatPad {
|
|
9
|
+
own?: number;
|
|
10
|
+
resolved: number;
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
type SelectableNode = CompiledGroup | CompiledSnippet;
|
|
8
14
|
|
|
@@ -16,12 +22,14 @@ type StepResult = {
|
|
|
16
22
|
direction?: string;
|
|
17
23
|
qualifier?: string;
|
|
18
24
|
qualifierName?: string;
|
|
25
|
+
padAfter: number;
|
|
19
26
|
gameData?: GameData;
|
|
20
27
|
tags?: string[];
|
|
21
28
|
} | {
|
|
22
29
|
type: "text";
|
|
23
30
|
id: string;
|
|
24
31
|
text: string;
|
|
32
|
+
padAfter: number;
|
|
25
33
|
gameData?: GameData;
|
|
26
34
|
tags?: string[];
|
|
27
35
|
} | {
|
|
@@ -50,6 +58,11 @@ interface BeatInfo {
|
|
|
50
58
|
qualifier?: string;
|
|
51
59
|
/** The qualifier's shown name (source locale), e.g. `V.O.`. */
|
|
52
60
|
qualifierName?: string;
|
|
61
|
+
/** The pause after this beat, in seconds, resolved from its own value and the defaults above it (line and
|
|
62
|
+
* text only; see line padding). What a delivered step carries. */
|
|
63
|
+
padAfter?: number;
|
|
64
|
+
/** The beat's own `padAfter`, when it sets one (line and text only). */
|
|
65
|
+
ownPadAfter?: number;
|
|
53
66
|
/** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */
|
|
54
67
|
text?: string;
|
|
55
68
|
/** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */
|
|
@@ -120,6 +133,9 @@ interface ChoicePrompt {
|
|
|
120
133
|
/** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
|
|
121
134
|
qualifier?: string;
|
|
122
135
|
qualifierName?: string;
|
|
136
|
+
/** The prompt's resolved pause, in seconds: when it is spoken (by `replayPromptOnChoose`, or by a game that
|
|
137
|
+
* voices it), the wait before the reply, negative to cut in on the question (line padding). */
|
|
138
|
+
padAfter: number;
|
|
123
139
|
}
|
|
124
140
|
/** A single option of a pending `choice` group. */
|
|
125
141
|
interface ChoiceOption {
|
|
@@ -374,6 +390,8 @@ interface FlowHost {
|
|
|
374
390
|
blockGameIdToId: Map<string, Map<string, string>>;
|
|
375
391
|
/** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */
|
|
376
392
|
tagIndex: Map<string, string[]>;
|
|
393
|
+
/** Every line and text beat's resolved pause (line padding), precomputed per bundle (pads.ts). */
|
|
394
|
+
padIndex: Map<string, BeatPad>;
|
|
377
395
|
/** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */
|
|
378
396
|
registry: ScopeRegistry;
|
|
379
397
|
/** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */
|
|
@@ -668,6 +686,20 @@ declare class Engine {
|
|
|
668
686
|
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
669
687
|
private static forget;
|
|
670
688
|
private refuseInCheckpoint;
|
|
689
|
+
/**
|
|
690
|
+
* Take flow `id` out of the engine as plain data, to be put back later with {@link restoreFlow}: its snapshot as
|
|
691
|
+
* `saveGame` writes it, and its own property values without its id (so it can come back under another). Shared
|
|
692
|
+
* state is never in it. Doesn't close the flow; close it with {@link closeFlow} AFTER capturing, since closing
|
|
693
|
+
* drops its values. Only reads, so it is allowed while a checkpoint is open.
|
|
694
|
+
*/
|
|
695
|
+
captureFlow(id: string): FlowState;
|
|
696
|
+
/**
|
|
697
|
+
* Open flow `id` from a {@link captureFlow} state, the per-flow half of `loadGame`: its values go into the
|
|
698
|
+
* registry as a load's do, and the flow is restored as a load restores each flow. `id` needn't be the id it was
|
|
699
|
+
* captured from. Against an edited bundle it re-finds its place by id, as a load does. Refused if a flow of
|
|
700
|
+
* that id is open, or while a checkpoint is.
|
|
701
|
+
*/
|
|
702
|
+
restoreFlow(id: string, state: FlowState): Flow;
|
|
671
703
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
672
704
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
673
705
|
closeFlow(id: string): void;
|
|
@@ -1005,6 +1037,12 @@ declare class Flow {
|
|
|
1005
1037
|
* locale, else the authoring `displayName`. Undefined when the character has no display name at all
|
|
1006
1038
|
* (the host falls back to the `character` token itself). */
|
|
1007
1039
|
private resolveCharacterName;
|
|
1040
|
+
/** The resolved pause of the beat an option shows as its prompt (its authored prompt, else its first content
|
|
1041
|
+
* line), for a saved option that predates prompts carrying one. */
|
|
1042
|
+
private promptPadOf;
|
|
1043
|
+
/** A line or text beat's resolved pause (line padding): its own `padAfter`, else the nearest default above
|
|
1044
|
+
* it, else the project's, else the built-in one; clamped to zero on a snippet's last line. */
|
|
1045
|
+
private padOf;
|
|
1008
1046
|
/** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
|
|
1009
1047
|
* active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
|
|
1010
1048
|
* maps the `qualifier` gameId itself). */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ScalarValue } from '@wildwinter/expr';
|
|
2
2
|
import { ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, ScopeResolver, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
4
|
-
import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
|
|
5
|
-
export { Bundle, EngineSave, FlowCursor, FlowSnapshot, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
|
|
4
|
+
import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, FlowState, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
|
|
5
|
+
export { Bundle, DEFAULT_PAD_AFTER, EngineSave, FlowCursor, FlowSnapshot, FlowState, FlowValues, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
|
|
6
|
+
|
|
7
|
+
/** A beat's own `padAfter` (when it sets one) and its resolved one (always). */
|
|
8
|
+
interface BeatPad {
|
|
9
|
+
own?: number;
|
|
10
|
+
resolved: number;
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
type SelectableNode = CompiledGroup | CompiledSnippet;
|
|
8
14
|
|
|
@@ -16,12 +22,14 @@ type StepResult = {
|
|
|
16
22
|
direction?: string;
|
|
17
23
|
qualifier?: string;
|
|
18
24
|
qualifierName?: string;
|
|
25
|
+
padAfter: number;
|
|
19
26
|
gameData?: GameData;
|
|
20
27
|
tags?: string[];
|
|
21
28
|
} | {
|
|
22
29
|
type: "text";
|
|
23
30
|
id: string;
|
|
24
31
|
text: string;
|
|
32
|
+
padAfter: number;
|
|
25
33
|
gameData?: GameData;
|
|
26
34
|
tags?: string[];
|
|
27
35
|
} | {
|
|
@@ -50,6 +58,11 @@ interface BeatInfo {
|
|
|
50
58
|
qualifier?: string;
|
|
51
59
|
/** The qualifier's shown name (source locale), e.g. `V.O.`. */
|
|
52
60
|
qualifierName?: string;
|
|
61
|
+
/** The pause after this beat, in seconds, resolved from its own value and the defaults above it (line and
|
|
62
|
+
* text only; see line padding). What a delivered step carries. */
|
|
63
|
+
padAfter?: number;
|
|
64
|
+
/** The beat's own `padAfter`, when it sets one (line and text only). */
|
|
65
|
+
ownPadAfter?: number;
|
|
53
66
|
/** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */
|
|
54
67
|
text?: string;
|
|
55
68
|
/** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */
|
|
@@ -120,6 +133,9 @@ interface ChoicePrompt {
|
|
|
120
133
|
/** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
|
|
121
134
|
qualifier?: string;
|
|
122
135
|
qualifierName?: string;
|
|
136
|
+
/** The prompt's resolved pause, in seconds: when it is spoken (by `replayPromptOnChoose`, or by a game that
|
|
137
|
+
* voices it), the wait before the reply, negative to cut in on the question (line padding). */
|
|
138
|
+
padAfter: number;
|
|
123
139
|
}
|
|
124
140
|
/** A single option of a pending `choice` group. */
|
|
125
141
|
interface ChoiceOption {
|
|
@@ -374,6 +390,8 @@ interface FlowHost {
|
|
|
374
390
|
blockGameIdToId: Map<string, Map<string, string>>;
|
|
375
391
|
/** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */
|
|
376
392
|
tagIndex: Map<string, string[]>;
|
|
393
|
+
/** Every line and text beat's resolved pause (line padding), precomputed per bundle (pads.ts). */
|
|
394
|
+
padIndex: Map<string, BeatPad>;
|
|
377
395
|
/** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */
|
|
378
396
|
registry: ScopeRegistry;
|
|
379
397
|
/** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */
|
|
@@ -668,6 +686,20 @@ declare class Engine {
|
|
|
668
686
|
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
669
687
|
private static forget;
|
|
670
688
|
private refuseInCheckpoint;
|
|
689
|
+
/**
|
|
690
|
+
* Take flow `id` out of the engine as plain data, to be put back later with {@link restoreFlow}: its snapshot as
|
|
691
|
+
* `saveGame` writes it, and its own property values without its id (so it can come back under another). Shared
|
|
692
|
+
* state is never in it. Doesn't close the flow; close it with {@link closeFlow} AFTER capturing, since closing
|
|
693
|
+
* drops its values. Only reads, so it is allowed while a checkpoint is open.
|
|
694
|
+
*/
|
|
695
|
+
captureFlow(id: string): FlowState;
|
|
696
|
+
/**
|
|
697
|
+
* Open flow `id` from a {@link captureFlow} state, the per-flow half of `loadGame`: its values go into the
|
|
698
|
+
* registry as a load's do, and the flow is restored as a load restores each flow. `id` needn't be the id it was
|
|
699
|
+
* captured from. Against an edited bundle it re-finds its place by id, as a load does. Refused if a flow of
|
|
700
|
+
* that id is open, or while a checkpoint is.
|
|
701
|
+
*/
|
|
702
|
+
restoreFlow(id: string, state: FlowState): Flow;
|
|
671
703
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
672
704
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
673
705
|
closeFlow(id: string): void;
|
|
@@ -1005,6 +1037,12 @@ declare class Flow {
|
|
|
1005
1037
|
* locale, else the authoring `displayName`. Undefined when the character has no display name at all
|
|
1006
1038
|
* (the host falls back to the `character` token itself). */
|
|
1007
1039
|
private resolveCharacterName;
|
|
1040
|
+
/** The resolved pause of the beat an option shows as its prompt (its authored prompt, else its first content
|
|
1041
|
+
* line), for a saved option that predates prompts carrying one. */
|
|
1042
|
+
private promptPadOf;
|
|
1043
|
+
/** A line or text beat's resolved pause (line padding): its own `padAfter`, else the nearest default above
|
|
1044
|
+
* it, else the project's, else the built-in one; clamped to zero on a snippet's last line. */
|
|
1045
|
+
private padOf;
|
|
1008
1046
|
/** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
|
|
1009
1047
|
* active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
|
|
1010
1048
|
* maps the `qualifier` gameId itself). */
|
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_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from "@patterkit/model";
|
|
7
|
+
import { walkNodes, effectiveGameId, castStringKey, qualifierStringKey, 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) {
|
|
@@ -40,6 +40,45 @@ function buildTagIndex(bundle) {
|
|
|
40
40
|
return index;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// src/pads.ts
|
|
44
|
+
import { DEFAULT_PAD_AFTER } from "@patterkit/model";
|
|
45
|
+
var spoken = (b) => b.kind === "line" || b.kind === "text";
|
|
46
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
47
|
+
function buildPadIndex(bundle) {
|
|
48
|
+
const index = /* @__PURE__ */ new Map();
|
|
49
|
+
const set = (beat, inherited, clamp) => {
|
|
50
|
+
const own = beat.kind === "gameEvent" ? void 0 : num(beat.padAfter);
|
|
51
|
+
let resolved = own ?? inherited;
|
|
52
|
+
if (clamp && resolved < 0) resolved = 0;
|
|
53
|
+
index.set(beat.id, own !== void 0 ? { own, resolved } : { resolved });
|
|
54
|
+
};
|
|
55
|
+
const visit = (node, inherited) => {
|
|
56
|
+
const def = num(node.padAfterDefault) ?? inherited;
|
|
57
|
+
if (node.type === "group") {
|
|
58
|
+
if (node.prompt) set(node.prompt, def, false);
|
|
59
|
+
for (const child of node.children) visit(child, def);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const beats = node.beats ?? [];
|
|
63
|
+
let last = -1;
|
|
64
|
+
beats.forEach((b, i) => {
|
|
65
|
+
if (spoken(b)) last = i;
|
|
66
|
+
});
|
|
67
|
+
beats.forEach((b, i) => {
|
|
68
|
+
if (spoken(b)) set(b, def, i === last);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const projectDefault = num(bundle.padAfterDefault) ?? DEFAULT_PAD_AFTER;
|
|
72
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
73
|
+
const sceneDefault = num(scene.padAfterDefault) ?? projectDefault;
|
|
74
|
+
for (const block of scene.blocks) {
|
|
75
|
+
const blockDefault = num(block.padAfterDefault) ?? sceneDefault;
|
|
76
|
+
for (const child of block.children) visit(child, blockDefault);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return index;
|
|
80
|
+
}
|
|
81
|
+
|
|
43
82
|
// src/engine.ts
|
|
44
83
|
import { SAVE_VERSION } from "@patterkit/model";
|
|
45
84
|
var astCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -52,11 +91,21 @@ function tagIndexFor(bundle) {
|
|
|
52
91
|
}
|
|
53
92
|
return index;
|
|
54
93
|
}
|
|
94
|
+
var padIndexCache = /* @__PURE__ */ new WeakMap();
|
|
95
|
+
function padIndexFor(bundle) {
|
|
96
|
+
let index = padIndexCache.get(bundle);
|
|
97
|
+
if (!index) {
|
|
98
|
+
index = buildPadIndex(bundle);
|
|
99
|
+
padIndexCache.set(bundle, index);
|
|
100
|
+
}
|
|
101
|
+
return index;
|
|
102
|
+
}
|
|
55
103
|
function tagsOf(host, id) {
|
|
56
104
|
return host.tagIndex.get(id)?.slice();
|
|
57
105
|
}
|
|
58
106
|
var OWNER = "Patter";
|
|
59
107
|
var esc = (id) => id.replace(/%/g, "%25").replace(/\//g, "%2F");
|
|
108
|
+
var unesc = (key) => key.replace(/%2F/g, "/").replace(/%25/g, "%");
|
|
60
109
|
var OWNS_REGISTRY = /* @__PURE__ */ Symbol("ownsRegistry");
|
|
61
110
|
var keys = {
|
|
62
111
|
/** A scene's SHARED `@scene` props (one bag per scene, every flow's). */
|
|
@@ -187,6 +236,7 @@ var Engine = class _Engine {
|
|
|
187
236
|
blockGameIdToId: this.blockGameIdToId,
|
|
188
237
|
// same instances the engine resolves with
|
|
189
238
|
tagIndex: tagIndexFor(bundle),
|
|
239
|
+
padIndex: padIndexFor(bundle),
|
|
190
240
|
registry,
|
|
191
241
|
ownsRegistry,
|
|
192
242
|
patterBag,
|
|
@@ -591,6 +641,9 @@ var Engine = class _Engine {
|
|
|
591
641
|
}
|
|
592
642
|
}
|
|
593
643
|
if (beat.kind === "line" || beat.kind === "text") {
|
|
644
|
+
const pad = this.host.padIndex.get(beat.id);
|
|
645
|
+
info.padAfter = pad?.resolved ?? DEFAULT_PAD_AFTER2;
|
|
646
|
+
if (pad?.own !== void 0) info.ownPadAfter = pad.own;
|
|
594
647
|
const source = this.host.defaultStrings[beat.id];
|
|
595
648
|
if (source !== void 0) info.text = source;
|
|
596
649
|
}
|
|
@@ -681,6 +734,45 @@ var Engine = class _Engine {
|
|
|
681
734
|
refuseInCheckpoint(what) {
|
|
682
735
|
if (this.host.journal) throw new Error(`${what} can't be undone, so it isn't allowed while a checkpoint is open`);
|
|
683
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* Take flow `id` out of the engine as plain data, to be put back later with {@link restoreFlow}: its snapshot as
|
|
739
|
+
* `saveGame` writes it, and its own property values without its id (so it can come back under another). Shared
|
|
740
|
+
* state is never in it. Doesn't close the flow; close it with {@link closeFlow} AFTER capturing, since closing
|
|
741
|
+
* drops its values. Only reads, so it is allowed while a checkpoint is open.
|
|
742
|
+
*/
|
|
743
|
+
captureFlow(id) {
|
|
744
|
+
const flow = this.flowsById.get(id);
|
|
745
|
+
if (!flow) throw new Error(`no open flow '${id}' to capture`);
|
|
746
|
+
return { version: SAVE_VERSION, ...flow.snapshot(), ...flowValuesFrom(id, this.host.registry.save()) };
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Open flow `id` from a {@link captureFlow} state, the per-flow half of `loadGame`: its values go into the
|
|
750
|
+
* registry as a load's do, and the flow is restored as a load restores each flow. `id` needn't be the id it was
|
|
751
|
+
* captured from. Against an edited bundle it re-finds its place by id, as a load does. Refused if a flow of
|
|
752
|
+
* that id is open, or while a checkpoint is.
|
|
753
|
+
*/
|
|
754
|
+
restoreFlow(id, state) {
|
|
755
|
+
this.refuseInCheckpoint("restoreFlow");
|
|
756
|
+
const version = state.version;
|
|
757
|
+
if (version !== 2 && version !== SAVE_VERSION) throw new Error(`unsupported flow state version: ${String(version)}`);
|
|
758
|
+
const cursor = state.cursor;
|
|
759
|
+
if (typeof cursor !== "object" || cursor === null) throw new Error("malformed flow state: no cursor");
|
|
760
|
+
if (this.flowsById.has(id)) throw new Error(`a flow '${id}' is already open; close it before restoring another under that id`);
|
|
761
|
+
this.assertExternalScopes();
|
|
762
|
+
const parked = keys.flow(id);
|
|
763
|
+
this.host.registry.discardParked(parked);
|
|
764
|
+
this.host.registry.load(flowSections(id, state), { keepParked: true });
|
|
765
|
+
const flow = new Flow(id, this.host, this.defaultSeed);
|
|
766
|
+
try {
|
|
767
|
+
flow.restore(state);
|
|
768
|
+
} catch (e) {
|
|
769
|
+
flow.close();
|
|
770
|
+
this.host.registry.discardParked(parked);
|
|
771
|
+
throw e;
|
|
772
|
+
}
|
|
773
|
+
this.flowsById.set(id, flow);
|
|
774
|
+
return flow;
|
|
775
|
+
}
|
|
684
776
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
685
777
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
686
778
|
closeFlow(id) {
|
|
@@ -1384,7 +1476,8 @@ var Flow = class {
|
|
|
1384
1476
|
const node = this.host.nodeIndex.get(o.id);
|
|
1385
1477
|
if (!node) continue;
|
|
1386
1478
|
byId.set(o.id, node);
|
|
1387
|
-
|
|
1479
|
+
const { prompt: saved, ...rest } = o;
|
|
1480
|
+
options.push({ ...rest, ...saved ? { prompt: { ...saved, padAfter: saved.padAfter ?? this.promptPadOf(node) } } : {} });
|
|
1388
1481
|
}
|
|
1389
1482
|
if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };
|
|
1390
1483
|
}
|
|
@@ -1393,7 +1486,7 @@ var Flow = class {
|
|
|
1393
1486
|
if (this.pendingPromptOwnerId) {
|
|
1394
1487
|
const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);
|
|
1395
1488
|
const beat = owner ? this.authoredPromptOf(owner) : void 0;
|
|
1396
|
-
if (beat) this.pendingPrompt = { beat, ...c.pendingPrompt ? { shown: { ...c.pendingPrompt } } : {} };
|
|
1489
|
+
if (beat) this.pendingPrompt = { beat, ...c.pendingPrompt ? { shown: { ...c.pendingPrompt, padAfter: c.pendingPrompt.padAfter ?? this.padOf(beat.id) } } : {} };
|
|
1397
1490
|
else this.pendingPromptOwnerId = null;
|
|
1398
1491
|
}
|
|
1399
1492
|
}
|
|
@@ -1763,7 +1856,7 @@ var Flow = class {
|
|
|
1763
1856
|
case "gameEvent":
|
|
1764
1857
|
return { type: "gameEvent", id: beat.id, gameData: beat.gameData, ...withTags };
|
|
1765
1858
|
case "text":
|
|
1766
|
-
return { type: "text", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), gameData: beat.gameData, ...withTags };
|
|
1859
|
+
return { type: "text", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), padAfter: this.padOf(beat.id), gameData: beat.gameData, ...withTags };
|
|
1767
1860
|
case "line": {
|
|
1768
1861
|
const raw = this.resolveString(beat.id);
|
|
1769
1862
|
const off = !this.host.captionsOn;
|
|
@@ -1779,6 +1872,8 @@ var Flow = class {
|
|
|
1779
1872
|
direction: silent ? void 0 : beat.direction,
|
|
1780
1873
|
qualifier: silent ? void 0 : beat.qualifier,
|
|
1781
1874
|
qualifierName: silent ? void 0 : this.resolveQualifierName(beat.qualifier),
|
|
1875
|
+
padAfter: this.padOf(beat.id),
|
|
1876
|
+
// a silent line still fires, so it still carries its pause
|
|
1782
1877
|
gameData: beat.gameData,
|
|
1783
1878
|
...withTags
|
|
1784
1879
|
};
|
|
@@ -1817,6 +1912,7 @@ var Flow = class {
|
|
|
1817
1912
|
const beat = this.promptBeatOf(node);
|
|
1818
1913
|
if (!beat) return void 0;
|
|
1819
1914
|
const text = this.interpolate(this.resolveString(beat.id));
|
|
1915
|
+
const padAfter = this.padOf(beat.id);
|
|
1820
1916
|
return beat.kind === "line" ? {
|
|
1821
1917
|
kind: "line",
|
|
1822
1918
|
text: this.captionLine(text),
|
|
@@ -1824,8 +1920,9 @@ var Flow = class {
|
|
|
1824
1920
|
characterName: this.resolveCharacterName(beat.character),
|
|
1825
1921
|
direction: beat.direction,
|
|
1826
1922
|
qualifier: beat.qualifier,
|
|
1827
|
-
qualifierName: this.resolveQualifierName(beat.qualifier)
|
|
1828
|
-
|
|
1923
|
+
qualifierName: this.resolveQualifierName(beat.qualifier),
|
|
1924
|
+
padAfter
|
|
1925
|
+
} : { kind: "text", text, padAfter };
|
|
1829
1926
|
}
|
|
1830
1927
|
/** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
|
|
1831
1928
|
authoredPromptOf(node) {
|
|
@@ -1836,7 +1933,7 @@ var Flow = class {
|
|
|
1836
1933
|
promptResult(beat, shown) {
|
|
1837
1934
|
const tags = tagsOf(this.host, beat.id);
|
|
1838
1935
|
const withTags = tags && tags.length ? { tags } : {};
|
|
1839
|
-
if (shown.kind === "text") return { type: "text", id: beat.id, text: shown.text, gameData: beat.gameData, ...withTags };
|
|
1936
|
+
if (shown.kind === "text") return { type: "text", id: beat.id, text: shown.text, padAfter: this.padOf(beat.id), gameData: beat.gameData, ...withTags };
|
|
1840
1937
|
return {
|
|
1841
1938
|
type: "line",
|
|
1842
1939
|
id: beat.id,
|
|
@@ -1846,6 +1943,7 @@ var Flow = class {
|
|
|
1846
1943
|
direction: shown.direction,
|
|
1847
1944
|
qualifier: shown.qualifier,
|
|
1848
1945
|
qualifierName: shown.qualifierName,
|
|
1946
|
+
padAfter: this.padOf(beat.id),
|
|
1849
1947
|
gameData: beat.gameData,
|
|
1850
1948
|
...withTags
|
|
1851
1949
|
};
|
|
@@ -1882,6 +1980,17 @@ var Flow = class {
|
|
|
1882
1980
|
const key = castStringKey(character);
|
|
1883
1981
|
return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.castDisplay.get(character);
|
|
1884
1982
|
}
|
|
1983
|
+
/** The resolved pause of the beat an option shows as its prompt (its authored prompt, else its first content
|
|
1984
|
+
* line), for a saved option that predates prompts carrying one. */
|
|
1985
|
+
promptPadOf(node) {
|
|
1986
|
+
const beat = this.promptBeatOf(node);
|
|
1987
|
+
return beat ? this.padOf(beat.id) : DEFAULT_PAD_AFTER2;
|
|
1988
|
+
}
|
|
1989
|
+
/** A line or text beat's resolved pause (line padding): its own `padAfter`, else the nearest default above
|
|
1990
|
+
* it, else the project's, else the built-in one; clamped to zero on a snippet's last line. */
|
|
1991
|
+
padOf(beatId) {
|
|
1992
|
+
return this.host.padIndex.get(beatId)?.resolved ?? DEFAULT_PAD_AFTER2;
|
|
1993
|
+
}
|
|
1885
1994
|
/** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
|
|
1886
1995
|
* active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
|
|
1887
1996
|
* maps the `qualifier` gameId itself). */
|
|
@@ -1989,6 +2098,7 @@ function savedPrompt(p) {
|
|
|
1989
2098
|
if (p.direction !== void 0) out.direction = p.direction;
|
|
1990
2099
|
if (p.qualifier !== void 0) out.qualifier = p.qualifier;
|
|
1991
2100
|
if (p.qualifierName !== void 0) out.qualifierName = p.qualifierName;
|
|
2101
|
+
if (typeof p.padAfter === "number") out.padAfter = p.padAfter;
|
|
1992
2102
|
return out;
|
|
1993
2103
|
}
|
|
1994
2104
|
function serialiseSelectors(map) {
|
|
@@ -2066,9 +2176,23 @@ function sectionsFromV2(save) {
|
|
|
2066
2176
|
const out = {};
|
|
2067
2177
|
if (save.shared?.patter) out["patter"] = save.shared.patter;
|
|
2068
2178
|
for (const [s, v] of Object.entries(save.stageBags ?? {})) out[keys.stage(s)] = v;
|
|
2069
|
-
for (const [id, f] of Object.entries(save.flows ?? {}))
|
|
2070
|
-
|
|
2071
|
-
|
|
2179
|
+
for (const [id, f] of Object.entries(save.flows ?? {})) Object.assign(out, flowSections(id, f));
|
|
2180
|
+
return out;
|
|
2181
|
+
}
|
|
2182
|
+
function flowSections(id, f) {
|
|
2183
|
+
const out = {};
|
|
2184
|
+
if (f.scopes?.patter) out[keys.flowGlobals(id)] = f.scopes.patter;
|
|
2185
|
+
for (const [s, v] of Object.entries(f.sceneBags ?? {})) out[keys.flowScene(id, s)] = v;
|
|
2186
|
+
return out;
|
|
2187
|
+
}
|
|
2188
|
+
function flowValuesFrom(id, saved) {
|
|
2189
|
+
const out = { scopes: {}, sceneBags: {} };
|
|
2190
|
+
const prefix = keys.flow(id);
|
|
2191
|
+
for (const [key, values] of Object.entries(saved)) {
|
|
2192
|
+
if (!key.startsWith(prefix)) continue;
|
|
2193
|
+
const rest = key.slice(prefix.length);
|
|
2194
|
+
if (rest === "patter") out.scopes.patter = values;
|
|
2195
|
+
else if (rest.startsWith("scene/")) out.sceneBags[unesc(rest.slice("scene/".length))] = values;
|
|
2072
2196
|
}
|
|
2073
2197
|
return out;
|
|
2074
2198
|
}
|
|
@@ -2079,6 +2203,9 @@ function truthy(v) {
|
|
|
2079
2203
|
return v.length > 0;
|
|
2080
2204
|
}
|
|
2081
2205
|
|
|
2206
|
+
// src/index.ts
|
|
2207
|
+
import { DEFAULT_PAD_AFTER as DEFAULT_PAD_AFTER3 } from "@patterkit/model";
|
|
2208
|
+
|
|
2082
2209
|
// src/describe.ts
|
|
2083
2210
|
import { effectiveGameId as effectiveGameId2 } from "@patterkit/model";
|
|
2084
2211
|
var isShared = (d, scopeDefault) => d.shared ?? scopeDefault;
|
|
@@ -2201,6 +2328,7 @@ function effectiveGameData(fields, node) {
|
|
|
2201
2328
|
}
|
|
2202
2329
|
export {
|
|
2203
2330
|
Checkpoint,
|
|
2331
|
+
DEFAULT_PAD_AFTER3 as DEFAULT_PAD_AFTER,
|
|
2204
2332
|
Engine,
|
|
2205
2333
|
Flow,
|
|
2206
2334
|
buildTagIndex,
|