Skip to content

Commit 13d6e08

Browse files
authored
Merge pull request #79 from NarraLeaf/dev_nomen
narraleaf-react-0.5.0
2 parents 8010a01 + 1395157 commit 13d6e08

55 files changed

Lines changed: 1289 additions & 344 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
# Changelog
22

3+
## [0.5.0]
4+
5+
### _Incompatible Changes_
6+
7+
- `game.config.cps` is deprecated, use `GamePreference.cps` instead
8+
- Menu GameElementHistory.`selected` may be null
9+
10+
### _Feature_
11+
12+
- New image transition: `Darkness`
13+
- Added method image.`darken`
14+
- Added method layer.`setZIndex`
15+
- Added `voiceVolume`, `bgmVolume`, `soundVolume`, and `globalVolume` to the game preferences
16+
- Using raw text for narrator instead of using Character instance
17+
- Added `waitForRouterExit` to wait for the page exit animation to complete
18+
19+
### Update
20+
21+
- The skip action will now listen to the window events instead of the player element by default
22+
- Added `isNarrator` to the dialog state
23+
24+
### Fixed
25+
26+
- Background music is not playing
27+
- Visual errors after applying transitions and before the elements are painted
28+
- Transform state is not updated correctly when the transform is skipped
29+
- Abort Events are not propagated correctly
30+
- Incorrect behavior of `router.back`
31+
- The game state is not flushed correctly
32+
- Different behavior between autoForward and user clicking
33+
- Incorrect transform repeat behavior
34+
- Unexpected NaN when converting align to percentage
35+
336
## [0.4.4] - 2025/5/9
437

538
### Fixed

README.md

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<picture>
22
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-transparent.png">
33
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4-
<img alt="Fallback image description" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4+
<img alt="NarraLeaf Logo" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
55
</picture>
66

77
<h1 align="center">NarraLeaf-React</h1>
@@ -31,49 +31,36 @@ NarraLeaf-React uses TypeScript for all scripting, so you don't have to learn a
3131
It also has a highly abstracted and easy-to-use API, for example:
3232

3333
```typescript
34-
import {Character, Menu, Scene, Word, c, b} from "narraleaf-react";
34+
import {Character, Menu, Scene, c, b} from "narraleaf-react";
3535
```
3636

3737
```typescript
38-
const scene1 = new Scene("scene1_hello_world", {
39-
background: "/background/scene1_hello_world.jpg",
38+
const scene1 = new Scene("Scene1: Hello World", {
39+
background: "/link/to/background.jpg",
4040
});
4141

42-
const johnSmith = new Character("John Smith");
43-
const johnDoe = new Character("John Doe");
42+
const jS = new Character("John Smith");
43+
const jD = new Character("John Doe");
4444

4545
scene1.action([
46-
/**
47-
* John Smith: Hello, world!
48-
* John Smith: This is my first **NarraLeaf** story.
49-
* John Smith: Start editing src/story.js and enjoy the journey!
50-
*/
51-
johnSmith
52-
.say("Hello, world!")
53-
.say`This is my first ${b("NarraLeaf")} story.`
54-
.say`Start editing ${c("src/story.js", "#00f")} and enjoy the journey!`,
55-
56-
/**
57-
* John Doe: Also, don't forget to check out the documentation!
58-
*/
59-
johnDoe.say("Also, don't forget to check out the documentation!"),
60-
61-
/**
62-
* Menu: Start the journey
63-
* > Yes I will!
64-
* - John Smith: Great! Let's start the journey!
65-
* - John Smith: You can open issues on GitHub if you have any questions.
66-
* > No, I'm going to check the documentation
67-
* - John Smith: Sure! Take your time!
68-
*/
46+
jS`Hello, world!`,
47+
jS`This is my first ${b("NarraLeaf")} story.`,
48+
jS`Start editing ${c("src/story.js", "#00f")} and enjoy the journey!`,
49+
50+
jD`Also, don't forget to check out the ${c("documentation", "#00f")}!`,
51+
52+
"By the way, the documentation is available on https://react.narraleaf.com/documentation",
53+
"You can also visit the website for demo and more information.",
54+
6955
Menu.prompt("Start the journey")
56+
7057
.choose("Yes I will!", [
71-
johnSmith
72-
.say("Great! Let's start the journey!")
73-
.say("You can open issues on GitHub if you have any questions.")
58+
jS`Great! Let's start the journey!`,
59+
jS`You can open issues on GitHub if you have any questions.`
7460
])
61+
7562
.choose("No, I'm going to check the documentation", [
76-
johnSmith.say("Sure! Take your time!")
63+
jS`Sure! Take your time!`
7764
])
7865
]);
7966
```
@@ -133,6 +120,17 @@ npm install narraleaf-react
133120
- [Plugin](https://react.narraleaf.com/documentation/core/plugin)
134121
- [Utils](https://react.narraleaf.com/documentation/core/utils)
135122
- [Player](https://react.narraleaf.com/documentation/player)
123+
- [Player](https://react.narraleaf.com/documentation/player/player)
124+
- [GameProviders](https://react.narraleaf.com/documentation/player/game-providers)
125+
- Hooks
126+
- [useGame](https://react.narraleaf.com/documentation/player/hooks/useGame)
127+
- [usePreferences](https://react.narraleaf.com/documentation/player/hooks/usePreferences)
128+
- [useRouter](https://react.narraleaf.com/documentation/player/hooks/useRouter)
129+
- [useDialog](https://react.narraleaf.com/documentation/player/hooks/useDialog)
130+
- [Page Router](https://react.narraleaf.com/documentation/player/page-router)
131+
- [Dialog](https://react.narraleaf.com/documentation/player/dialog)
132+
- [Notification](https://react.narraleaf.com/documentation/player/notification)
133+
- [Menu](https://react.narraleaf.com/documentation/player/menu)
136134
- About
137135
- [License](https://react.narraleaf.com/documentation/info/license)
138136
- [Incompatible Changes](https://react.narraleaf.com/documentation/info/incompatible-changes)

docs/README.zh-CN.md

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<picture>
22
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-transparent.png">
33
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4-
<img alt="Fallback image description" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4+
<img alt="NarraLeaf Logo" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
55
</picture>
66

77
<h1 align="center">NarraLeaf-React</h1>
@@ -35,46 +35,33 @@ import {Character, Menu, Scene, Word, c, b} from "narraleaf-react";
3535
```
3636

3737
```typescript
38-
const scene1 = new Scene("场景1_你好_世界", {
39-
background: "/background/scene1_hello_world.jpg",
38+
const scene1 = new Scene("场景1: 你好,世界", {
39+
background: "/link/to/background.jpg",
4040
});
4141

42-
const johnSmith = new Character("约翰·史密斯");
43-
const johnDoe = new Character("约翰·多");
42+
const jS = new Character("John Smith");
43+
const jD = new Character("John Doe");
4444

4545
scene1.action([
46-
/**
47-
* 约翰·史密斯: 你好世界!
48-
* 约翰·史密斯: 这是我的第一个 **NarraLeaf** 视觉小说
49-
* 约翰·史密斯: 开始编辑 src/story.js 并享受旅程!
50-
*/
51-
johnSmith
52-
.say("你好世界!")
53-
.say`这是我的第一个 ${b("NarraLeaf")} 视觉小说`
54-
.say`开始编辑 ${c("src/story.js", "#00f")} 并享受旅程!`,
55-
56-
/**
57-
* 约翰·多: 对了,别忘了查看文档!
58-
*/
59-
johnDoe.say("对了,别忘了查看文档!"),
60-
61-
/**
62-
* Menu: 开始旅程
63-
* > 是的,我会!
64-
* - 约翰·史密斯: 太好了!让我们开始旅程!
65-
* - 约翰·史密斯: 如果您有任何问题,可以在GitHub上提出问题。
66-
* > 不,我要查看文档
67-
* - 约翰·史密斯: 当然!慢慢来!
68-
*/
69-
Menu.promp("开始旅程")
70-
.choose("是的,我会!", [
71-
johnSmith
72-
.say("太好了!让我们开始旅程!")
73-
.say("如果您有任何问题,可以在GitHub上提出问题。")
74-
])
75-
.choose("不,我要查看文档", [
76-
johnSmith.say("当然!慢慢来!")
77-
])
46+
jS`你好,世界!`,
47+
jS`这是我的第一个 ${b("NarraLeaf")} 故事。`,
48+
jS`开始编辑 ${c("src/story.js", "#00f")} 并享受旅程!`,
49+
50+
jD`别忘了检查 ${c("文档", "#00f")}!`,
51+
52+
"顺便说一句,文档在 https://react.narraleaf.com/documentation",
53+
"你也可以访问网站获取更多信息。",
54+
55+
Menu.prompt("开始旅程")
56+
57+
.choose("是的,我愿意!", [
58+
jS`太好了!让我们开始旅程!`,
59+
jS`如果你有任何问题,可以在 GitHub 上提出问题。`
60+
])
61+
62+
.choose("不,我要检查文档", [
63+
jS`好的,请慢慢来!`
64+
])
7865
]);
7966
```
8067

@@ -124,7 +111,7 @@ npm install narraleaf-react
124111
- [文本](https://react.narraleaf.com/documentation/core/elements/text)
125112
- [持久化](https://react.narraleaf.com/documentation/core/elements/persistent)
126113
- [故事](https://react.narraleaf.com/documentation/core/elements/story)
127-
- [Displayable](https://react.narraleaf.com/documentation/core/elements/displayable)
114+
- [可视化组件](https://react.narraleaf.com/documentation/core/elements/displayable)
128115
- [图层](https://react.narraleaf.com/documentation/core/elements/layer)
129116
- [服务](https://react.narraleaf.com/documentation/core/elements/service)
130117
- [视频](https://react.narraleaf.com/documentation/core/elements/video)
@@ -133,6 +120,17 @@ npm install narraleaf-react
133120
- [插件](https://react.narraleaf.com/documentation/core/plugin)
134121
- [实用工具](https://react.narraleaf.com/documentation/core/utils)
135122
- [播放器](https://react.narraleaf.com/documentation/player)
123+
- [Player](https://react.narraleaf.com/documentation/player/player)
124+
- [GameProviders](https://react.narraleaf.com/documentation/player/game-providers)
125+
- 钩子
126+
- [useGame](https://react.narraleaf.com/documentation/player/hooks/useGame)
127+
- [usePreferences](https://react.narraleaf.com/documentation/player/hooks/usePreferences)
128+
- [useRouter](https://react.narraleaf.com/documentation/player/hooks/useRouter)
129+
- [useDialog](https://react.narraleaf.com/documentation/player/hooks/useDialog)
130+
- [页面路由](https://react.narraleaf.com/documentation/player/page-router)
131+
- [对话框](https://react.narraleaf.com/documentation/player/dialog)
132+
- [通知](https://react.narraleaf.com/documentation/player/notification)
133+
- [选项框](https://react.narraleaf.com/documentation/player/menu)
136134
- 关于
137135
- [许可](https://react.narraleaf.com/documentation/info/license)
138136
- [不兼容的更改](https://react.narraleaf.com/documentation/info/incompatible-changes)

src/game/nlcore/action/actionHistory.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Action } from "./action";
33
import { randId } from "@lib/util/data";
44
import { LiveGameEventToken } from "../types";
55
import { LogicAction } from "../game";
6+
import { GameHistory, GameHistoryManager } from "./gameHistory";
67

78
export type ActionHistory<T extends Array<unknown> = any> = {
89
action: Action;
@@ -32,7 +33,7 @@ export class ActionHistoryManager {
3233
*/
3334
public push<T extends Array<any> = Array<any>>(action: Action, onUndo?: (...args: T) => void, args?: T, timeline?: Timeline): {id: string} {
3435
const id = randId(6);
35-
this.history.push({action, id, args, undo: onUndo, timeline});
36+
this.history.push({action, id, args: args || [], undo: onUndo, timeline});
3637

3738
// Check if the history size exceeds the limit
3839
if (this.history.length > this.maxHistorySize) {
@@ -63,31 +64,42 @@ export class ActionHistoryManager {
6364

6465
const affected: ActionHistory<any>[] = [];
6566
for (let i = this.history.length - 1; i >= index; i--) {
66-
if (this.history[i].timeline && !this.history[i].timeline!.isSettled) {
67+
if (this.history[i].timeline && !this.history[i].timeline!.isSettled()) {
6768
this.history[i].timeline!.abort();
6869
}
70+
console.log("NarraLeaf-React [ActionHistory] Undoing", this.history[i].action.type, this.history[i]);
6971
this.history[i].undo?.(...(this.history[i].args || []));
7072
affected.push(this.history[i]);
7173
}
7274

73-
this.history.splice(index);
75+
this.history.length = index;
7476
this.hooks.onUndo.forEach(cb => cb(affected));
7577
return (affected[affected.length - 1]?.action || null) as LogicAction.Actions | null;
7678
}
7779

78-
public undo(): LogicAction.Actions | null {
79-
const last = this.history.pop();
80-
if (last) {
81-
if (last.timeline && !last.timeline.isSettled) {
82-
last.timeline.abort();
80+
public undo(gameHistory: GameHistoryManager): LogicAction.Actions | null {
81+
if (!this.ableToUndo(gameHistory)) {
82+
return null;
83+
}
84+
85+
const history = gameHistory.getHistory();
86+
let last: GameHistory | undefined;
87+
for (let i = history.length - 1; i >= 0; i--) {
88+
if (history[i].isPending !== true) {
89+
last = history[i];
90+
break;
8391
}
84-
last.undo?.(...last.args);
85-
this.hooks.onUndo.forEach(cb => cb([last]));
86-
return last.action as LogicAction.Actions;
92+
}
93+
if (last) {
94+
return this.undoUntil(last.token);
8795
}
8896
return null;
8997
}
9098

99+
public ableToUndo(gameHistory: GameHistoryManager): boolean {
100+
return this.history.length > 0 && gameHistory.getHistory().some((h: GameHistory) => h.isPending !== true);
101+
}
102+
91103
public onUndo(callback: (affected: ActionHistory<any>[]) => void): LiveGameEventToken {
92104
this.hooks.onUndo.push(callback);
93105
return {

src/game/nlcore/action/actionTypes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ export const ImageActionTypes = {
7979
flush: "image:flush",
8080
initWearable: "image:initWearable",
8181
setAppearance: "image:setAppearance",
82+
setDarkness: "image:setDarkness",
8283
} as const;
8384
export type ImageActionContentType = {
8485
[K in typeof ImageActionTypes[keyof typeof ImageActionTypes]]:
8586
K extends "image:setSrc" ? [ImageSrc | Color] :
8687
K extends "image:flush" ? [] :
8788
K extends "image:initWearable" ? [Image] :
8889
K extends "image:setAppearance" ? [FlexibleTuple<SelectElementFromEach<TagGroupDefinition>> | string[], ImageTransition | undefined] :
90+
K extends "image:setDarkness" ? [darkness: number, duration?: number, easing?: TransformDefinitions.EasingDefinition] :
8991
any;
9092
} & DisplayableActionContentType<ImageTransition>;
9193
/* Condition */
@@ -187,10 +189,12 @@ export type PersistentActionContentType = {
187189
/* Layer */
188190
export const LayerActionTypes = {
189191
action: "layer:action",
192+
setZIndex: "layer:setZIndex",
190193
} as const;
191194
export type LayerActionContentType = {
192195
[K in typeof LayerActionTypes[keyof typeof LayerActionTypes]]:
193196
K extends "layer:action" ? any :
197+
K extends "layer:setZIndex" ? [number] :
194198
any;
195199
}
196200
/* Video */

src/game/nlcore/action/actions/characterAction.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ export class CharacterAction<T extends typeof CharacterActionTypes[keyof typeof
4848
}
4949

5050
// Create dialog
51-
const dialog = gameState.createDialog(this.getId(), sentence, () => {
51+
const dialogId = gameState.idManager.generateId();
52+
const dialog = gameState.createDialog(dialogId, sentence, () => {
5253
if (voice) {
5354
const task = gameState.audioManager.stop(voice);
5455
timeline.attachChild(task);
5556
}
5657

58+
gameState.gameHistory.resolvePending(id); // accessing id is technically dangerous, but I think it is impossible to happen
59+
5760
awaitable.resolve({
5861
type: this.type,
5962
node: this.contentNode.getChild()
@@ -69,6 +72,7 @@ export class CharacterAction<T extends typeof CharacterActionTypes[keyof typeof
6972
const task = gameState.audioManager.stop(voice);
7073
timeline.attachChild(task);
7174
}
75+
dialog.cancel();
7276
});
7377
gameState.gameHistory.push({
7478
token: id,
@@ -77,7 +81,9 @@ export class CharacterAction<T extends typeof CharacterActionTypes[keyof typeof
7781
type: "say",
7882
text: dialog.text,
7983
voice: voice ? voice.getSrc() : null,
80-
}
84+
character: this.callee.state.name,
85+
},
86+
isPending: true,
8187
});
8288

8389
return awaitable;

0 commit comments

Comments
 (0)