narraleaf-react 0.38.0 → 0.39.1

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.
@@ -42,6 +42,9 @@ export declare const SceneActionTypes: {
42
42
  readonly init: "scene:init";
43
43
  readonly exit: "scene:exit";
44
44
  readonly jumpTo: "scene:jumpTo";
45
+ readonly callTo: "scene:callTo";
46
+ readonly preSuspend: "scene:preSuspend";
47
+ readonly resume: "scene:resume";
45
48
  readonly setBackgroundMusic: "scene:setBackgroundMusic";
46
49
  readonly preUnmount: "scene:preUnmount";
47
50
  readonly transitionToScene: "scene:transitionToScene";
@@ -55,7 +58,7 @@ export type NvlBlockOptions = {
55
58
  hideTransition?: Partial<TransformDefinitions.CommonTransformProps>;
56
59
  };
57
60
  export type SceneActionContentType = {
58
- [K in typeof SceneActionTypes[keyof typeof SceneActionTypes]]: K extends typeof SceneActionTypes["action"] ? Scene : K extends typeof SceneActionTypes["init"] ? [Scene] : K extends typeof SceneActionTypes["exit"] ? [] : K extends typeof SceneActionTypes["jumpTo"] ? [Scene] : K extends typeof SceneActionTypes["setBackgroundMusic"] ? [Sound | null, number?] : K extends typeof SceneActionTypes["preUnmount"] ? [] : K extends typeof SceneActionTypes["transitionToScene"] ? [Transition, Scene] : K extends typeof SceneActionTypes["nvlBlock"] ? [LogicAction.Actions[], NvlBlockOptions] : K extends typeof SceneActionTypes["nvlShow"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlHide"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlEnd"] ? [NvlBlockOptions?] : any;
61
+ [K in typeof SceneActionTypes[keyof typeof SceneActionTypes]]: K extends typeof SceneActionTypes["action"] ? Scene : K extends typeof SceneActionTypes["init"] ? [Scene] : K extends typeof SceneActionTypes["exit"] ? [] : K extends typeof SceneActionTypes["jumpTo"] ? [Scene] : K extends typeof SceneActionTypes["callTo"] ? [Scene] : K extends typeof SceneActionTypes["preSuspend"] ? [Scene] : K extends typeof SceneActionTypes["resume"] ? [Scene] : K extends typeof SceneActionTypes["setBackgroundMusic"] ? [Sound | null, number?] : K extends typeof SceneActionTypes["preUnmount"] ? [] : K extends typeof SceneActionTypes["transitionToScene"] ? [Transition, Scene] : K extends typeof SceneActionTypes["nvlBlock"] ? [LogicAction.Actions[], NvlBlockOptions] : K extends typeof SceneActionTypes["nvlShow"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlHide"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlEnd"] ? [NvlBlockOptions?] : any;
59
62
  };
60
63
  export declare const StoryActionTypes: {
61
64
  readonly action: "story:action";
@@ -25,6 +25,9 @@ export declare class SceneAction<T extends typeof SceneActionTypes[keyof typeof
25
25
  readonly init: "scene:init";
26
26
  readonly exit: "scene:exit";
27
27
  readonly jumpTo: "scene:jumpTo";
28
+ readonly callTo: "scene:callTo";
29
+ readonly preSuspend: "scene:preSuspend";
30
+ readonly resume: "scene:resume";
28
31
  readonly setBackgroundMusic: "scene:setBackgroundMusic";
29
32
  readonly preUnmount: "scene:preUnmount";
30
33
  readonly transitionToScene: "scene:transitionToScene";
@@ -42,8 +45,15 @@ export declare class SceneAction<T extends typeof SceneActionTypes[keyof typeof
42
45
  * Initialize background music for the target scene.
43
46
  * Waits until the previous BGM has completely faded out (if any) before
44
47
  * resolving, ensuring seamless audio transition when jumping between scenes.
48
+ *
49
+ * A **suspended** scene starts nothing. This is the one place every path that starts a scene's
50
+ * music passes through, and it has to be the guard, because the call that reaches it is not
51
+ * always the one that asked: `getExposedStateAsync` waits on a component that is not mounted
52
+ * yet, and a stage remount - which is what loading a save performs - fires those waiting
53
+ * callbacks all over again. A caller parked behind a scene call therefore came back from a save
54
+ * with its music playing over the scene it had called.
45
55
  */
46
- static initBackgroundMusic(scene: Scene, exposed: ExposedState[ExposedStateType.scene]): Promise<void>;
56
+ static initBackgroundMusic(scene: Scene, exposed: ExposedState[ExposedStateType.scene], state?: GameState): Promise<void>;
47
57
  static createSceneSnapshot(scene: Scene, state: GameState): SceneSnapshot;
48
58
  static restoreSceneSnapshot(snapshot: SceneSnapshot, state: GameState): void;
49
59
  /**
@@ -54,6 +64,25 @@ export declare class SceneAction<T extends typeof SceneActionTypes[keyof typeof
54
64
  * subtrees rather than swapping one image's source underneath them.
55
65
  */
56
66
  applyStageTransition(gameState: GameState, transition: Transition, target: Scene, injection: ActionExecutionInjection): Awaitable<CalledActionResult, CalledActionResult>;
67
+ /**
68
+ * Take a scene off the stage: the same three steps `scene:exit` performs, for the two paths
69
+ * that unload a scene the exit action never runs for - a call returning, and a plain jump
70
+ * giving up the callers parked behind it.
71
+ */
72
+ static unloadScene(scene: Scene, state: GameState): void;
73
+ /**
74
+ * Give up every scene parked by a returnable jump, innermost first.
75
+ *
76
+ * A plain jump clears the execution stack, and the frames it clears are the only things that
77
+ * could ever have returned to those scenes. Leaving them mounted would leave the stage carrying
78
+ * scenes no player can reach, each still holding its layers and its local variables.
79
+ *
80
+ * The snapshots come back so the jump can put them all back if it is undone - the same
81
+ * bargain `scene:exit` strikes with its own single scene.
82
+ */
83
+ static unwindCallStack(state: GameState): [scene: Scene, snapshot: SceneSnapshot][];
84
+ /** Put back what {@link SceneAction.unwindCallStack} took away, in the order it took it. */
85
+ static rewindCallStack(unwound: [scene: Scene, snapshot: SceneSnapshot][], state: GameState): void;
57
86
  exit(state: GameState): void;
58
87
  applyNvlVisibility(gameState: GameState, visible: boolean, options: Partial<TransformDefinitions.CommonTransformProps> | undefined, injection: ActionExecutionInjection): CalledActionResult | Awaitable<CalledActionResult, CalledActionResult>;
59
88
  executeAction(gameState: GameState, injection: ActionExecutionInjection): ExecutedActionResult;
@@ -195,5 +195,19 @@ export declare class StackModel {
195
195
  reset(): void;
196
196
  deserialize(data: StackModelRawData, actionMap: Map<string, LogicAction.Actions>): this;
197
197
  isEmpty(): boolean;
198
+ /**
199
+ * Drop everything above the innermost scene-call return address, or the whole stack if there
200
+ * is none.
201
+ *
202
+ * This is what moving the play head has to do instead of clearing outright once a scene can be
203
+ * called. A `scene:resume` item on the stack is a promise to come back to the scene that made
204
+ * the call, and it sits below everything the called scene has queued - so clearing to it moves
205
+ * the head within the called scene and leaves the promise intact, while clearing past it would
206
+ * strand a suspended scene on the stage with nothing able to return to it.
207
+ *
208
+ * With no call open the stack has no such item and this is exactly {@link reset}, which is what
209
+ * an in-scene jump has always done.
210
+ */
211
+ clearAboveCallFrame(): this;
198
212
  push(...items: (CalledActionResult | Awaitable<CalledActionResult>)[]): this;
199
213
  }
@@ -43,6 +43,27 @@ export type JumpConfig = {
43
43
  * expected to be gone by the time a scene ends.
44
44
  */
45
45
  transition: Transition;
46
+ /**
47
+ * Come back here when the target scene runs out of actions, instead of leaving for good.
48
+ *
49
+ * Defaults to `false`, which is the plain jump this method has always performed: the calling
50
+ * scene is unloaded and everything after the jump is unreachable.
51
+ *
52
+ * With `true` the calling scene is **suspended** rather than unloaded. It keeps its stage, its
53
+ * sprites, its layers and its scene-local variables, its background music is paused, and it
54
+ * stops painting while the target scene is on screen. When the target scene runs out of
55
+ * actions the call returns: the target is unloaded, the calling scene paints again, its music
56
+ * resumes from where it was paused, and the action after the jump runs.
57
+ *
58
+ * Only the scene's own background music is suspended. Everything else a sound plays through
59
+ * belongs to the story rather than to one scene, and plays on across a call exactly as it
60
+ * plays on across a plain jump.
61
+ *
62
+ * A scene cannot be called while it is already on the call stack: one `Scene` owns one place
63
+ * on the stage and one set of local variables, so it cannot be in two of them at once. Calling
64
+ * one that is already there throws.
65
+ */
66
+ returnable: boolean;
46
67
  };
47
68
  type ChainableAction = Proxied<LogicAction.GameElement, Chained<LogicAction.Actions>> | LogicAction.Actions;
48
69
  type ChainedScene = Proxied<Scene, Chained<LogicAction.Actions>>;
@@ -71,9 +92,11 @@ export declare class Scene extends Constructable<LogicAction.Actions, Scene> {
71
92
  */
72
93
  setBackground(background: Color | ImageSrc, transition?: ImageTransition): ChainedScene;
73
94
  /**
74
- * Jump to another scene and discard the current one.
95
+ * Jump to another scene, either for good or for the length of that scene.
75
96
  *
76
- * After the jump the calling scene is unloaded and any actions that follow are ignored.
97
+ * By default the calling scene is unloaded and any actions that follow the jump are ignored.
98
+ * Pass `returnable: true` to suspend the calling scene instead and come back to the action
99
+ * after the jump once the target scene runs out of actions; see {@link JumpConfig.returnable}.
77
100
  *
78
101
  * A `transition` plays across the whole stage rather than across the background alone; see
79
102
  * {@link JumpConfig.transition}.
@@ -86,6 +109,14 @@ export declare class Scene extends Constructable<LogicAction.Actions, Scene> {
86
109
  * scene.jumpTo(nextScene, new FadeIn({duration: 800}))
87
110
  * ]);
88
111
  * ```
112
+ * @example
113
+ * ```ts
114
+ * // Play the title card, then carry on with the line after the jump.
115
+ * scene.action([
116
+ * scene.jumpTo(titleCard, {returnable: true}),
117
+ * character.say("...and we were back."),
118
+ * ]);
119
+ * ```
89
120
  */
90
121
  jumpTo(scene: Scene, config?: Partial<JumpConfig> | JumpConfig["transition"]): ChainableAction;
91
122
  /**
@@ -433,6 +433,18 @@ export type GameConfig = {
433
433
  * @default 100
434
434
  */
435
435
  maxActionHistory: number;
436
+ /**
437
+ * How many scenes a chain of returnable jumps may keep suspended at once.
438
+ *
439
+ * A returnable jump keeps the scene it left mounted, so a chain of them holds every scene in
440
+ * the chain on the stage together with its layers and sprites. This is the ceiling on that
441
+ * chain; the story throws when a call would cross it.
442
+ *
443
+ * It is not a recursion guard - recursion is impossible either way, because a scene that is
444
+ * already on the call stack cannot be called again.
445
+ * @default 8
446
+ */
447
+ maxSceneCallDepth: number;
436
448
  };
437
449
  export type GameSettings = {
438
450
  volume: number;
@@ -91,6 +91,13 @@ export type PlayerStateElement = {
91
91
  layers: Map<Layer, LogicAction.DisplayableElements[]>;
92
92
  texts: Clickable<TextElement>[];
93
93
  menus: Clickable<MenuElement, Chosen>[];
94
+ /**
95
+ * Set while this scene is a caller waiting for a returnable jump to come back
96
+ * ({@link JumpConfig.returnable}). A suspended scene keeps everything it has - its layers, its
97
+ * sprites, its local variables - but stops painting, stops being the scene new dialog and
98
+ * menus attach to, and has its background music paused.
99
+ */
100
+ suspended?: boolean;
94
101
  };
95
102
  export type NvlStateData = {
96
103
  active: boolean;
@@ -112,6 +119,11 @@ export type NvlDialogEntryData = {
112
119
  export type PlayerStateData = {
113
120
  scenes: {
114
121
  sceneId: string;
122
+ /**
123
+ * Whether this scene is a suspended caller waiting for a returnable jump to return.
124
+ * Absent in saves written before scene calls existed; readers must read it as `false`.
125
+ */
126
+ suspended?: boolean;
115
127
  elements: {
116
128
  /**@deprecated */
117
129
  displayable?: string[];
@@ -134,6 +146,14 @@ export type PresentationSnapshot = {
134
146
  export type PlayerStateElementSnapshot = {
135
147
  scene: Scene;
136
148
  layers: Map<Layer, [LogicAction.DisplayableElements, Record<string, any>][]>;
149
+ /**
150
+ * Whether this scene was a caller parked behind a returnable jump when the snapshot was taken.
151
+ *
152
+ * A snapshot is restored by rebuilding the element, so anything the element carries and the
153
+ * snapshot does not is silently dropped on the way back. This one is the difference between a
154
+ * scene that is on the stage and one that is on the stage waiting for a call to return.
155
+ */
156
+ suspended?: boolean;
137
157
  };
138
158
  export type PlayerAction = CalledActionResult;
139
159
  interface StageUtils {
@@ -219,6 +239,17 @@ export declare class GameState {
219
239
  popScene(): this;
220
240
  removeScene(scene: Scene): this;
221
241
  getSceneElements(): PlayerStateElement[];
242
+ /**
243
+ * The scene the story is currently running in.
244
+ *
245
+ * Walks from the end of the list past any **suspended** scene. A suspended scene is a caller
246
+ * parked mid-jump: it is still mounted and still holds its stage, but it is not where the next
247
+ * line of dialogue belongs. Without this skip a called scene's dialogue would attach to the
248
+ * scene that called it, which still renders its own dialog box.
249
+ *
250
+ * With nothing suspended - which is every story that never makes a returnable jump, and every
251
+ * moment of one that does not have a call open - this is byte for byte what it always was.
252
+ */
222
253
  getLastScene(): Scene | null;
223
254
  getCurrentScene(): Scene | null;
224
255
  findCurrentPortraitForCharacter(character: Character): NormalizedCharacterPortraitConfig | null;
@@ -8,6 +8,20 @@ import { AudioBusState } from "../../nlcore/game/audioBus";
8
8
  export type AudioDataRaw = {
9
9
  isPlaying: boolean;
10
10
  position: number;
11
+ /**
12
+ * The clip is paused rather than stopped: it is off, and it is expected to be picked up again
13
+ * from `position`.
14
+ *
15
+ * On the record rather than left to the sound element's own state, because a sound only reaches
16
+ * a save through the element table when something has marked it dirty AND its state differs
17
+ * from what the script authored - and a scene's background music is not any action's callee, so
18
+ * it is routinely absent from that table. This record is written for every clip the manager is
19
+ * holding, so it is the one place that can speak for a paused one.
20
+ *
21
+ * Absent in saves written before scene calls existed, where it reads as the element's own state
22
+ * exactly as it did then.
23
+ */
24
+ paused?: boolean;
11
25
  };
12
26
  export type AudioManagerDataRaw = {
13
27
  sounds: [string, AudioDataRaw][];
@@ -140,6 +154,18 @@ export declare class AudioManager {
140
154
  stop(sound: SoundElement, duration?: number): Awaitable<void>;
141
155
  setVolume(sound: SoundElement, volume: number, duration?: number): Awaitable<void>;
142
156
  mute(sound: SoundElement, muted?: boolean): Awaitable<void>;
157
+ /**
158
+ * Arm a transport change that will only act once a fade has finished, and hand back the test
159
+ * for whether it is still wanted by then.
160
+ *
161
+ * `FadeToken.finished` resolves when the fade is **cancelled** as well as when it runs out, and
162
+ * every later transport call on the same token cancels it - `resume` writes a volume, which is
163
+ * what cancels a pause's fade-out. Without this test, a scene call that returned while the
164
+ * caller's pause fade was still in flight landed the pause *after* the resume, with nothing
165
+ * left to undo it: the caller's music stayed silent for the rest of the scene while
166
+ * `sound.state.paused` said it was playing, so the next save recorded a stopped clip.
167
+ */
168
+ private arm;
143
169
  pause(sound: SoundElement, duration?: number): Awaitable<void>;
144
170
  resume(sound: SoundElement, duration?: number): Awaitable<void>;
145
171
  /**