Skip to content
Merged

4.6.1 #970

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9d70ff0
feat: 实现编辑器预览同步 V1 协议与运行时
A-kirami Apr 7, 2026
0270f49
fix: 解决报告问题
A-kirami Apr 7, 2026
7703cec
fix: 修复非官方引擎在 build 时版本号被错误覆盖的问题
boomwwww May 3, 2026
1d265ed
Merge branch 'dev' into feat/editor-preview-sync-v1-runtime
A-kirami May 4, 2026
a1ccae4
refactor(webgal): 将引擎特效纹理从 game/tex 迁移到 assets
A-kirami May 6, 2026
a2d392a
fix(parser): resolve vocal named argument with assetSetter
A-kirami May 6, 2026
63c055d
format code
A-kirami May 6, 2026
a1a6b6a
Merge branch 'dev' into feat/editor-preview-sync-v1-runtime
A-kirami May 10, 2026
a8aaec7
Create pt-br.ts
aracnus May 14, 2026
e3d6270
feat: support series argument for unlockCg
ChangeSuger May 17, 2026
806816c
Merge branch 'dev' into pr/926
MakinoharaShoko May 18, 2026
d496b52
fix: publish committed preview snapshots
MakinoharaShoko May 18, 2026
f35d0be
Merge pull request #926 from A-kirami/feat/editor-preview-sync-v1-run…
MakinoharaShoko May 18, 2026
be91f89
Merge pull request #940 from boomwwww/fix-update-engine-version.js
MakinoharaShoko May 18, 2026
1640805
Merge pull request #946 from A-kirami/fix/parser-vocal-asset-path
MakinoharaShoko May 18, 2026
587f667
update parser version
MakinoharaShoko May 18, 2026
367ee01
Update packages/webgal/src/translations/pt-br.ts
aracnus May 19, 2026
9c5bbfa
Update packages/webgal/src/translations/pt-br.ts
aracnus May 19, 2026
67433cc
Update packages/webgal/src/translations/pt-br.ts
aracnus May 19, 2026
4af37ef
Update pt-br.ts
aracnus May 19, 2026
026b47e
fix: register pt-br translation
MakinoharaShoko May 19, 2026
d308465
Merge pull request #951 from aracnus/main
MakinoharaShoko May 19, 2026
5e4769e
feat: improve extra cg stack preview
MakinoharaShoko May 19, 2026
0a820b9
Merge pull request #954 from ChangeSuger/feat/extra-series
MakinoharaShoko May 19, 2026
d7c48ae
fix: change MAX_FORWARD_SCRIPT_EXECUTION to 1000 to avoid call stack …
MakinoharaShoko May 21, 2026
eb7581a
fix: preview sync
MakinoharaShoko May 21, 2026
a20e5b7
fix: 实时预览关闭启动 Logo
MakinoharaShoko May 21, 2026
0e02a05
feat: debug protocol support debugVariable
MakinoharaShoko May 23, 2026
75d94e8
feat: 在加载模板后去除底样式
MakinoharaShoko May 23, 2026
e88d3e4
fix: 简化 textbox 样式写法
MakinoharaShoko May 23, 2026
2a79ad3
feat: continue game function update
ChangeSuger May 23, 2026
d359379
fix: base preview set-effect on captured transform
A-kirami May 24, 2026
62ca9cb
Merge branch 'dev' into fix/preview-set-effect-baseline
A-kirami May 24, 2026
ec8f12c
refactor: 简化预览 set-effect 基线合并
A-kirami May 24, 2026
79f53f3
fix: throttle continue autosave
MakinoharaShoko May 27, 2026
8bdd196
Merge branch 'dev' into pr/961
MakinoharaShoko May 27, 2026
643a8b9
Merge pull request #961 from ChangeSuger/feat/continue-game
MakinoharaShoko May 27, 2026
5f5d36a
Merge branch 'dev' into pr/962
MakinoharaShoko May 27, 2026
d653ef6
Merge pull request #962 from A-kirami/fix/preview-set-effect-baseline
MakinoharaShoko May 27, 2026
787236f
Merge pull request #945 from A-kirami/refactor/move-engine-effect-tex…
MakinoharaShoko May 28, 2026
8accb4c
animation support ignore default
MakinoharaShoko May 28, 2026
b617cf2
fix: stabilize fast-forward animation state
MakinoharaShoko May 28, 2026
81381b8
add kor translation
YRae02 Jun 3, 2026
c9b791e
chore: update locale from zhCn to ko
9um4 Jun 3, 2026
8228da7
Merge pull request #1 from 9um4/dev
YRae02 Jun 3, 2026
1f1b463
优化语言选择
MakinoharaShoko Jun 6, 2026
6beebd0
Merge pull request #966 from YRae02/dev
MakinoharaShoko Jun 6, 2026
29bbf6d
Merge pull request #964 from OpenWebGAL/animation-ignore-default
MakinoharaShoko Jun 6, 2026
4773d99
fix: 不用 randomUUID
MakinoharaShoko Jun 6, 2026
5f569eb
update version
MakinoharaShoko Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webgal/base",
"version": "4.6.0",
"version": "4.6.1",
"description": "A brand new web Visual Novel engine.",
"repository": "https://github.com/OpenWebGAL/WebGAL.git",
"author": "Mahiru <Mahiru_@outlook.com>",
Expand Down
2 changes: 1 addition & 1 deletion packages/parser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webgal-parser",
"version": "4.5.18",
"version": "4.6.1",
"description": "WebGAL script parser",
"scripts": {
"test": "vitest",
Expand Down
63 changes: 33 additions & 30 deletions packages/parser/src/scriptParser/argsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,39 @@ export function argsParser(
key: 'vocal',
value: assetSetter(e, fileType.vocal),
});
} else {
// 判断是不是省略参数
if (argValue === undefined) {
returnArrayList.push({
key: argName,
value: true,
});
} else {
// 是字符串描述的布尔值
if (argValue === 'true' || argValue === 'false') {
returnArrayList.push({
key: argName,
value: argValue === 'true',
});
} else {
// 是数字
if (!isNaN(Number(argValue))) {
returnArrayList.push({
key: argName,
value: Number(argValue),
});
} else {
// 是普通参数
returnArrayList.push({
key: argName,
value: argValue,
});
}
}
}
} else if (argName === 'vocal' && argValue !== undefined) {
returnArrayList.push({
key: argName,
value: assetSetter(argValue, fileType.vocal),
});
}
// 判断是不是省略参数
else if (argValue === undefined) {
returnArrayList.push({
key: argName,
value: true,
});
}
// 是字符串描述的布尔值
else if (argValue === 'true' || argValue === 'false') {
returnArrayList.push({
key: argName,
value: argValue === 'true',
});
}
// 是数字
else if (!isNaN(Number(argValue))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When argValue is an empty string "" (for example, when parsing an argument like -arg=), Number(argValue) evaluates to 0, and !isNaN(0) is true. This causes empty arguments to be incorrectly parsed as the number 0 instead of an empty string.

Adding a check to ensure argValue is not empty prevents this issue.

Suggested change
else if (!isNaN(Number(argValue))) {
else if (argValue !== '' && !isNaN(Number(argValue))) {

returnArrayList.push({
key: argName,
value: Number(argValue),
});
}
// 是普通参数
else {
returnArrayList.push({
key: argName,
value: argValue,
});
}
});
return returnArrayList;
Expand Down
21 changes: 21 additions & 0 deletions packages/parser/test/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,27 @@ test("say statement", async () => {
expect(result.sentenceList).toContainEqual(expectSentenceItem);
});

test("say statement applies asset setter to vocal named argument", async () => {
const parser = new SceneParser((assetList) => {
}, (fileName, assetType) => {
if (assetType === fileType.vocal) {
return `./game/vocal/${fileName}`;
}
return fileName;
}, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);

const result = parser.parse(`say:123 -speaker=xx -vocal=a.mp3;`, 'test', 'test');
const sentence = result.sentenceList[0];

expect(sentence.args).toContainEqual({ key: 'vocal', value: './game/vocal/a.mp3' });
expect(sentence.sentenceAssets).toContainEqual({
name: './game/vocal/a.mp3',
url: './game/vocal/a.mp3',
type: fileType.vocal,
lineNumber: 0,
});
});

test("wait command", async () => {
const parser = new SceneParser((assetList) => {
}, (fileName, assetType) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/webgal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webgal-engine",
"version": "4.6.0",
"version": "4.6.1",
"scripts": {
"dev": "vite --host --port 3000",
"build": "node scripts/update-engine-version.js && cross-env NODE_ENV=production tsc && vite build --base=./",
Expand Down
1 change: 1 addition & 0 deletions packages/webgal/public/game/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Title_img:WebGAL_New_Enter_Image.webp;
Title_bgm:s_Title.mp3;
Game_Logo:WebGalEnter.webp;
Enable_Appreciation:true;
Enable_Continue:true;
3 changes: 2 additions & 1 deletion packages/webgal/public/game/scene/demo_zh_cn.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ setTransition: -target=bg-main -exit=shockwaveOut;
:你好|欢迎来到 {engine} 的世界;
changeBg:bg.webp -next;
setTransition: -target=bg-main -enter=shockwaveIn -next;
unlockCg:bg.webp -name=良い夜; // 解锁CG并赋予名称
unlockCg:bg.webp -name=良い夜 -series=op; // 解锁CG并赋予名称
unlockCg:WebGAL_New_Enter_Image.webp -name=Enter -series=op; // 解锁CG并赋予名称
changeFigure:stand.webp -left -enter=enter-from-left -animationFlag=on -eyesOpen=stand.webp -eyesClose=stand.webp -mouthOpen=open_mouth.webp -mouthHalfOpen=open_mouth.webp -mouthClose=stand.webp -next;
miniAvatar:miniavatar.webp;
{heroine}:欢迎使用 {engine}!这是一款全新的网页端视觉小说引擎。 -v1.wav -left;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,4 @@
background-clip: border-box;
-webkit-background-clip: border-box;
color: #C0C0C0;
-webkit-text-fill-color: #C0C0C0;
}
1 change: 0 additions & 1 deletion packages/webgal/public/game/template/UI/Title/title.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
background-clip: border-box;
-webkit-background-clip: border-box;
color: #ffffff;
-webkit-text-fill-color: #ffffff;
z-index: 2;
text-shadow:
0 2px 6px rgba(7, 12, 20, 0.15),
Expand Down
2 changes: 1 addition & 1 deletion packages/webgal/public/game/template/template.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name":"WebGAL Refine 2026",
"webgal-version":"4.6.0"
"webgal-version":"4.6.1"
}
4 changes: 2 additions & 2 deletions packages/webgal/public/webgal-engine.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"schemaVersion": "1.0.0",
"id": "open-webgal.webgal",
"name": "WebGAL",
"version": "4.6.0",
"version": "4.6.1",
"type": "official",
"webgalVersion": "4.6.0",
"webgalVersion": "4.6.1",
"description": "界面美观、功能强大、易于开发的全新网页端视觉小说引擎",
"descriptions": {
"en": "A brand new web Visual Novel engine with a beautiful interface, powerful features, and easy development",
Expand Down
6 changes: 4 additions & 2 deletions packages/webgal/scripts/update-engine-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ try {
const engineJson = JSON.parse(fs.readFileSync(engineJsonPath, 'utf-8'));

// 更新版本号
const oldVersion = engineJson.version;
engineJson.version = version;
const oldVersion = engineJson.webgalVersion;
if (engineJson.type === 'official') {
engineJson.version = version;
}
engineJson.webgalVersion = version;

// 写回文件(保持格式化)
Expand Down
50 changes: 29 additions & 21 deletions packages/webgal/src/Core/Modules/animationFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,19 @@ export function getAnimationTimeline(
}
const mappedEffects = effect.effects.map((effect) => {
const targetSetEffect = stageStateManager.getCalculationStageState().effects.find((e) => e.target === target);
const sourceTransform =
!writeDefault && targetSetEffect && targetSetEffect.transform ? targetSetEffect.transform : baseTransform;
let newEffect;

if (!writeDefault && targetSetEffect && targetSetEffect.transform) {
if (writeFullEffect) {
newEffect = cloneDeep({ ...targetSetEffect.transform, duration: 0, ease: '' });
} else {
const targetScale = pickBy(targetSetEffect.transform.scale || {}, (source, key) => unionScaleKeys.has(key));
const targetPosition = pickBy(targetSetEffect.transform.position || {}, (s, key) => unionPositionKeys.has(key));
const originalTransform = { ...pickBy(targetSetEffect.transform, (source, key) => unionKeys.has(key)) };
originalTransform.scale = targetScale;
originalTransform.position = targetPosition;
newEffect = cloneDeep({ ...originalTransform, duration: 0, ease: '' });
}
if (writeFullEffect) {
newEffect = cloneDeep({ ...sourceTransform, duration: 0, ease: '' });
} else {
newEffect = cloneDeep({ ...baseTransform, duration: 0, ease: '' });
const targetScale = pickBy(sourceTransform.scale || {}, (source, key) => unionScaleKeys.has(key));
const targetPosition = pickBy(sourceTransform.position || {}, (s, key) => unionPositionKeys.has(key));
const originalTransform = { ...pickBy(sourceTransform, (source, key) => unionKeys.has(key)) };
if (unionScaleKeys.size > 0) originalTransform.scale = targetScale;
if (unionPositionKeys.size > 0) originalTransform.position = targetPosition;
newEffect = cloneDeep({ ...originalTransform, duration: 0, ease: '' });
}

PixiStage.assignTransform(newEffect, effect, false);
Expand Down Expand Up @@ -121,22 +119,26 @@ export function getEnterExitAnimation(
if (isBg) {
duration = DEFAULT_BG_IN_DURATION;
}
duration =
stageStateManager.getCalculationStageState().animationSettings.find((setting) => setting.target === target)
?.enterDuration ??
duration;
const animationSettings = stageStateManager
.getCalculationStageState()
.animationSettings.find((setting) => setting.target === target);
duration = animationSettings?.enterDuration ?? duration;
// 走默认动画
let animation: IAnimationObject | null = generateUniversalSoftInAnimationObj(realTarget ?? target, duration);

const transformState = stageStateManager.getCalculationStageState().effects;
const targetEffect = transformState.find((effect) => effect.target === target);

const animationName = stageStateManager
.getCalculationStageState()
.animationSettings.find((setting) => setting.target === target)?.enterAnimationName;
const animationName = animationSettings?.enterAnimationName;
if (animationName && !targetEffect) {
logger.debug('取代默认进入动画', target);
animation = getAnimationObject(animationName, realTarget ?? target, getAnimateDuration(animationName), false);
animation = getAnimationObject(
animationName,
realTarget ?? target,
getAnimateDuration(animationName),
false,
!(animationSettings?.enterAnimationIgnoreDefault ?? false),
);
duration = getAnimateDuration(animationName);
}
return { duration, animation };
Expand All @@ -155,7 +157,13 @@ export function getEnterExitAnimation(
const animationName = animationSettings?.exitAnimationName;
if (animationName) {
logger.debug('取代默认退出动画', target);
animation = getAnimationObject(animationName, realTarget ?? target, getAnimateDuration(animationName), false);
animation = getAnimationObject(
animationName,
realTarget ?? target,
getAnimateDuration(animationName),
false,
!(animationSettings?.exitAnimationIgnoreDefault ?? false),
);
duration = getAnimateDuration(animationName);
}
if (animationSettings) {
Expand Down
3 changes: 3 additions & 0 deletions packages/webgal/src/Core/Modules/gamePlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class Gameplay {
this._isFast = value;
setFastButton(value);
}
public get skipAnimation() {
return this.isFast || this.isFastPreview;
}

public resetGamePlay() {
this.isAuto = false;
Expand Down
2 changes: 2 additions & 0 deletions packages/webgal/src/Core/Modules/stage/stageInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export interface IStageAnimationSetting {
exitAnimationName?: string;
enterDuration?: number;
exitDuration?: number;
enterAnimationIgnoreDefault?: boolean;
exitAnimationIgnoreDefault?: boolean;
}

export type StageAnimationSettingUpdatableKey = Exclude<keyof IStageAnimationSetting, 'target'>;
Expand Down
2 changes: 2 additions & 0 deletions packages/webgal/src/Core/controller/gamePlay/backToTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { stopAuto } from '@/Core/controller/gamePlay/autoPlay';
import { stopFast } from '@/Core/controller/gamePlay/fastSkip';
import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
import { stageStateManager } from '@/Core/Modules/stage/stageStateManager';
import { fastSaveGame } from '../storage/fastSaveLoad';

export const backToTitle = () => {
if (webgalStore.getState().GUI.showTitle) return;
fastSaveGame();
const dispatch = webgalStore.dispatch;
stopAllPerform();
stopAuto();
Expand Down
28 changes: 23 additions & 5 deletions packages/webgal/src/Core/controller/gamePlay/fastSkip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { webgalStore } from "@/store/store";
import { SYSTEM_CONFIG } from '@/config';
import { stageStateManager } from '@/Core/Modules/stage/stageStateManager';

let temporaryFastTimeout: ReturnType<typeof setTimeout> | null = null;

const clearTemporaryFast = () => {
if (temporaryFastTimeout !== null) clearTimeout(temporaryFastTimeout);
temporaryFastTimeout = null;
};

/**
* 设置 fast 按钮的激活与否
* @param on
Expand All @@ -25,9 +32,7 @@ export const setFastButton = (on: boolean) => {
* 停止快进模式
*/
export const stopFast = () => {
if (!isFast()) {
return;
}
clearTemporaryFast();
WebGAL.gameplay.isFast = false;
if (WebGAL.gameplay.fastInterval !== null) {
clearInterval(WebGAL.gameplay.fastInterval);
Expand All @@ -39,7 +44,8 @@ export const stopFast = () => {
* 开启快进
*/
export const startFast = (force = false) => {
if (isFast()) {
clearTemporaryFast();
if (WebGAL.gameplay.fastInterval !== null) {
return;
}
WebGAL.gameplay.isFast = true;
Expand All @@ -53,6 +59,18 @@ export const startFast = (force = false) => {
}, SYSTEM_CONFIG.fast_timeout);
};

export const startTemporaryFast = (duration = 150) => {
clearTemporaryFast();
if (WebGAL.gameplay.fastInterval !== null) stopFast();
WebGAL.gameplay.isFast = true;
temporaryFastTimeout = setTimeout(() => {
if (WebGAL.gameplay.fastInterval === null) {
WebGAL.gameplay.isFast = false;
}
temporaryFastTimeout = null;
}, duration);
};

// 判断是否是快进模式
export const isFast = function () {
return WebGAL.gameplay.isFast;
Expand All @@ -71,7 +89,7 @@ export const stopAll = () => {
*/
export const switchFast = () => {
// 现在正在快进
if (WebGAL.gameplay.isFast) {
if (WebGAL.gameplay.fastInterval !== null) {
stopFast();
} else {
// 当前不在快进
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { stageStateManager } from '@/Core/Modules/stage/stageStateManager';
import { jumpToLabel } from '@/Core/gameScripts/label/jumpToLabel';
import { prefetchCurrentSceneByProgress } from '@/Core/util/prefetcher/progressPrefetcher';

const MAX_FORWARD_SCRIPT_EXECUTION = 10000;
const MAX_FORWARD_SCRIPT_EXECUTION = 1000;

export const whenChecker = (whenValue: string | undefined): boolean => {
if (whenValue === undefined) {
Expand Down
15 changes: 2 additions & 13 deletions packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,9 @@ export async function continueGame() {
* 重设模糊背景
*/
setEbg(stageStateManager.getViewStageState().bgName);
// 当且仅当游戏未开始时使用快速存档
// 当游戏开始后 使用原来的逻辑
if ((await hasFastSaveRecord()) && WebGAL.sceneManager.sceneData.currentSentenceId === 0) {
if ((await hasFastSaveRecord())) {
webgalStore.dispatch(setVisibility({ component: 'showTitle', visibility: false }));
// 恢复记录
await loadFastSaveGame();
return;
}
if (
WebGAL.sceneManager.sceneData.currentSentenceId === 0 &&
WebGAL.sceneManager.sceneData.currentScene.sceneName === 'start.txt'
) {
// 如果游戏没有开始,开始游戏
nextSentence();
} else {
restorePerform();
}
}
Loading
Loading