From ebcfd2620e4944b69109bcb89431cd8e52b1eafe Mon Sep 17 00:00:00 2001 From: ChangeSuger Date: Thu, 16 Apr 2026 00:42:26 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=B7=B2?= =?UTF-8?q?=E8=AF=BB=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=94=AF=E6=8C=81=E5=8C=BA=E5=88=86=E5=B7=B2?= =?UTF-8?q?=E8=AF=BB=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../game/template/Stage/TextBox/textbox.scss | 4 + packages/webgal/src/Core/Modules/backlog.ts | 2 + .../webgal/src/Core/Modules/readHistory.ts | 103 ++++++++++++++++++ .../controller/gamePlay/scriptExecutor.ts | 1 + .../src/Core/controller/stage/resetStage.ts | 1 + packages/webgal/src/Core/webgalCore.ts | 2 + .../webgal/src/Stage/TextBox/IMSSTextbox.tsx | 3 +- packages/webgal/src/Stage/TextBox/TextBox.tsx | 2 + .../src/Stage/TextBox/textbox.module.scss | 4 + packages/webgal/src/Stage/TextBox/types.ts | 1 + packages/webgal/src/store/stageInterface.ts | 1 + packages/webgal/src/store/stageReducer.ts | 1 + .../webgal/src/store/userDataInterface.ts | 1 + packages/webgal/src/store/userDataReducer.ts | 5 + 14 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 packages/webgal/src/Core/Modules/readHistory.ts diff --git a/packages/webgal/public/game/template/Stage/TextBox/textbox.scss b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss index b2bd03c01..b75eb2480 100644 --- a/packages/webgal/public/game/template/Stage/TextBox/textbox.scss +++ b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss @@ -176,3 +176,7 @@ .text { overflow: hidden; } + +.read { + color: rgb(237, 162, 162); +} diff --git a/packages/webgal/src/Core/Modules/backlog.ts b/packages/webgal/src/Core/Modules/backlog.ts index 8d9c9e8c8..e17571de9 100644 --- a/packages/webgal/src/Core/Modules/backlog.ts +++ b/packages/webgal/src/Core/Modules/backlog.ts @@ -42,6 +42,8 @@ export class BacklogManager { // 存一下 Backlog const currentStageState = webgalStore.getState().stage; const stageStateToBacklog = cloneDeep(currentStageState); + // 确保原先未读的文本在使用 backlog 时能正确显示为已读文本 + stageStateToBacklog.isRead = true; stageStateToBacklog.PerformList.forEach((ele) => { ele.script.args.forEach((argelement) => { if (argelement.key === 'concat') { diff --git a/packages/webgal/src/Core/Modules/readHistory.ts b/packages/webgal/src/Core/Modules/readHistory.ts new file mode 100644 index 000000000..03dc486d6 --- /dev/null +++ b/packages/webgal/src/Core/Modules/readHistory.ts @@ -0,0 +1,103 @@ +/** + * 已读历史记录 + */ + +import { webgalStore } from "@/store/store"; +import { SceneManager } from "./scene"; +import { setReadHistory } from "@/store/userDataReducer"; +import { setStage } from "@/store/stageReducer"; +import { setStorage } from "../controller/storage/storageController"; + +export class ReadHistoryManager { + private history: Map = new Map(); + + private load: boolean = false; + + private readonly sceneManager: SceneManager; + + public constructor(sceneManager: SceneManager) { + this.sceneManager = sceneManager; + } + + private loadReadHistory() { + const readHistory = webgalStore.getState().userData.readHistory; + + Object.entries(readHistory).forEach(([key, value]) => { + try { + const uint8 = Uint8Array.from(Buffer.from(value, 'base64')); + this.history.set(key, uint8); + } catch { + // 浏览器环境下没有 Buffer 时的兜底逻辑 + const binary = atob(value); + const uint8 = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + uint8[i] = binary.charCodeAt(i); + } + console.log('Camille Load', uint8); + this.history.set(key, uint8); + } + }); + + this.load = true; + } + + private checkLoad() { + if (!this.load) { + this.loadReadHistory(); + } + } + + private saveReadHistory(key: string) { + const bitset = this.history.get(key)!; + + try { + const base64 = Buffer.from(bitset).toString('base64'); + webgalStore.dispatch(setReadHistory({ + key, + value: base64, + })); + } catch { + // 浏览器环境下没有 Buffer 时的兜底逻辑 + const base64 = btoa(String.fromCharCode(...bitset)); + webgalStore.dispatch(setReadHistory({ + key, + value: base64, + })); + } + setStorage(); + } + + private addReadHistory() { + const scenarioName = this.sceneManager.sceneData.currentScene.sceneName; + const index = this.sceneManager.sceneData.currentSentenceId; + + if (!this.history.has(scenarioName)) { + const length = this.sceneManager.sceneData.currentScene.sentenceList.length; + this.history.set(scenarioName, new Uint8Array(Math.ceil(length / 8))); + } + const bitset = this.history.get(scenarioName)!; + bitset[index >> 3] |= (1 << (index & 7)); + + this.saveReadHistory(scenarioName); + } + + public checkIsReaded() { + this.checkLoad(); + + const scenarioName = this.sceneManager.sceneData.currentScene.sceneName; + const index = this.sceneManager.sceneData.currentSentenceId; + + let isReaded = false; + if (this.history.has(scenarioName)) { + const bitset = this.history.get(scenarioName)!; + isReaded = (bitset[index >> 3] & (1 << (index & 7))) !== 0; + } + webgalStore.dispatch(setStage({ + key: 'isRead', + value: isReaded, + })); + if (!isReaded) { + this.addReadHistory(); + } + } +} diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index 04f5c997b..ceeb5d2f9 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -100,6 +100,7 @@ export const scriptExecutor = () => { nextSentence(); return; } + WebGAL.readHistoryManager.checkIsReaded(); runScript(currentScript); // 是否要进行下一句 let isNext = getBooleanArgByKey(currentScript, 'next') ?? false; diff --git a/packages/webgal/src/Core/controller/stage/resetStage.ts b/packages/webgal/src/Core/controller/stage/resetStage.ts index b72573834..ffe1f7823 100644 --- a/packages/webgal/src/Core/controller/stage/resetStage.ts +++ b/packages/webgal/src/Core/controller/stage/resetStage.ts @@ -2,6 +2,7 @@ import { initState, resetStageState, setStage } from '@/store/stageReducer'; import { webgalStore } from '@/store/store'; import cloneDeep from 'lodash/cloneDeep'; import { WebGAL } from '@/Core/WebGAL'; +import { saveActions } from '@/store/savesReducer'; export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { /** diff --git a/packages/webgal/src/Core/webgalCore.ts b/packages/webgal/src/Core/webgalCore.ts index 98d1c53ec..71e85d770 100644 --- a/packages/webgal/src/Core/webgalCore.ts +++ b/packages/webgal/src/Core/webgalCore.ts @@ -1,4 +1,5 @@ import { BacklogManager } from '@/Core/Modules/backlog'; +import { ReadHistoryManager } from './Modules/readHistory'; import mitt from 'mitt'; import { SceneManager } from '@/Core/Modules/scene'; import { AnimationManager } from '@/Core/Modules/animations'; @@ -11,6 +12,7 @@ import { IWebGALStyleObj } from 'webgal-parser/build/types/styleParser'; export class WebgalCore { public sceneManager = new SceneManager(); public backlogManager = new BacklogManager(this.sceneManager); + public readHistoryManager = new ReadHistoryManager(this.sceneManager); public animationManager = new AnimationManager(); public gameplay = new Gameplay(); public gameName = ''; diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx index f603f2792..e3eec5350 100644 --- a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -14,6 +14,7 @@ export default function IMSSTextbox(props: ITextboxProps) { textDelay, currentConcatDialogPrev, currentDialogKey, + isRead, isText, isSafari, isFirefox: boolean, @@ -167,7 +168,7 @@ export default function IMSSTextbox(props: ITextboxProps) { > {e} - {e} + {e} {isUseStroke && {e}} diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index dea485b2d..f6c1d028c 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -27,6 +27,7 @@ export const TextBox = () => { const textDuration = useTextAnimationDuration(userDataState.optionData.textSpeed); let size = getTextSize(userDataState.optionData.textSize) + '%'; const font = useFontFamily(); + const isRead = stageState.isRead; const isText = stageState.showText !== '' || stageState.showName !== ''; let textSizeState = userDataState.optionData.textSize; if (isText && stageState.showTextSize !== -1) { @@ -88,6 +89,7 @@ export const TextBox = () => { return ( ; figureAssociatedAnimation: Array; + isRead: boolean; // 是否已读 showText: string; // 文字 showTextSize: number; // 文字 showName: string; // 人物名 diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index ac382f44a..c2f463221 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -35,6 +35,7 @@ export const initState: IStageState = { figNameRight: '', // 立绘_右 文件地址(相对或绝对) freeFigure: [], figureAssociatedAnimation: [], + isRead: false, showText: '', // 文字 showTextSize: -1, showName: '', // 人物名 diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts index 111b0e1c9..355651e27 100644 --- a/packages/webgal/src/store/userDataInterface.ts +++ b/packages/webgal/src/store/userDataInterface.ts @@ -91,6 +91,7 @@ export interface IUserData { optionData: IOptionData; // 用户设置选项数据 appreciationData: IAppreciation; gameConfigInit: IGameVar; + readHistory: Record; } export interface ISetUserDataPayload { diff --git a/packages/webgal/src/store/userDataReducer.ts b/packages/webgal/src/store/userDataReducer.ts index d7cd5fa9a..88e45d118 100644 --- a/packages/webgal/src/store/userDataReducer.ts +++ b/packages/webgal/src/store/userDataReducer.ts @@ -47,6 +47,7 @@ export const initState: IUserData = { cg: [], }, gameConfigInit: {}, + readHistory: {}, }; const userDataSlice = createSlice({ @@ -142,6 +143,9 @@ const userDataSlice = createSlice({ const { gameConfigInit } = state; Object.assign(state, { ...cloneDeep(initState), globalGameVar: cloneDeep(gameConfigInit), gameConfigInit }); }, + setReadHistory: (state, action: PayloadAction>) => { + state.readHistory[action.payload.key] = action.payload.value; + }, }, }); @@ -156,6 +160,7 @@ export const { unlockBgmInUserData, resetOptionSet, resetAllData, + setReadHistory, } = userDataSlice.actions; export default userDataSlice.reducer; From 916b57d97dce2c29df92224eb6233e8ece1fd000 Mon Sep 17 00:00:00 2001 From: ChangeSuger Date: Thu, 16 Apr 2026 01:37:44 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=B7=B2?= =?UTF-8?q?=E8=AF=BB=E5=BF=AB=E8=BF=9B=E4=B8=8E=E5=85=A8=E6=96=87=E5=BF=AB?= =?UTF-8?q?=E8=BF=9B=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webgal/src/Core/controller/gamePlay/fastSkip.ts | 7 ++++++- .../webgal/src/UI/Menu/Options/System/System.tsx | 13 +++++++++++++ packages/webgal/src/hooks/useHotkey.tsx | 3 ++- packages/webgal/src/store/userDataInterface.ts | 1 + packages/webgal/src/store/userDataReducer.ts | 1 + packages/webgal/src/translations/de.ts | 7 +++++++ packages/webgal/src/translations/en.ts | 7 +++++++ packages/webgal/src/translations/fr.ts | 7 +++++++ packages/webgal/src/translations/jp.ts | 7 +++++++ packages/webgal/src/translations/zh-cn.ts | 7 +++++++ packages/webgal/src/translations/zh-tw.ts | 7 +++++++ 11 files changed, 65 insertions(+), 2 deletions(-) diff --git a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts index 8798f3243..9e3fd2d1e 100644 --- a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts +++ b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts @@ -4,6 +4,7 @@ import styles from '@/UI/BottomControlPanel/bottomControlPanel.module.scss'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { WebGAL } from '@/Core/WebGAL'; +import { webgalStore } from "@/store/store"; import { SYSTEM_CONFIG } from '@/config'; /** @@ -36,12 +37,16 @@ export const stopFast = () => { /** * 开启快进 */ -export const startFast = () => { +export const startFast = (force = false) => { if (isFast()) { return; } WebGAL.gameplay.isFast = true; + const skipAll = force || webgalStore.getState().userData.optionData.skipAll; WebGAL.gameplay.fastInterval = setInterval(() => { + if (!skipAll && !webgalStore.getState().stage.isRead) { + stopFast(); + } nextSentence(); }, SYSTEM_CONFIG.fast_timeout); }; diff --git a/packages/webgal/src/UI/Menu/Options/System/System.tsx b/packages/webgal/src/UI/Menu/Options/System/System.tsx index 00333ee94..4b7a69580 100644 --- a/packages/webgal/src/UI/Menu/Options/System/System.tsx +++ b/packages/webgal/src/UI/Menu/Options/System/System.tsx @@ -116,6 +116,19 @@ export function System() { }} /> + + { + dispatch(setOptionData({ key: 'skipAll', value: false })); + setStorage(); + }, () => { + dispatch(setOptionData({ key: 'skipAll', value: true })); + setStorage(); + }]} + currentChecked={userDataState.optionData.skipAll ? 1 : 0} + /> + e.keyCode === 17, []); const handleCtrlKeydown = useCallback((e) => { if (isCtrlKey(e) && isGameActive()) { - startFast(); + // 按下 ctrl 键快进时,强制全文快进 + startFast(true); } }, []); const handleCtrlKeyup = useCallback((e) => { diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts index 355651e27..e0245c2a1 100644 --- a/packages/webgal/src/store/userDataInterface.ts +++ b/packages/webgal/src/store/userDataInterface.ts @@ -46,6 +46,7 @@ export interface IOptionData { language: language; voiceInterruption: voiceOption; // 是否中断语音 fullScreen: fullScreenOption; + skipAll: boolean; // 快进已读/快进全文 } /** diff --git a/packages/webgal/src/store/userDataReducer.ts b/packages/webgal/src/store/userDataReducer.ts index 88e45d118..856b74ef7 100644 --- a/packages/webgal/src/store/userDataReducer.ts +++ b/packages/webgal/src/store/userDataReducer.ts @@ -35,6 +35,7 @@ const initialOptionSet: IOptionData = { language: language.zhCn, voiceInterruption: voiceOption.no, fullScreen: fullScreenOption.off, + skipAll: false, }; // 初始化用户数据 diff --git a/packages/webgal/src/translations/de.ts b/packages/webgal/src/translations/de.ts index 905ce7828..a40875a38 100644 --- a/packages/webgal/src/translations/de.ts +++ b/packages/webgal/src/translations/de.ts @@ -58,6 +58,13 @@ const de = { contributors: 'Contributors', website: 'Website', }, + skipAll: { + title: 'Schnellvorlauf-Modus', + options: { + read: 'Gelesen', + all: 'Alle', + } + } }, }, display: { diff --git a/packages/webgal/src/translations/en.ts b/packages/webgal/src/translations/en.ts index 7b982ddcf..801e41296 100644 --- a/packages/webgal/src/translations/en.ts +++ b/packages/webgal/src/translations/en.ts @@ -58,6 +58,13 @@ const en = { contributors: 'Contributors', website: 'Website', }, + skipAll: { + title: 'Skip Mode', + options: { + read: 'Read', + all: 'All', + } + } }, }, display: { diff --git a/packages/webgal/src/translations/fr.ts b/packages/webgal/src/translations/fr.ts index 4c6dd0af1..faf3296dd 100644 --- a/packages/webgal/src/translations/fr.ts +++ b/packages/webgal/src/translations/fr.ts @@ -58,6 +58,13 @@ const fr = { contributors: 'Contributeurs', website: 'Site web', }, + skipAll: { + title: 'Mode Avance Rapide', + options: { + read: 'Lu', + all: 'Tout', + } + } }, }, display: { diff --git a/packages/webgal/src/translations/jp.ts b/packages/webgal/src/translations/jp.ts index bfefcea93..d54154471 100644 --- a/packages/webgal/src/translations/jp.ts +++ b/packages/webgal/src/translations/jp.ts @@ -58,6 +58,13 @@ const jp = { contributors: '貢献者', website: 'ウェブサイト', }, + skipAll: { + title: 'スキップモード', + options: { + read: '既読', + all: 'すべて', + } + } }, }, display: { diff --git a/packages/webgal/src/translations/zh-cn.ts b/packages/webgal/src/translations/zh-cn.ts index ec9cb0d71..8a178b558 100644 --- a/packages/webgal/src/translations/zh-cn.ts +++ b/packages/webgal/src/translations/zh-cn.ts @@ -58,6 +58,13 @@ const zhCn = { contributors: '贡献者', website: '网站', }, + skipAll: { + title: '快进模式', + options: { + read: '已读', + all: '全部', + } + } }, }, display: { diff --git a/packages/webgal/src/translations/zh-tw.ts b/packages/webgal/src/translations/zh-tw.ts index 184cf477b..0da6c9f43 100644 --- a/packages/webgal/src/translations/zh-tw.ts +++ b/packages/webgal/src/translations/zh-tw.ts @@ -58,6 +58,13 @@ const zhTw = { contributors: '貢獻者', website: '網站', }, + skipAll: { + title: '快進模式', + options: { + read: '已讀', + all: '全部', + } + } }, }, display: { From 527a810d4e26b8d7b67aaefccac08b451dca755b Mon Sep 17 00:00:00 2001 From: ChangeSuger Date: Thu, 16 Apr 2026 01:50:53 +0800 Subject: [PATCH 3/5] fix: typo --- packages/webgal/src/Core/Modules/readHistory.ts | 10 +++++----- .../src/Core/controller/gamePlay/scriptExecutor.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/webgal/src/Core/Modules/readHistory.ts b/packages/webgal/src/Core/Modules/readHistory.ts index 03dc486d6..f609cf79f 100644 --- a/packages/webgal/src/Core/Modules/readHistory.ts +++ b/packages/webgal/src/Core/Modules/readHistory.ts @@ -81,22 +81,22 @@ export class ReadHistoryManager { this.saveReadHistory(scenarioName); } - public checkIsReaded() { + public checkIsRead() { this.checkLoad(); const scenarioName = this.sceneManager.sceneData.currentScene.sceneName; const index = this.sceneManager.sceneData.currentSentenceId; - let isReaded = false; + let isRead = false; if (this.history.has(scenarioName)) { const bitset = this.history.get(scenarioName)!; - isReaded = (bitset[index >> 3] & (1 << (index & 7))) !== 0; + isRead = (bitset[index >> 3] & (1 << (index & 7))) !== 0; } webgalStore.dispatch(setStage({ key: 'isRead', - value: isReaded, + value: isRead, })); - if (!isReaded) { + if (!isRead) { this.addReadHistory(); } } diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index ceeb5d2f9..83b814684 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -100,7 +100,7 @@ export const scriptExecutor = () => { nextSentence(); return; } - WebGAL.readHistoryManager.checkIsReaded(); + WebGAL.readHistoryManager.checkIsRead(); runScript(currentScript); // 是否要进行下一句 let isNext = getBooleanArgByKey(currentScript, 'next') ?? false; From cf7dcb97e2f6872d7c8b17743bd8cce3b027d86d Mon Sep 17 00:00:00 2001 From: ChangeSuger Date: Fri, 17 Apr 2026 01:09:33 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20update=20-=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98=20-=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E8=84=9A=E6=9C=AC=E5=8F=98=E6=9B=B4=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=AF=BC=E8=87=B4=E7=9A=84=E7=B4=A2=E5=BC=95=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webgal/src/Core/Modules/readHistory.ts | 23 +++++++++++++++---- .../Menu/Options/TextPreview/TextPreview.tsx | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/webgal/src/Core/Modules/readHistory.ts b/packages/webgal/src/Core/Modules/readHistory.ts index f609cf79f..aa5336abb 100644 --- a/packages/webgal/src/Core/Modules/readHistory.ts +++ b/packages/webgal/src/Core/Modules/readHistory.ts @@ -33,7 +33,6 @@ export class ReadHistoryManager { for (let i = 0; i < binary.length; i++) { uint8[i] = binary.charCodeAt(i); } - console.log('Camille Load', uint8); this.history.set(key, uint8); } }); @@ -58,7 +57,12 @@ export class ReadHistoryManager { })); } catch { // 浏览器环境下没有 Buffer 时的兜底逻辑 - const base64 = btoa(String.fromCharCode(...bitset)); + let binary = ''; + const len = bitset.length; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bitset[i]); + } + const base64 = btoa(binary); webgalStore.dispatch(setReadHistory({ key, value: base64, @@ -75,8 +79,19 @@ export class ReadHistoryManager { const length = this.sceneManager.sceneData.currentScene.sentenceList.length; this.history.set(scenarioName, new Uint8Array(Math.ceil(length / 8))); } - const bitset = this.history.get(scenarioName)!; - bitset[index >> 3] |= (1 << (index & 7)); + let bitset = this.history.get(scenarioName)!; + + // 处理因剧本更新可能导致的 index 溢出问题 + const requiredIndex = index >> 3; + if (requiredIndex >= bitset.length) { + const length = this.sceneManager.sceneData.currentScene.sentenceList.length; + const newBitset = new Uint8Array(Math.ceil(length / 8)); + newBitset.set(bitset); + bitset = newBitset; + this.history.set(scenarioName, bitset); + } + + bitset[requiredIndex] |= (1 << (index & 7)); this.saveReadHistory(scenarioName); } diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx index 0a86b672e..1a40ccc2d 100644 --- a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx +++ b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx @@ -54,6 +54,7 @@ export const TextPreview = (props: any) => { lineLimit: 3, isUseStroke: true, textboxOpacity: textboxOpacity, + isRead: false, }; return ( From 3a6cce81db02b45b4d01317d1401ce790f60174b Mon Sep 17 00:00:00 2001 From: ChangeSuger Date: Fri, 17 Apr 2026 01:25:47 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20backlog=20?= =?UTF-8?q?=E4=B8=8E=20load=20=E6=97=B6=E8=83=BD=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E6=96=87=E6=9C=AC=E4=B8=BA=E5=B7=B2=E8=AF=BB?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webgal/src/Core/Modules/backlog.ts | 2 -- packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts | 3 +++ packages/webgal/src/Core/controller/storage/loadGame.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/webgal/src/Core/Modules/backlog.ts b/packages/webgal/src/Core/Modules/backlog.ts index e17571de9..8d9c9e8c8 100644 --- a/packages/webgal/src/Core/Modules/backlog.ts +++ b/packages/webgal/src/Core/Modules/backlog.ts @@ -42,8 +42,6 @@ export class BacklogManager { // 存一下 Backlog const currentStageState = webgalStore.getState().stage; const stageStateToBacklog = cloneDeep(currentStageState); - // 确保原先未读的文本在使用 backlog 时能正确显示为已读文本 - stageStateToBacklog.isRead = true; stageStateToBacklog.PerformList.forEach((ele) => { ele.script.args.forEach((argelement) => { if (argelement.key === 'concat') { diff --git a/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts index 2ebf4ce6b..562966e58 100644 --- a/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts +++ b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts @@ -67,6 +67,9 @@ export const jumpFromBacklog = (index: number, refetchScene = true) => { // 恢复舞台状态 const newStageState: IStageState = cloneDeep(backlogFile.currentStageState); + // 确保原先未读的文本在使用 backlog 时能正确显示为已读文本 + newStageState.isRead = true; + dispatch(resetStageState(newStageState)); // 恢复演出 diff --git a/packages/webgal/src/Core/controller/storage/loadGame.ts b/packages/webgal/src/Core/controller/storage/loadGame.ts index ae471ab7e..f0e8aa04b 100644 --- a/packages/webgal/src/Core/controller/storage/loadGame.ts +++ b/packages/webgal/src/Core/controller/storage/loadGame.ts @@ -61,6 +61,8 @@ export function loadGameFromStageData(stageData: ISaveData) { // 恢复舞台状态 const newStageState = cloneDeep(loadFile.nowStageState); + // 确保原先未读的文本在 load 时能正确显示为已读文本 + newStageState.isRead = true; const dispatch = webgalStore.dispatch; dispatch(resetStageState(newStageState));