narraleaf-react 0.25.0 → 0.27.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/game/nlcore/action/actionHistory.d.ts +8 -0
- package/dist/game/nlcore/action/actionTypes.d.ts +1 -1
- package/dist/game/nlcore/action/actions/sceneAction.d.ts +9 -2
- package/dist/game/nlcore/action/baseElement.d.ts +14 -0
- package/dist/game/nlcore/action/gameHistory.d.ts +55 -8
- package/dist/game/nlcore/common/elements.d.ts +1 -0
- package/dist/game/nlcore/elements/camera.d.ts +13 -2
- package/dist/game/nlcore/elements/character/word.d.ts +96 -0
- package/dist/game/nlcore/elements/character.d.ts +10 -3
- package/dist/game/nlcore/elements/scene.d.ts +13 -1
- package/dist/game/nlcore/elements/transition/transitions/image/imageTransition.d.ts +14 -0
- package/dist/game/nlcore/game/liveGame.d.ts +48 -17
- package/dist/game/nlcore/gameTypes.d.ts +19 -4
- package/dist/game/player/elements/say/Sentence.d.ts +24 -1
- package/dist/game/player/elements/say/dialogOverlay.d.ts +52 -0
- package/dist/game/player/elements/say/wordRenderer.d.ts +29 -0
- package/dist/game/player/elements/scene/stageTransition.d.ts +47 -0
- package/dist/game/player/gameState.d.ts +31 -0
- package/dist/game/player/lib/elementProps.d.ts +1 -0
- package/dist/game/player/lib/useSuspendAdvance.d.ts +22 -0
- package/dist/game/player/lib/verticalText.d.ts +58 -0
- package/dist/game/player/libElements.d.ts +13 -1
- package/dist/main.js +52 -50
- package/package.json +1 -1
|
@@ -30,6 +30,14 @@ export declare class ActionHistoryManager {
|
|
|
30
30
|
push<T extends Array<any> = Array<any>>(props: ActionHistoryPushOptions, onUndo?: (...args: T) => void, args?: T): {
|
|
31
31
|
id: string;
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Whether this stack still holds the entry with the given id — i.e. whether the game can be
|
|
35
|
+
* stepped back to it in place, rather than by restoring its snapshot.
|
|
36
|
+
*
|
|
37
|
+
* The two go by the same name: a backlog entry's token *is* the id of the action-history entry
|
|
38
|
+
* pushed for the same line.
|
|
39
|
+
*/
|
|
40
|
+
has(id: string): boolean;
|
|
33
41
|
/**
|
|
34
42
|
* Undo all actions until the given id
|
|
35
43
|
*
|
|
@@ -52,7 +52,7 @@ export type NvlBlockOptions = {
|
|
|
52
52
|
hideTransition?: Partial<TransformDefinitions.CommonTransformProps>;
|
|
53
53
|
};
|
|
54
54
|
export type SceneActionContentType = {
|
|
55
|
-
[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"] ? [
|
|
55
|
+
[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;
|
|
56
56
|
};
|
|
57
57
|
export declare const StoryActionTypes: {
|
|
58
58
|
readonly action: "story:action";
|
|
@@ -7,7 +7,7 @@ import { ContentNode } from "../../action/tree/actionTree";
|
|
|
7
7
|
import { LogicAction } from "../../action/logicAction";
|
|
8
8
|
import { TypedAction } from "../../action/actions";
|
|
9
9
|
import { Story } from "../../elements/story";
|
|
10
|
-
import {
|
|
10
|
+
import type { Transition } from "../../elements/transition/transition";
|
|
11
11
|
import { ActionSearchOptions } from "../../types";
|
|
12
12
|
import { ExposedState, ExposedStateType } from "../../../player/type";
|
|
13
13
|
import type { TransformDefinitions } from "../../elements/transform/type";
|
|
@@ -46,7 +46,14 @@ export declare class SceneAction<T extends typeof SceneActionTypes[keyof typeof
|
|
|
46
46
|
static initBackgroundMusic(scene: Scene, exposed: ExposedState[ExposedStateType.scene]): Promise<void>;
|
|
47
47
|
static createSceneSnapshot(scene: Scene, state: GameState): SceneSnapshot;
|
|
48
48
|
static restoreSceneSnapshot(snapshot: SceneSnapshot, state: GameState): void;
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Play `transition` across the whole stage while jumping from this scene to `target`.
|
|
51
|
+
*
|
|
52
|
+
* Both scenes are mounted at this point — `scene:init` added the incoming one and
|
|
53
|
+
* `scene:exit` has not yet removed this one — so the transition drives the two live scene
|
|
54
|
+
* subtrees rather than swapping one image's source underneath them.
|
|
55
|
+
*/
|
|
56
|
+
applyStageTransition(gameState: GameState, transition: Transition, target: Scene, injection: ActionExecutionInjection): Awaitable<CalledActionResult, CalledActionResult>;
|
|
50
57
|
exit(state: GameState): void;
|
|
51
58
|
applyNvlVisibility(gameState: GameState, visible: boolean, options: Partial<TransformDefinitions.CommonTransformProps> | undefined, injection: ActionExecutionInjection): CalledActionResult | Awaitable<CalledActionResult, CalledActionResult>;
|
|
52
59
|
executeAction(gameState: GameState, injection: ActionExecutionInjection): ExecutedActionResult;
|
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
export declare class BaseElement {
|
|
2
|
+
/**
|
|
3
|
+
* Tell the engine this element's state has been written to, so the next save carries it.
|
|
4
|
+
*
|
|
5
|
+
* The engine marks an element itself whenever an action runs against it, which covers everything
|
|
6
|
+
* a story does. This is for a host that reaches past the story and writes element state
|
|
7
|
+
* directly — an editor moving a sprite, say. Without it the write is invisible to the save: the
|
|
8
|
+
* element is skipped, and loading brings back the state the script wrote, with nothing reporting
|
|
9
|
+
* that anything was lost. Debug builds do notice, and warn (see
|
|
10
|
+
* {@link Story.findUnmarkedElements}).
|
|
11
|
+
*
|
|
12
|
+
* Marking an element that turns out not to have changed is free — what decides whether an
|
|
13
|
+
* element reaches a save is the comparison against its authored state, not this flag.
|
|
14
|
+
*/
|
|
15
|
+
markDirty(): this;
|
|
2
16
|
}
|
|
@@ -36,23 +36,70 @@ export type GameHistory = GameHistoryAction & {
|
|
|
36
36
|
snapshot?: SerializedGameState | null;
|
|
37
37
|
};
|
|
38
38
|
export declare class GameHistoryManager {
|
|
39
|
+
/**
|
|
40
|
+
* Every line this playthrough has reached, in order — including the ones ahead of the play head
|
|
41
|
+
* after a rewind. What separates the two is {@link GameHistoryManager.cursor}.
|
|
42
|
+
*/
|
|
39
43
|
private history;
|
|
44
|
+
/**
|
|
45
|
+
* Index of the line the game is on. Everything up to and including it is the backlog; everything
|
|
46
|
+
* after it is a future the player has already read once and can step forward into again.
|
|
47
|
+
* `-1` when nothing has been reached yet.
|
|
48
|
+
*/
|
|
49
|
+
private cursor;
|
|
50
|
+
/**
|
|
51
|
+
* Set when the play head is moved by a rewind, cleared by the first line pushed afterwards.
|
|
52
|
+
*
|
|
53
|
+
* A line's snapshot is taken as the line is reached, so resuming from it re-runs it: the first
|
|
54
|
+
* push after a rewind is the current line again, not the next one. Without knowing that, a
|
|
55
|
+
* retrace looks like the story diverging and the lines ahead are thrown away.
|
|
56
|
+
*/
|
|
57
|
+
private resumingAtCursor;
|
|
40
58
|
private actionHistoryMgr;
|
|
41
59
|
constructor(actionHistoryMgr: ActionHistoryManager);
|
|
42
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Record the line the game has just reached.
|
|
62
|
+
*
|
|
63
|
+
* After a rewind the play head sits behind lines that were already read, and what happens to
|
|
64
|
+
* them depends on whether play is retracing its steps or leaving them behind. Reaching the same
|
|
65
|
+
* action again is a retrace — the lines ahead are kept, so a player who stepped back three lines
|
|
66
|
+
* and read forward again can still step forward through the rest. Reaching a different action
|
|
67
|
+
* means the story went somewhere else (the other side of a choice, most often), and a future
|
|
68
|
+
* that no longer follows from the present is dropped.
|
|
69
|
+
*
|
|
70
|
+
* There are two shapes of retrace, because a line's snapshot is taken as it is *reached*, before
|
|
71
|
+
* it runs. So resuming from a restored line re-runs that very line — the first entry pushed
|
|
72
|
+
* after a rewind is the current one coming round again — and only the ones after it arrive as
|
|
73
|
+
* the line ahead.
|
|
74
|
+
*
|
|
75
|
+
* A retraced line keeps the token it had. A caller holding one is holding a reference to a line
|
|
76
|
+
* of the story, and reading past it a second time should not quietly break that reference.
|
|
77
|
+
*/
|
|
78
|
+
push(entry: GameHistory): this;
|
|
79
|
+
/**
|
|
80
|
+
* The backlog: everything read up to and including the current line.
|
|
81
|
+
*
|
|
82
|
+
* Lines ahead of the play head after a rewind are deliberately not here — a backlog showing the
|
|
83
|
+
* future would be reporting what has not happened yet. Ask {@link GameHistoryManager.getFuture}
|
|
84
|
+
* for those.
|
|
85
|
+
*/
|
|
43
86
|
getHistory(): GameHistory[];
|
|
44
|
-
getByToken(token: string): GameHistory | null;
|
|
45
87
|
/**
|
|
46
|
-
*
|
|
88
|
+
* The lines ahead of the play head — read once, rewound past, and steppable into again.
|
|
47
89
|
*/
|
|
48
|
-
|
|
90
|
+
getFuture(): GameHistory[];
|
|
91
|
+
canUndo(): boolean;
|
|
92
|
+
canRedo(): boolean;
|
|
93
|
+
/** Searches the whole timeline, so a line ahead of the play head can be named too. */
|
|
94
|
+
getByToken(token: string): GameHistory | null;
|
|
49
95
|
/**
|
|
50
|
-
* Serialize the backlog
|
|
96
|
+
* Serialize the backlog for persistence.
|
|
51
97
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
98
|
+
* Only up to the play head: a save written after rewinding is a save of that moment, and the
|
|
99
|
+
* lines the player had read beyond it are not part of it. Loading such a save therefore opens
|
|
100
|
+
* with nothing to step forward into, which is what saving in the past means.
|
|
54
101
|
*/
|
|
55
|
-
|
|
102
|
+
serialize(): SerializedGameHistory[];
|
|
56
103
|
/**
|
|
57
104
|
* Rebuild the backlog from persisted entries.
|
|
58
105
|
*
|
|
@@ -29,3 +29,4 @@ export type { LayeredDefinition, LayerGroupDefinition, LayerResolver, LayerSlot,
|
|
|
29
29
|
export type { SentenceMetadata } from "../elements/character/sentence";
|
|
30
30
|
export type { TextEventAppearance, TextEventConfig, TextEventExpression, } from "../elements/character/textEvent";
|
|
31
31
|
export type { CharacterPortraitConfig, DialogAvatar, DialogAvatarResolver, DialogAvatarResolverContext, DialogAvatarResolution, DialogAvatarSource, } from "../elements/character/avatar";
|
|
32
|
+
export type { WordConfig, WordRenderProps, WordRenderer, } from "../elements/character/word";
|
|
@@ -31,7 +31,7 @@ export type CameraDataRaw = {
|
|
|
31
31
|
* story.camera.pan({ xalign: 0.3 }, 800), // slide the view left
|
|
32
32
|
* story.camera.darken(0.6, 500), // dim the stage
|
|
33
33
|
* jS`It's getting dark...`,
|
|
34
|
-
* story.camera.
|
|
34
|
+
* story.camera.resetCamera(600), // return to the neutral pose
|
|
35
35
|
* ]);
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
@@ -68,7 +68,18 @@ export declare class Camera extends Displayable<CameraDataRaw, Camera, Transform
|
|
|
68
68
|
/**
|
|
69
69
|
* Return the camera to its neutral pose: centred, zoom `1`, no rotation, fully opaque and no
|
|
70
70
|
* filter (which also clears {@link Camera.darken}).
|
|
71
|
+
*
|
|
72
|
+
* Named `resetCamera` rather than `reset` because every element already owns an internal
|
|
73
|
+
* `reset()` lifecycle hook — the one the engine calls when a new game starts — and an authoring
|
|
74
|
+
* helper of the same name would quietly stand in for it.
|
|
71
75
|
* @chainable
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* scene.action([
|
|
79
|
+
* story.camera.zoom(2, 800),
|
|
80
|
+
* story.camera.resetCamera(600),
|
|
81
|
+
* ]);
|
|
82
|
+
* ```
|
|
72
83
|
*/
|
|
73
|
-
|
|
84
|
+
resetCamera(duration?: number, easing?: TransformDefinitions.EasingDefinition): Proxied<Camera, Chained<LogicAction.Actions, Camera>>;
|
|
74
85
|
}
|
|
@@ -2,12 +2,63 @@ import { Color, Font } from "../../types";
|
|
|
2
2
|
import { DynamicWord } from "../../elements/character/sentence";
|
|
3
3
|
import { Pausing } from "../../elements/character/pause";
|
|
4
4
|
import { TextEvent } from "../../elements/character/textEvent";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
/**
|
|
7
|
+
* What a custom word renderer receives.
|
|
8
|
+
*
|
|
9
|
+
* A custom-rendered word is still an ordinary text word — it goes into the backlog, the read-text
|
|
10
|
+
* record and the voice pipeline as its plain text, and it is typed out character by character like
|
|
11
|
+
* any other. The renderer only decides what the revealed characters look like and what happens when
|
|
12
|
+
* they are interacted with.
|
|
13
|
+
*/
|
|
14
|
+
export type WordRenderProps<T = unknown> = {
|
|
15
|
+
/**
|
|
16
|
+
* The word's text as the engine has already laid it out — ruby, vertical writing mode and
|
|
17
|
+
* tate-chu-yoko are applied here. Render this rather than {@link WordRenderProps.text}, or those
|
|
18
|
+
* settings are silently dropped.
|
|
19
|
+
*/
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
/** The characters revealed so far. Equal to `fullText` once the typewriter has passed the word. */
|
|
22
|
+
text: string;
|
|
23
|
+
/** The whole word, revealed or not. */
|
|
24
|
+
fullText: string;
|
|
25
|
+
/** Whether every character of this word has been revealed. */
|
|
26
|
+
revealed: boolean;
|
|
27
|
+
/** Whether the whole line has finished revealing. */
|
|
28
|
+
done: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The resolved style chain for this word (engine defaults, then the dialog's text props, then
|
|
31
|
+
* the sentence, then the word). Already applied to the element wrapping the renderer; passed in
|
|
32
|
+
* so the renderer can measure against it or carry it into a portal.
|
|
33
|
+
*/
|
|
34
|
+
style: Readonly<React.CSSProperties>;
|
|
35
|
+
/** The word's own config, as given to {@link Word}. */
|
|
36
|
+
config: Readonly<Partial<WordConfig>>;
|
|
37
|
+
/** The payload passed as `data` when the word was created. */
|
|
38
|
+
data: T;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A component to render a word with, or the id of one registered with `registerWordRenderer`.
|
|
42
|
+
*
|
|
43
|
+
* Pass a component in code; pass an id when the word comes from data (a story file, a plugin) and
|
|
44
|
+
* cannot carry a function. An id that resolves to nothing renders as plain text.
|
|
45
|
+
*/
|
|
46
|
+
export type WordRenderer<T = any> = string | React.ComponentType<WordRenderProps<T>>;
|
|
5
47
|
export type WordConfig = {
|
|
6
48
|
className: string;
|
|
7
49
|
ruby: string;
|
|
8
50
|
color: Color;
|
|
9
51
|
pause: boolean;
|
|
10
52
|
cps?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Renders this word's revealed characters. See {@link Word.custom}.
|
|
55
|
+
*/
|
|
56
|
+
render?: WordRenderer;
|
|
57
|
+
/**
|
|
58
|
+
* Arbitrary payload handed to {@link WordConfig.render} as `data`. Never merged or cloned — the
|
|
59
|
+
* renderer receives the very value given here.
|
|
60
|
+
*/
|
|
61
|
+
data?: unknown;
|
|
11
62
|
} & Font;
|
|
12
63
|
export declare class Word<T extends string | DynamicWord | Pausing | TextEvent = string | DynamicWord | Pausing | TextEvent> {
|
|
13
64
|
static isWord(obj: any): obj is Word;
|
|
@@ -31,6 +82,51 @@ export declare class Word<T extends string | DynamicWord | Pausing | TextEvent =
|
|
|
31
82
|
* @param text - The text or word to italicize.
|
|
32
83
|
*/
|
|
33
84
|
static italic(text: string | Word): Word;
|
|
85
|
+
/**
|
|
86
|
+
* Render a word with a component of your own — an inline glossary term that opens a definition
|
|
87
|
+
* popup, a name that links into an in-game encyclopedia, anything the dialogue box cannot say
|
|
88
|
+
* with color and weight alone.
|
|
89
|
+
*
|
|
90
|
+
* The word stays a text word in every other respect: it is typed out character by character, it
|
|
91
|
+
* reaches the backlog and the read-text record as its plain text, and it is never serialized —
|
|
92
|
+
* the renderer is re-attached when the `say` action is evaluated again, so a save carries no
|
|
93
|
+
* trace of it.
|
|
94
|
+
*
|
|
95
|
+
* The renderer sits *inside* the element the engine styles, so the style chain (engine defaults
|
|
96
|
+
* → the dialog's text props → the sentence → the word) already applies to it; anything the
|
|
97
|
+
* renderer sets wins, being last. `render` propagates to plain strings a dynamic word returns,
|
|
98
|
+
* the same way `className` does.
|
|
99
|
+
*
|
|
100
|
+
* While a custom word is still being typed, a click on it advances the line as a click anywhere
|
|
101
|
+
* else would. Once revealed, the word takes its own clicks — the line does not advance behind
|
|
102
|
+
* the renderer's back.
|
|
103
|
+
*
|
|
104
|
+
* @param text - The word's text, or an existing word to re-render.
|
|
105
|
+
* @param render - A component, or the id of one registered with `registerWordRenderer`.
|
|
106
|
+
* @param config - Optional styling, plus the `data` payload handed to the renderer.
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* function GlossaryTerm({children, revealed, data}: WordRenderProps<{entry: string}>) {
|
|
110
|
+
* const [open, setOpen] = useState(false);
|
|
111
|
+
* return (
|
|
112
|
+
* <span className="underline decoration-dotted"
|
|
113
|
+
* onClick={() => revealed && setOpen(v => !v)}>
|
|
114
|
+
* {children}
|
|
115
|
+
* {open && <span className="absolute">{lookUp(data.entry)}</span>}
|
|
116
|
+
* </span>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* character.say([
|
|
121
|
+
* "今天的",
|
|
122
|
+
* Word.custom("以太浓度", GlossaryTerm, {data: {entry: "aether"}}),
|
|
123
|
+
* "高得反常。",
|
|
124
|
+
* ]);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
static custom<T = unknown>(text: string | Word, render: WordRenderer<T>, config?: Partial<Omit<WordConfig, "render" | "data">> & {
|
|
128
|
+
data?: T;
|
|
129
|
+
}): Word;
|
|
34
130
|
/**
|
|
35
131
|
* Wrap raw data (string, dynamic function, or pause) into a `Word` for sentences.
|
|
36
132
|
* @param text - The payload shown in dialogue, which may be static, dynamic, or a pause.
|
|
@@ -11,8 +11,15 @@ export type CharacterConfig = {
|
|
|
11
11
|
avatar?: DialogAvatar | false;
|
|
12
12
|
portraits: (Image | CharacterPortraitConfig)[];
|
|
13
13
|
};
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* What a save carries for one character.
|
|
16
|
+
*
|
|
17
|
+
* Named and shaped like every other element's save payload ({@link import("../elements/sound").SoundDataRaw},
|
|
18
|
+
* `LayerDataRaw`, `ImageDataRaw`) so that the section a serializer owns stays one level down and
|
|
19
|
+
* there is somewhere to put anything a character comes to carry besides its state.
|
|
20
|
+
*/
|
|
21
|
+
export type CharacterDataRaw = {
|
|
22
|
+
state: Record<string, any>;
|
|
16
23
|
};
|
|
17
24
|
export interface Character {
|
|
18
25
|
(content: string, config?: SentenceUserConfig): Proxied<Character, Chained<LogicAction.Actions>>;
|
|
@@ -20,7 +27,7 @@ export interface Character {
|
|
|
20
27
|
(content: SentencePrompt, config?: SentenceUserConfig): Proxied<Character, Chained<LogicAction.Actions>>;
|
|
21
28
|
(texts: TemplateStringsArray, ...words: SingleWord[]): Proxied<Character, Chained<LogicAction.Actions>>;
|
|
22
29
|
}
|
|
23
|
-
export declare class Character extends Actionable<
|
|
30
|
+
export declare class Character extends Actionable<CharacterDataRaw, Character> {
|
|
24
31
|
constructor(name: string | null, config?: DeepPartial<CharacterConfig>);
|
|
25
32
|
/**
|
|
26
33
|
* Say something
|
|
@@ -10,6 +10,7 @@ import { NVLToken } from "./nvl";
|
|
|
10
10
|
import type { TransformDefinitions } from "../elements/transform/type";
|
|
11
11
|
import type { ActionStatements } from "../elements/type";
|
|
12
12
|
import type { Persistent } from "../elements/persistent";
|
|
13
|
+
import { Transition } from "../elements/transition/transition";
|
|
13
14
|
export interface ISceneUserConfig {
|
|
14
15
|
/**
|
|
15
16
|
* Background music
|
|
@@ -33,7 +34,15 @@ export interface ISceneUserConfig {
|
|
|
33
34
|
layers: Layer[];
|
|
34
35
|
}
|
|
35
36
|
export type JumpConfig = {
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Played across the whole stage while the scenes swap: the outgoing scene drives the
|
|
39
|
+
* transition's outgoing half and the incoming scene its incoming half, so sprites, text and
|
|
40
|
+
* every other layer take part rather than only the background.
|
|
41
|
+
*
|
|
42
|
+
* The dialogue box is deliberately not part of it — it is rendered outside the stage and is
|
|
43
|
+
* expected to be gone by the time a scene ends.
|
|
44
|
+
*/
|
|
45
|
+
transition: Transition;
|
|
37
46
|
};
|
|
38
47
|
type ChainableAction = Proxied<LogicAction.GameElement, Chained<LogicAction.Actions>> | LogicAction.Actions;
|
|
39
48
|
type ChainedScene = Proxied<Scene, Chained<LogicAction.Actions>>;
|
|
@@ -65,6 +74,9 @@ export declare class Scene extends Constructable<LogicAction.Actions, Scene> {
|
|
|
65
74
|
* Jump to another scene and discard the current one.
|
|
66
75
|
*
|
|
67
76
|
* After the jump the calling scene is unloaded and any actions that follow are ignored.
|
|
77
|
+
*
|
|
78
|
+
* A `transition` plays across the whole stage rather than across the background alone; see
|
|
79
|
+
* {@link JumpConfig.transition}.
|
|
68
80
|
* @param scene - The destination scene instance.
|
|
69
81
|
* @param config - Optional transition config (or transition object).
|
|
70
82
|
* @chainable
|
|
@@ -9,9 +9,23 @@ export declare abstract class ImageTransition<T extends TransitionAnimationType[
|
|
|
9
9
|
/**@package */
|
|
10
10
|
private _currentSrc;
|
|
11
11
|
/**@package */
|
|
12
|
+
private _detached;
|
|
13
|
+
/**@package */
|
|
12
14
|
private _prevLayers;
|
|
13
15
|
/**@package */
|
|
14
16
|
private _targetLayers;
|
|
17
|
+
/**
|
|
18
|
+
* Detach the transition from any image source.
|
|
19
|
+
*
|
|
20
|
+
* A detached transition drives an element that owns its own content — the stage transition
|
|
21
|
+
* driver points the two halves at whole scene subtrees — so there is no src for the
|
|
22
|
+
* resolvers to carry, and `asPrev`/`asTarget` must contribute style only. Without this the
|
|
23
|
+
* src injection below throws, because "no src" is otherwise a genuine authoring error.
|
|
24
|
+
* @package
|
|
25
|
+
*/
|
|
26
|
+
_setDetached(detached: boolean): this;
|
|
27
|
+
/**@package */
|
|
28
|
+
_isDetached(): boolean;
|
|
15
29
|
/**@package */
|
|
16
30
|
_setPrevLayers(layers: (string | null)[]): this;
|
|
17
31
|
/**@package */
|
|
@@ -90,33 +90,64 @@ export declare class LiveGame {
|
|
|
90
90
|
*/
|
|
91
91
|
deserialize(savedGame: SavedGame): void;
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
93
|
+
* The backlog: every line read up to and including the one the game is on.
|
|
94
94
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* After stepping back, the lines beyond the play head are not here — they are a future the
|
|
96
|
+
* player can step into again with {@link redo}, and a backlog listing them would be showing what
|
|
97
|
+
* has not happened yet. {@link getFuture} returns those.
|
|
97
98
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* This method is an utility method for creating a backlog
|
|
99
|
+
* Each entry carries a `token`, which is how {@link restoreToHistory} names a line. A token
|
|
100
|
+
* keeps naming its line across saves and rewinds.
|
|
101
101
|
*/
|
|
102
102
|
getHistory(): GameHistory[];
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
104
|
+
* The lines ahead of the play head: read once, stepped back past, and reachable again.
|
|
105
|
+
*
|
|
106
|
+
* Empty during ordinary play, and empty right after loading a save — a save written in the past
|
|
107
|
+
* carries no future, because saving after stepping back saves that moment and not the lines that
|
|
108
|
+
* had been read beyond it.
|
|
109
|
+
*/
|
|
110
|
+
getFuture(): GameHistory[];
|
|
111
|
+
/** Whether there is a line before this one to step back to. */
|
|
112
|
+
canUndo(): boolean;
|
|
113
|
+
/** Whether a line stepped back past is waiting ahead. */
|
|
114
|
+
canRedo(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Step back one line.
|
|
117
|
+
*
|
|
118
|
+
* Backward and forward are one mechanism: each line recorded a self-contained snapshot of the
|
|
119
|
+
* game when it was reached, and moving in either direction restores the snapshot of the line
|
|
120
|
+
* being moved to. That is what lets this work after loading a save, which the undo stack of
|
|
121
|
+
* live closures it replaced could not — those closures cannot be written to a file, so before
|
|
122
|
+
* this, loading a save left the player with a backlog they could not step back into.
|
|
123
|
+
*
|
|
124
|
+
* The line stepped back from is not discarded; see {@link redo}.
|
|
125
|
+
*
|
|
126
|
+
* @returns `true` if the game moved, `false` if this is already the first line or that line
|
|
127
|
+
* carries no snapshot.
|
|
128
|
+
*/
|
|
129
|
+
undo(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Step forward one line, into a line stepped back past.
|
|
132
|
+
*
|
|
133
|
+
* Only reaches lines the player has already read: this replays the recorded future rather than
|
|
134
|
+
* running the story on. Reading forward normally after stepping back keeps that future while the
|
|
135
|
+
* story retraces the same lines, and drops it the moment the story goes somewhere else — a
|
|
136
|
+
* different branch of a choice has a different future, and the old one no longer follows.
|
|
105
137
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
138
|
+
* @returns `true` if the game moved, `false` if there is nothing ahead or it carries no
|
|
139
|
+
* snapshot.
|
|
108
140
|
*/
|
|
109
|
-
|
|
141
|
+
redo(): boolean;
|
|
110
142
|
/**
|
|
111
|
-
*
|
|
143
|
+
* Move the game to a recorded line, named by its token.
|
|
112
144
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* so restoring re-applies that snapshot and trims the backlog back to that line.
|
|
145
|
+
* The same mechanism as {@link undo} and {@link redo}, and it reaches in either direction: a
|
|
146
|
+
* token from {@link getHistory} steps back, one from {@link getFuture} steps forward.
|
|
116
147
|
*
|
|
117
|
-
* @param token - the
|
|
118
|
-
* @returns `true` if the line was restored, `false` if the token is unknown or the
|
|
119
|
-
* no
|
|
148
|
+
* @param token - the token of the line to move to
|
|
149
|
+
* @returns `true` if the line was restored, `false` if the token is unknown or the line carries
|
|
150
|
+
* no snapshot.
|
|
120
151
|
*/
|
|
121
152
|
restoreToHistory(token: string): boolean;
|
|
122
153
|
/**
|
|
@@ -17,8 +17,13 @@ import type { AudioBusDeclaration } from "./game/audioBus";
|
|
|
17
17
|
* - v1 (undefined on the save): core resume state only, no backlog history.
|
|
18
18
|
* - v2: adds `game.history`, a full backlog where every entry carries a self-contained
|
|
19
19
|
* restore snapshot, so loading a save keeps the backlog and any past line can be restored.
|
|
20
|
+
* - v3: `elementStates` lists only the elements whose state differs from what the script wrote.
|
|
21
|
+
* Restoring resets every element first, so an absent element means "as authored" rather than
|
|
22
|
+
* "unchanged". Older engines read a v3 save without resetting, and would leave elements holding
|
|
23
|
+
* whatever the running session put in them; newer engines read v1/v2 saves unchanged, since a
|
|
24
|
+
* list of every element is just a list that happens to name them all.
|
|
20
25
|
*/
|
|
21
|
-
export declare const SAVE_FORMAT_VERSION =
|
|
26
|
+
export declare const SAVE_FORMAT_VERSION = 3;
|
|
22
27
|
export interface SavedGameMetaData {
|
|
23
28
|
/**
|
|
24
29
|
* The timestamp of when the game was created
|
|
@@ -76,6 +81,15 @@ export interface SerializedGameState {
|
|
|
76
81
|
* self-contained snapshot that restores the game to exactly this line.
|
|
77
82
|
*/
|
|
78
83
|
export interface SerializedGameHistory {
|
|
84
|
+
/**
|
|
85
|
+
* The entry's backlog token, kept so that it survives a load.
|
|
86
|
+
*
|
|
87
|
+
* A token is how a backlog UI names a line — it is what {@link LiveGame.restoreToHistory} takes.
|
|
88
|
+
* Rebuilding the backlog with fresh tokens would invalidate every token a caller was holding the
|
|
89
|
+
* moment a save is loaded or a line restored, so restoring to the same line twice would fail.
|
|
90
|
+
* Absent on saves written before this was persisted; those entries are given fresh tokens.
|
|
91
|
+
*/
|
|
92
|
+
token?: string;
|
|
79
93
|
/**
|
|
80
94
|
* Stable action id anchor (`action.getId()`). Used to re-bind the entry to the live story on
|
|
81
95
|
* load; entries whose action no longer exists (script changed) are dropped from the backlog.
|
|
@@ -298,10 +312,11 @@ export type GameConfig = {
|
|
|
298
312
|
*/
|
|
299
313
|
allowSkipBackgroundTransform: boolean;
|
|
300
314
|
/**
|
|
301
|
-
* If true, when you press [GameConfig.player.skipKey], the game will skip the
|
|
302
|
-
* @
|
|
315
|
+
* If true, when you press [GameConfig.player.skipKey], the game will skip the transition
|
|
316
|
+
* played between scenes by {@link Scene.jumpTo}
|
|
317
|
+
* @default true
|
|
303
318
|
*/
|
|
304
|
-
|
|
319
|
+
allowSkipSceneTransition: boolean;
|
|
305
320
|
/**
|
|
306
321
|
* If true, when you press [GameConfig.player.skipKey], the game will skip the text transform
|
|
307
322
|
* @default true
|
|
@@ -7,6 +7,7 @@ import { Color } from "../../../../game/nlcore/types";
|
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { DialogState } from "./UIDialog";
|
|
9
9
|
import type { NvlDialogEntry } from "../../gameState";
|
|
10
|
+
import { type TateChuYoko, type TextGlyphOrientation, type TextWritingMode } from "../../lib/verticalText";
|
|
10
11
|
export type TextAppearanceProps = {
|
|
11
12
|
/**
|
|
12
13
|
* The default color of the text
|
|
@@ -16,6 +17,25 @@ export type TextAppearanceProps = {
|
|
|
16
17
|
fontWeight?: React.CSSProperties["fontWeight"];
|
|
17
18
|
fontWeightBold?: React.CSSProperties["fontWeight"];
|
|
18
19
|
fontFamily?: React.CSSProperties["fontFamily"];
|
|
20
|
+
/**
|
|
21
|
+
* Block flow of the text box. `vertical-rl` is the classic Japanese novel setting: columns
|
|
22
|
+
* read top to bottom and advance leftwards.
|
|
23
|
+
* @default "horizontal-tb"
|
|
24
|
+
*/
|
|
25
|
+
writingMode?: TextWritingMode;
|
|
26
|
+
/**
|
|
27
|
+
* How glyphs sit inside a vertical column. `mixed` keeps CJK upright and lays Latin on its
|
|
28
|
+
* side; ignored while the box is horizontal.
|
|
29
|
+
* @default "mixed"
|
|
30
|
+
*/
|
|
31
|
+
textOrientation?: TextGlyphOrientation;
|
|
32
|
+
/**
|
|
33
|
+
* Tate-chu-yoko (縦中横): sets a short Latin or digit run upright across the column instead of
|
|
34
|
+
* on its side. `true` combines runs of up to two characters; a number sets the limit.
|
|
35
|
+
* Ignored while the box is horizontal.
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
tateChuYoko?: TateChuYoko;
|
|
19
39
|
};
|
|
20
40
|
export type BaseTextsProps = TextAppearanceProps & {
|
|
21
41
|
className?: string;
|
|
@@ -88,6 +108,9 @@ export interface TextsPreviewProps extends Omit<React.HTMLAttributes<HTMLDivElem
|
|
|
88
108
|
fontWeight?: React.CSSProperties["fontWeight"];
|
|
89
109
|
fontWeightBold?: React.CSSProperties["fontWeight"];
|
|
90
110
|
fontFamily?: React.CSSProperties["fontFamily"];
|
|
111
|
+
writingMode?: TextWritingMode;
|
|
112
|
+
textOrientation?: TextGlyphOrientation;
|
|
113
|
+
tateChuYoko?: TateChuYoko;
|
|
91
114
|
onCompleted?: () => void;
|
|
92
115
|
}
|
|
93
116
|
export type EntryTextsProps = BaseTextsProps & {
|
|
@@ -97,7 +120,7 @@ export type EntryTextsProps = BaseTextsProps & {
|
|
|
97
120
|
useTypeEffect: boolean;
|
|
98
121
|
isActive: boolean;
|
|
99
122
|
};
|
|
100
|
-
export declare function TextsPreview({ text, sentence, words, useTypeEffect, loop, restartDelay, cps, gameSpeed, pauseDuration, defaultColor, className, style, fontSize, fontWeight, fontWeightBold, fontFamily, onCompleted, ...props }: TextsPreviewProps): React.JSX.Element;
|
|
123
|
+
export declare function TextsPreview({ text, sentence, words, useTypeEffect, loop, restartDelay, cps, gameSpeed, pauseDuration, defaultColor, className, style, fontSize, fontWeight, fontWeightBold, fontFamily, writingMode, textOrientation, tateChuYoko, onCompleted, ...props }: TextsPreviewProps): React.JSX.Element;
|
|
101
124
|
export type RawTextsProps = BaseTextsProps;
|
|
102
125
|
export declare function RawTexts(props: BaseTextsProps): React.JSX.Element;
|
|
103
126
|
/**
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A rectangle in the overlay's own coordinates — the dialog box drawn at its authored size, before
|
|
4
|
+
* the stage scales it to the window.
|
|
5
|
+
*/
|
|
6
|
+
export type DialogOverlayRect = {
|
|
7
|
+
left: number;
|
|
8
|
+
top: number;
|
|
9
|
+
right: number;
|
|
10
|
+
bottom: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export type DialogOverlay = {
|
|
15
|
+
/**
|
|
16
|
+
* Renders its children above the dialog, still inside the scaled stage, so a popup keeps the
|
|
17
|
+
* dialog's own scale and is not clipped by the text box it belongs to.
|
|
18
|
+
*/
|
|
19
|
+
Portal: React.FC<{
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Where an element sits in the overlay's coordinates. Feed the result straight to `left`/`top`
|
|
24
|
+
* on a child of {@link DialogOverlay.Portal}.
|
|
25
|
+
*/
|
|
26
|
+
measure: (element: Element | null) => DialogOverlayRect | null;
|
|
27
|
+
/** The overlay element, or `null` outside a dialog. */
|
|
28
|
+
container: HTMLElement | null;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Somewhere to draw things that belong to a line but must not live inside it — the definition popup
|
|
32
|
+
* of an inline glossary word, a tooltip on a name.
|
|
33
|
+
*
|
|
34
|
+
* The overlay covers the dialog and sits above it, inside the same scaled stage, so a popup is
|
|
35
|
+
* drawn at the dialog's own scale and escapes the text box's clipping. It lets clicks through
|
|
36
|
+
* everywhere its children do not paint; give the popup itself `pointer-events: auto`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const overlay = useDialogOverlay();
|
|
41
|
+
* const rect = overlay.measure(anchorRef.current);
|
|
42
|
+
*
|
|
43
|
+
* return rect && (
|
|
44
|
+
* <overlay.Portal>
|
|
45
|
+
* <div style={{position: "absolute", left: rect.left, top: rect.top, pointerEvents: "auto"}}>
|
|
46
|
+
* …
|
|
47
|
+
* </div>
|
|
48
|
+
* </overlay.Portal>
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function useDialogOverlay(): DialogOverlay;
|