diff --git a/package.json b/package.json index db4ebdd90..05dc8055b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@webgal/base", - "version": "4.6.2", + "version": "4.6.3", "description": "A brand new web Visual Novel engine.", "repository": "https://github.com/OpenWebGAL/WebGAL.git", "author": "Mahiru ", diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index ae4cdb4fd..c9e077d24 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,5 +1,32 @@ # WebGAL Parser Changelog +## [4.6.3] - 2026-08-01 + +正式发布 4.6.3-beta.1 / beta.2 的多行语句支持,另含以下改动。 + +### Added +- 新增 `return` 命令,用于从被 `callScene` 调用的场景提前返回 +- `sceneEntry` 新增 `locals` / `writeReturnTo`,承载 `callScene` 传入的局部变量与返回值写回目标 +- 语音参数识别 `.opus` 扩展名 + +## [4.6.3-beta.2] - 2026-07-31 + +### Fixed +- `sceneTextPreProcess`:折叠序列的首行是空行时(例如空行后面紧跟一条续行),整条序列会被静默丢弃,导致预处理后行数变少。这会击穿「解析后语句 index == 文件行号」,进而让存档进度错位。改用 `null` 哨兵区分「不在折叠序列中」与「首行内容为空串」 + +## [4.6.3-beta.1] - 2026-07-31 + +### Added +- 正式启用多行语句支持:`sceneParser` 现在会先经过 `sceneTextPreProcess`,把以空白开头且首字符为 `-` 或 `|` 的续行折叠进上一条语句 +- `ISentence` 新增行范围字段 `startLine` / `endLine`(0-based,含首尾)与 `isLineBreakHolder`,供图形编辑器按行范围整体替换语句 +- 导出 `WEBGAL_LINE_BREAK_MARK` 与 `isLineBreakPlaceholder` + +### Fixed +- `sceneTextPreProcess`:移除逐行 `console.log`;场景首行不再被误判为续行;场景末行以 `\` 结尾时不再丢失整段累积内容 + +### Technical Notes +- 续行会被替换成 `;_WEBGAL_LINE_BREAK_` 占位注释行,**预处理前后行数严格一致**。因此「解析后语句 index == 文件行号」这一不变量继续成立,引擎的存读档、Backlog、场景栈、已读记录(都存语句 index)无需改动,旧存档也不会错位 + ## [4.5.13] - 2025-07-05 ### Fixed diff --git a/packages/parser/package.json b/packages/parser/package.json index 480e75678..2c86b3b4f 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "webgal-parser", - "version": "4.6.2", + "version": "4.6.3", "description": "WebGAL script parser", "scripts": { "test": "vitest", diff --git a/packages/parser/src/config/scriptConfig.ts b/packages/parser/src/config/scriptConfig.ts index 1e1db77e9..f9c5a3620 100644 --- a/packages/parser/src/config/scriptConfig.ts +++ b/packages/parser/src/config/scriptConfig.ts @@ -39,6 +39,7 @@ export const SCRIPT_CONFIG = [ { scriptString: 'applyStyle', scriptType: commandType.applyStyle }, { scriptString: 'wait', scriptType: commandType.wait }, { scriptString: 'callSteam', scriptType: commandType.callSteam }, + { scriptString: 'return', scriptType: commandType.return }, ]; export const ADD_NEXT_ARG_LIST = [ commandType.bgm, diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 030e1bc7b..454fc8018 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -9,7 +9,11 @@ import { fileType } from './interface/assets'; import { IAsset } from './interface/sceneInterface'; import { sceneParser } from './sceneParser'; import { IWebGALStyleObj, scss2cssinjsParser } from "./styleParser"; -import { sceneTextPreProcess } from "./sceneTextPreProcessor"; +import { + isLineBreakPlaceholder, + sceneTextPreProcess, + WEBGAL_LINE_BREAK_MARK, +} from "./sceneTextPreProcessor"; export default class SceneParser { private readonly SCRIPT_CONFIG_MAP: ConfigMap; @@ -77,4 +81,4 @@ export default class SceneParser { } export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG }; -export { sceneTextPreProcess }; +export { sceneTextPreProcess, isLineBreakPlaceholder, WEBGAL_LINE_BREAK_MARK }; diff --git a/packages/parser/src/interface/runtimeInterface.ts b/packages/parser/src/interface/runtimeInterface.ts index 981bd5754..841a02c43 100644 --- a/packages/parser/src/interface/runtimeInterface.ts +++ b/packages/parser/src/interface/runtimeInterface.ts @@ -6,6 +6,8 @@ export interface sceneEntry { sceneName: string; // 场景名称 sceneUrl: string; // 场景url continueLine: number; // 继续原场景的行号 + locals?: Record; // 该帧的局部变量 + writeReturnTo?: string; // 返回值写回该帧的哪个变量 } /** diff --git a/packages/parser/src/interface/sceneInterface.ts b/packages/parser/src/interface/sceneInterface.ts index 1b05024c8..657ea53ae 100644 --- a/packages/parser/src/interface/sceneInterface.ts +++ b/packages/parser/src/interface/sceneInterface.ts @@ -40,6 +40,7 @@ export enum commandType { applyStyle, wait, callSteam, // 调用Steam功能 + return, // 从被调用的场景返回 } /** @@ -74,6 +75,15 @@ export interface ISentence { sentenceAssets: Array; // 语句携带的资源列表 subScene: Array; // 语句包含子场景列表 inlineComment: string; // 行内注释 + /** + * 本语句在原始场景文件中占据的行范围(0-based,含首尾)。 + * 单行语句 startLine === endLine;多行语句 endLine 指向最后一条续行。 + * 图形编辑器据此做「只替换被编辑语句所占的行」。 + */ + startLine: number; + endLine: number; + /** 本条是多行语句被折叠掉的续行占位,仅用于补齐行数,不应展示给用户 */ + isLineBreakHolder: boolean; } /** diff --git a/packages/parser/src/sceneParser.ts b/packages/parser/src/sceneParser.ts index 7a8ff5814..32d0ae08f 100644 --- a/packages/parser/src/sceneParser.ts +++ b/packages/parser/src/sceneParser.ts @@ -7,6 +7,10 @@ import { import { scriptParser } from './scriptParser/scriptParser'; import { fileType } from './interface/assets'; import { ConfigMap } from './config/scriptConfig'; +import { + isLineBreakPlaceholder, + sceneTextPreProcess, +} from './sceneTextPreProcessor'; /** * 场景解析器 @@ -28,7 +32,9 @@ export const sceneParser = ( ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, ): IScene => { - const rawSentenceList = rawScene.replaceAll('\r', '').split('\n'); // 原始句子列表 + // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行, + // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。 + const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表 // 去分号留到后面去做了,现在注释要单独处理 const rawSentenceListWithoutEmpty = rawSentenceList; @@ -52,6 +58,8 @@ export const sceneParser = ( }, ); + markMultilineRanges(sentenceList, rawSentenceList); + // 开始资源的预加载 assetsList = deduplicateAssets(assetsList); assetsPrefetcher(assetsList); @@ -65,6 +73,33 @@ export const sceneParser = ( }; }; +/** + * 回填多行语句的行范围。 + * + * 预处理后,一条多行语句的完整内容都落在它的首行,紧跟其后的若干占位行 + * 就是被折叠掉的续行。这里把占位行标记出来,并把首行语句的 endLine + * 推到最后一条续行,图形编辑器据此按整个行范围替换语句。 + * + * @param sentenceList 解析后的语句列表 + * @param processedLines 预处理后的行,与 sentenceList 一一对应 + */ +const markMultilineRanges = ( + sentenceList: ISentence[], + processedLines: string[], +) => { + let headIndex = -1; // 最近一条非占位语句的下标,即当前多行语句的首行 + for (let index = 0; index < sentenceList.length; index++) { + if (!isLineBreakPlaceholder(processedLines[index])) { + headIndex = index; + continue; + } + sentenceList[index].isLineBreakHolder = true; + if (headIndex >= 0) { + sentenceList[headIndex].endLine = index; + } + } +}; + const deduplicateAssets = (assetsList: IAsset[]): IAsset[] => { const seenAssets = new Set(); return assetsList.filter((asset) => { diff --git a/packages/parser/src/sceneTextPreProcessor.ts b/packages/parser/src/sceneTextPreProcessor.ts index 244c2c91b..d356cfcfc 100644 --- a/packages/parser/src/sceneTextPreProcessor.ts +++ b/packages/parser/src/sceneTextPreProcessor.ts @@ -31,15 +31,11 @@ function sceneTextPreProcessPassOne(lines: string[]): string[] { let thisLineIsMultiline = false; for (const line of lines) { - thisLineIsMultiline = false; - - if (canBeMultiline(line)) { - thisLineIsMultiline = true; - } - - if (shouldNotBeMultiline(line, lastLineIsMultiline)) { - thisLineIsMultiline = false; - } + // 续行需要接到上一行,所以场景的第一行永远不会是续行。 + thisLineIsMultiline = + processedLines.length > 0 && + canBeMultiline(line) && + !shouldNotBeMultiline(line, lastLineIsMultiline); if (thisLineIsMultiline) { processedLines[processedLines.length - 1] += '\\'; @@ -98,7 +94,10 @@ function isEmptyLine(line: string): boolean { */ function sceneTextPreProcessPassTwo(lines: string[]): string[] { const processedLines: string[] = []; - let currentMultilineContent = ""; + // null 表示当前不在多行序列里。这里不能用空串来判断: + // 序列的首行本身可能就是空行(比如空行后面紧跟着一条续行), + // 那样整条序列会被静默丢弃,破坏「预处理前后行数一致」这个前提。 + let currentMultilineContent: string | null = null; let placeHolderLines: string[] = []; function concat(line: string) { @@ -110,12 +109,18 @@ function sceneTextPreProcessPassTwo(lines: string[]): string[] { placeHolderLines.push(placeholderLine(line)); } + /** 把折叠好的整条语句连同补齐行数的占位行一起输出 */ + function flushMultiline() { + processedLines.push(currentMultilineContent as string, ...placeHolderLines); + placeHolderLines = []; + currentMultilineContent = null; + } + for (const line of lines) { - console.log(line); if (line.endsWith('\\')) { const trueLine = line.slice(0, -1); - if (currentMultilineContent === "") { + if (currentMultilineContent === null) { // first line currentMultilineContent = trueLine; } else { @@ -125,23 +130,38 @@ function sceneTextPreProcessPassTwo(lines: string[]): string[] { continue; } - if (currentMultilineContent !== "") { + if (currentMultilineContent !== null) { // end line concat(line); - processedLines.push(currentMultilineContent); - processedLines.push(...placeHolderLines); - - placeHolderLines = []; - currentMultilineContent = ""; + flushMultiline(); continue; } processedLines.push(line); } + // 场景末行仍以 \ 结尾时循环里没有机会收尾,在这里把累积的内容补出去。 + if (currentMultilineContent !== null) { + flushMultiline(); + } + return processedLines; } +/** + * 占位行的前缀。占位行本身是一条注释,引擎会空跑掉; + * 它的作用是让预处理后的行数与原始场景一致,从而保持 + * 「解析后语句 index == 文件行号」这一不变量。 + */ +export const WEBGAL_LINE_BREAK_MARK = ';_WEBGAL_LINE_BREAK_'; + +/** + * 判断预处理后的某一行是否是占位行,即它是上一条多行语句折叠掉的续行。 + */ +export function isLineBreakPlaceholder(processedLine: string): boolean { + return processedLine.startsWith(WEBGAL_LINE_BREAK_MARK); +} + /** * Placeholder Line. Adding this line preserves the original number of lines * in the scene text, so that it can be compatible with the graphical editor. @@ -150,74 +170,5 @@ function sceneTextPreProcessPassTwo(lines: string[]): string[] { * @returns The placeholder line */ function placeholderLine(content = "") { - return ";_WEBGAL_LINE_BREAK_" + content; + return WEBGAL_LINE_BREAK_MARK + content; } - -// export function sceneTextPreProcess(sceneText: string): string { -// const lines = sceneText.replaceAll('\r', '').split('\n'); -// const processedLines: string[] = []; -// let lastNonMultilineIndex = -1; -// let isInMultilineSequence = false; - -// function isMultiline(line: string): boolean { -// if (!line.startsWith(' ')) return false; -// const trimmedLine = line.trimStart(); -// return trimmedLine.startsWith('|') || trimmedLine.startsWith('-'); -// } - -// for (let i = 0; i < lines.length; i++) { -// const line = lines[i]; - -// if (line.trim() === '') { -// // Empty line handling -// if (isInMultilineSequence) { -// // Check if the next line is a multiline line - -// let isStillInMulti = false; -// for (let j = i + 1; j < lines.length; j++) { -// const lookForwardLine = lines[j] || ''; -// // 遇到正常语句了,直接中断 -// if (lookForwardLine.trim() !== '' && !isMultiline(lookForwardLine)) { -// isStillInMulti = false; -// break; -// } -// // 必须找到后面接的是参数,并且中间没有遇到任何正常语句才行 -// if (lookForwardLine.trim() !== '' && isMultiline(lookForwardLine)) { -// isStillInMulti = true; -// break; -// } -// } -// if (isStillInMulti) { -// // Still within a multiline sequence -// processedLines.push(';_WEBGAL_LINE_BREAK_'); -// } else { -// // End of multiline sequence -// isInMultilineSequence = false; -// processedLines.push(line); -// } -// } else { -// // Preserve empty lines outside of multiline sequences -// processedLines.push(line); -// } -// } else if (isMultiline(line)) { -// // Multiline statement handling -// if (lastNonMultilineIndex >= 0) { -// // Concatenate to the previous non-multiline statement -// const trimedLine = line.trimStart(); -// const addBlank = trimedLine.startsWith('-') ? ' ' : ''; -// processedLines[lastNonMultilineIndex] += addBlank + trimedLine; -// } - -// // Add the special comment line -// processedLines.push(';_WEBGAL_LINE_BREAK_' + line); -// isInMultilineSequence = true; -// } else { -// // Non-multiline statement handling -// processedLines.push(line); -// lastNonMultilineIndex = processedLines.length - 1; -// isInMultilineSequence = false; -// } -// } - -// return processedLines.join('\n'); -// } \ No newline at end of file diff --git a/packages/parser/src/scriptParser/argsParser.ts b/packages/parser/src/scriptParser/argsParser.ts index c9b998d0e..26328ebc7 100644 --- a/packages/parser/src/scriptParser/argsParser.ts +++ b/packages/parser/src/scriptParser/argsParser.ts @@ -30,7 +30,7 @@ export function argsParser( argValue = undefined; } // 判断是不是语音参数 - if (argName.toLowerCase().match(/.ogg|.mp3|.wav/)) { + if (argName.toLowerCase().match(/.ogg|.mp3|.wav|.opus/)) { returnArrayList.push({ key: 'vocal', value: assetSetter(e, fileType.vocal), diff --git a/packages/parser/src/scriptParser/scriptParser.ts b/packages/parser/src/scriptParser/scriptParser.ts index 0fd07d93e..dc2acd526 100644 --- a/packages/parser/src/scriptParser/scriptParser.ts +++ b/packages/parser/src/scriptParser/scriptParser.ts @@ -51,6 +51,9 @@ export const scriptParser = ( sentenceAssets: [], // 语句携带的资源列表 subScene: [], // 语句携带的子场景 inlineComment: '', // 行内注释 + startLine: lineNumber, + endLine: lineNumber, + isLineBreakHolder: false, }; } // 截取命令 @@ -116,5 +119,9 @@ export const scriptParser = ( sentenceAssets: sentenceAssets, // 语句携带的资源列表 subScene: subScene, // 语句携带的子场景 inlineComment: sentenceComment.trim(), // 行内注释 + // 单行语句的行范围就是自己,多行语句的 endLine 由 sceneParser 回填 + startLine: lineNumber, + endLine: lineNumber, + isLineBreakHolder: false, }; }; diff --git a/packages/parser/test/parser.test.ts b/packages/parser/test/parser.test.ts index d32c8222b..26c205807 100644 --- a/packages/parser/test/parser.test.ts +++ b/packages/parser/test/parser.test.ts @@ -5,6 +5,18 @@ import { commandType, IAsset, ISentence } from "../src/interface/sceneInterface" import * as fsp from 'fs/promises'; import { fileType } from "../src/interface/assets"; +/** + * 行范围(startLine / endLine / isLineBreakHolder)由 sceneParser 按行号回填, + * 与下面各用例关心的解析结果无关,因此断言时统一剥掉。 + * 行范围本身由 parserMultiline.test.ts 专门覆盖。 + */ +type SentenceWithoutLineRange = Omit; + +const dropLineRange = ({ startLine, endLine, isLineBreakHolder, ...rest }: ISentence): SentenceWithoutLineRange => rest; + +const expectSentenceIn = (sentenceList: ISentence[], expected: SentenceWithoutLineRange) => + expect(sentenceList.map(dropLineRange)).toContainEqual(expected); + test("label", async () => { const sceneRaw = await fsp.readFile('test/test-resources/start.txt'); @@ -16,7 +28,7 @@ test("label", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(sceneText, "start", "/start.txt"); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.label, commandRaw: "label", content: "end", @@ -27,7 +39,7 @@ test("label", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("args", async () => { @@ -41,7 +53,7 @@ test("args", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(sceneText, "start", "/start.txt"); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.changeFigure, commandRaw: "changeFigure", content: "m2.png", @@ -53,7 +65,7 @@ test("args", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("choose", async () => { @@ -67,7 +79,7 @@ test("choose", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(sceneText, "choose", "/choose.txt"); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.choose, commandRaw: "choose", content: "", @@ -76,7 +88,7 @@ test("choose", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("long-script", async () => { @@ -93,7 +105,7 @@ test("long-script", async () => { console.time('parse-time-consumed'); const result = parser.parse(sceneText, "start", "/start.txt"); console.timeEnd('parse-time-consumed'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.label, commandRaw: "label", content: "end", @@ -104,7 +116,7 @@ test("long-script", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("var", async () => { @@ -118,7 +130,7 @@ test("var", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(sceneText, "var", "/var.txt"); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.say, commandRaw: "WebGAL", content: "a=1?", @@ -127,7 +139,7 @@ test("var", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("config", async () => { @@ -190,7 +202,7 @@ test("say statement", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`say:123 -speaker=xx;`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.say, commandRaw: "say", content: "123", @@ -199,7 +211,7 @@ test("say statement", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("say statement applies asset setter to vocal named argument", async () => { @@ -223,6 +235,29 @@ test("say statement applies asset setter to vocal named argument", async () => { }); }); +test("audio commands accept opus resources", async () => { + const parser = new SceneParser((assetList) => { + }, (fileName, assetType) => { + return `./game/${assetType === fileType.bgm ? 'bgm' : 'vocal'}/${fileName}`; + }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); + + const result = parser.parse(`say:123 -a.opus; +bgm:track.opus; +playEffect:se.opus; +unlockBgm:extra.opus;`, 'test', 'test'); + + expect(result.sentenceList[0].args).toContainEqual({ key: 'vocal', value: './game/vocal/a.opus' }); + expect(result.sentenceList[0].sentenceAssets).toContainEqual({ + name: './game/vocal/a.opus', + url: './game/vocal/a.opus', + type: fileType.vocal, + lineNumber: 0, + }); + expect(result.sentenceList[1].content).toBe('./game/bgm/track.opus'); + expect(result.sentenceList[2].content).toBe('./game/vocal/se.opus'); + expect(result.sentenceList[3].content).toBe('./game/bgm/extra.opus'); +}); + test("scene assets are deduplicated by type and url", async () => { let prefetchedAssets: IAsset[] = []; const parser = new SceneParser((assetList) => { @@ -263,7 +298,7 @@ test("wait command", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`wait:1000;`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.wait, commandRaw: "wait", content: "1000", @@ -272,7 +307,7 @@ test("wait command", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("changeFigure with duration and animation args", async () => { @@ -282,7 +317,7 @@ test("changeFigure with duration and animation args", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`changeFigure:stand.webp -duration=1000 -enter=fadeIn -exit=fadeOut;`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.changeFigure, commandRaw: "changeFigure", content: "stand.webp", @@ -295,7 +330,7 @@ test("changeFigure with duration and animation args", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("changeBg with animation parameters", async () => { @@ -305,7 +340,7 @@ test("changeBg with animation parameters", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`changeBg:background.jpg -duration=2000 -enter=slideIn -transform={"alpha":0.8};`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.changeBg, commandRaw: "changeBg", content: "background.jpg", @@ -318,7 +353,7 @@ test("changeBg with animation parameters", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("inline comment is preserved on normal statement", async () => { @@ -328,7 +363,7 @@ test("inline comment is preserved on normal statement", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`say:123 -speaker=xx; // this is an inline comment`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.say, commandRaw: "say", content: "123", @@ -337,7 +372,7 @@ test("inline comment is preserved on normal statement", async () => { subScene: [], inlineComment: "// this is an inline comment" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("escaped semicolon is preserved in content and inline comment is preserved", async () => { @@ -347,7 +382,7 @@ test("escaped semicolon is preserved in content and inline comment is preserved" }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(String.raw`say:price\;100;comment-part`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.say, commandRaw: "say", content: "price;100", @@ -356,7 +391,7 @@ test("escaped semicolon is preserved in content and inline comment is preserved" subScene: [], inlineComment: "comment-part" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("inline comment preserves following semicolons", async () => { @@ -366,7 +401,7 @@ test("inline comment preserves following semicolons", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`say:123; first; second; third`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.say, commandRaw: "say", content: "123", @@ -375,7 +410,7 @@ test("inline comment preserves following semicolons", async () => { subScene: [], inlineComment: "first; second; third" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("comment-only line keeps comment in content", async () => { @@ -385,7 +420,7 @@ test("comment-only line keeps comment in content", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`; only comment here`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.comment, commandRaw: "comment", content: "only comment here", @@ -394,7 +429,7 @@ test("comment-only line keeps comment in content", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); test("comment-only line preserves following semicolons", async () => { @@ -404,7 +439,7 @@ test("comment-only line preserves following semicolons", async () => { }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); const result = parser.parse(`; first; second; third`, 'test', 'test'); - const expectSentenceItem: ISentence = { + const expectSentenceItem: SentenceWithoutLineRange = { command: commandType.comment, commandRaw: "comment", content: "first; second; third", @@ -413,5 +448,5 @@ test("comment-only line preserves following semicolons", async () => { subScene: [], inlineComment: "" }; - expect(result.sentenceList).toContainEqual(expectSentenceItem); + expectSentenceIn(result.sentenceList, expectSentenceItem); }); diff --git a/packages/parser/test/parserMultiline.test.ts b/packages/parser/test/parserMultiline.test.ts index 378b02c1c..2a1703b3e 100644 --- a/packages/parser/test/parserMultiline.test.ts +++ b/packages/parser/test/parserMultiline.test.ts @@ -1,5 +1,10 @@ import { sceneTextPreProcess } from "../src/sceneTextPreProcessor"; +import SceneParser from "../src/index"; +import { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG } from "../src/config/scriptConfig"; import { expect, test } from "vitest"; +import * as fsp from 'fs/promises'; + +const parser = new SceneParser(() => { }, (fileName) => fileName, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); test("parser-multiline-basic", async () => { const testScene = `changeFigure:a.png -left @@ -146,3 +151,53 @@ WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游 const preprocessedScene = sceneTextPreProcess(testScene); expect(preprocessedScene).toEqual(expected); }); + +test("parser-multiline-marks-line-range", async () => { + const testScene = `changeFigure:a.png -left + -next + -id=id1 +saySomething`; + + const { sentenceList } = parser.parse(testScene, 'test', 'test'); + + // 折叠后的参数全部落在首行语句上 + expect(sentenceList[0].args).toEqual([ + { key: 'left', value: true }, + { key: 'next', value: true }, + { key: 'id', value: 'id1' }, + ]); + // 首行语句的行范围覆盖被折叠掉的两条续行 + expect(sentenceList[0]).toMatchObject({ startLine: 0, endLine: 2, isLineBreakHolder: false }); + expect(sentenceList[1]).toMatchObject({ startLine: 1, endLine: 1, isLineBreakHolder: true }); + expect(sentenceList[2]).toMatchObject({ startLine: 2, endLine: 2, isLineBreakHolder: true }); + // 续行之后的语句回到单行 + expect(sentenceList[3]).toMatchObject({ startLine: 3, endLine: 3, isLineBreakHolder: false }); +}); + +/** + * 行数守恒是存档兼容性的护栏:引擎的 currentSentenceId / continueLine 存的是 + * 语句 index,只要「语句数 == 文件行数」成立,多行支持就不会让旧存档错位。 + */ +test("parser-multiline-preserves-line-count", async () => { + const sceneText = (await fsp.readFile('test/test-resources/long-script.txt')) + .toString() + .replaceAll('\r', ''); + + const { sentenceList } = parser.parse(sceneText, 'test', 'test'); + + expect(sentenceList.length).toBe(sceneText.split('\n').length); +}); + +/** + * 折叠序列的首行是空行时,曾经整条序列会被丢掉,导致预处理后行数变少、 + * 「语句 index == 文件行号」失效,进而让存档错位。 + */ +test("parser-multiline-empty-first-line-keeps-line-count", async () => { + const testScene = `A; + + -next +B;`; + + expect(sceneTextPreProcess(testScene).split('\n')).toHaveLength(4); + expect(parser.parse(testScene, 'test', 'test').sentenceList).toHaveLength(4); +}); diff --git a/packages/webgal/.eslintignore b/packages/webgal/.eslintignore deleted file mode 100644 index 0bdc31f75..000000000 --- a/packages/webgal/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -*.md -*.css -*.scss -*.otf -*.svg -*.ttf -*.ico -*.mp3 -*.wav -*.txt diff --git a/packages/webgal/.eslintrc.js b/packages/webgal/.eslintrc.js deleted file mode 100644 index ba9e148db..000000000 --- a/packages/webgal/.eslintrc.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - extends: ['alloy', 'alloy/react', 'alloy/typescript', 'plugin:prettier/recommended'], - env: { - // 你的环境变量(包含多个预定义的全局变量) - // - // browser: true, - // node: true, - // mocha: true, - // jest: true, - // jquery: true - }, - globals: { - // 你的全局变量(设置为 false 表示它不允许被重新赋值) - // - // myGlobal: false - }, - rules: { - // 自定义你的规则 - // 最大圈复杂度 - complexity: ['error', 30], - 'linebreak-style': ['error', 'unix'], - semi: 2, - // indent: ['error', 2], - 'semi-style': ['error', 'last'], - 'react/jsx-no-useless-fragment': [ - 'error', - { - allowExpressions: true, - }, - ], - }, -}; diff --git a/packages/webgal/eslint.config.js b/packages/webgal/eslint.config.js new file mode 100644 index 000000000..7863b2e34 --- /dev/null +++ b/packages/webgal/eslint.config.js @@ -0,0 +1,62 @@ +const js = require('@eslint/js'); +const { defineConfig } = require('eslint/config'); +const prettierConfig = require('eslint-config-prettier'); +const prettier = require('eslint-plugin-prettier'); +const react = require('eslint-plugin-react'); +const globals = require('globals'); +const tseslint = require('typescript-eslint'); + +module.exports = defineConfig([ + { + ignores: ['**/*.{css,ico,md,mp3,otf,scss,svg,ttf,txt,wav}'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + react.configs.flat.recommended, + react.configs.flat['jsx-runtime'], + { + files: ['src/**/*.{ts,tsx}'], + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, + languageOptions: { + globals: globals.browser, + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + complexity: ['error', 30], + 'linebreak-style': ['error', 'unix'], + 'no-prototype-builtins': 'off', + 'no-useless-escape': 'off', + 'prefer-const': 'off', + semi: 'error', + 'semi-style': ['error', 'last'], + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/no-unused-expressions': [ + 'error', + { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true }, + ], + '@typescript-eslint/no-unused-vars': 'off', + 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], + }, + }, + prettierConfig, + { + files: ['src/**/*.{ts,tsx}'], + plugins: { + prettier, + }, + rules: { + 'arrow-body-style': 'off', + 'prefer-arrow-callback': 'off', + 'prettier/prettier': 'error', + }, + }, +]); diff --git a/packages/webgal/package.json b/packages/webgal/package.json index f5ffbbc95..b9df98064 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -1,11 +1,11 @@ { "name": "webgal-engine", - "version": "4.6.2", + "version": "4.6.3", "scripts": { "dev": "vite --host --port 3000", "build": "node scripts/update-engine-version.js && cross-env NODE_ENV=production tsc && vite build --base=./", "preview": "vite preview", - "lint": "eslint src/** --fix", + "lint": "eslint \"src/**/*.{ts,tsx}\" --fix", "prepublishOnly": "npm run build" }, "dependencies": { @@ -39,19 +39,19 @@ "@types/react": "^17.0.33", "@types/react-dom": "^17.0.10", "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", + "@eslint/js": "^9.39.5", "@vitejs/plugin-react": "^4.0.4", "cross-env": "^7.0.3", - "eslint": "^9.26.0", - "eslint-config-alloy": "^4.5.1", + "eslint": "^9.39.5", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react": "^7.37.5", + "globals": "^17.7.0", "lint-staged": "^12.3.7", "prettier": "^2.6.2", "rollup-plugin-visualizer": "^5.6.0", - "typescript": "^4.5.4", + "typescript": "^4.9.5", + "typescript-eslint": "^8.65.0", "unplugin-info": "^1.2.2", "vite": "^5.4.21", "vite-plugin-compression": "^0.5.1" diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json index 981384f04..cb8bbbc9b 100644 --- a/packages/webgal/public/game/template/template.json +++ b/packages/webgal/public/game/template/template.json @@ -1,4 +1,4 @@ { "name":"WebGAL Refine 2026", - "webgal-version":"4.6.2" + "webgal-version":"4.6.3" } diff --git a/packages/webgal/public/webgal-engine.json b/packages/webgal/public/webgal-engine.json index 7cdd18a14..4668fda4f 100644 --- a/packages/webgal/public/webgal-engine.json +++ b/packages/webgal/public/webgal-engine.json @@ -2,9 +2,9 @@ "schemaVersion": "1.0.0", "id": "open-webgal.webgal", "name": "WebGAL", - "version": "4.6.2", + "version": "4.6.3", "type": "official", - "webgalVersion": "4.6.2", + "webgalVersion": "4.6.3", "description": "界面美观、功能强大、易于开发的全新网页端视觉小说引擎", "descriptions": { "en": "A brand new web Visual Novel engine with a beautiful interface, powerful features, and easy development", diff --git a/packages/webgal/src/Core/Modules/animationFunctions.ts b/packages/webgal/src/Core/Modules/animationFunctions.ts index 1ba79e854..e46dd5b9e 100644 --- a/packages/webgal/src/Core/Modules/animationFunctions.ts +++ b/packages/webgal/src/Core/Modules/animationFunctions.ts @@ -1,4 +1,3 @@ -import { generateUniversalSoftInAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftIn'; import { logger } from '@/Core/util/logger'; import { generateUniversalSoftOffAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftOff'; import cloneDeep from 'lodash/cloneDeep'; @@ -8,12 +7,7 @@ import { WebGAL } from '@/Core/WebGAL'; import PixiStage, { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController'; import { IUserAnimation } from './animations'; import { pickBy } from 'lodash'; -import { - DEFAULT_BG_IN_DURATION, - DEFAULT_BG_OUT_DURATION, - DEFAULT_FIG_IN_DURATION, - DEFAULT_FIG_OUT_DURATION, -} from '../constants'; +import { DEFAULT_BG_OUT_DURATION, DEFAULT_FIG_OUT_DURATION } from '../constants'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; import { AnimationFrame } from '@/Core/Modules/animations'; @@ -24,11 +18,10 @@ export function getAnimationObject( duration: number, writeDefault: boolean, writeFullEffect = true, - syncEndStateToStageState = true, ) { const mappedEffects = getAnimationTimeline(animationName, target, writeDefault, writeFullEffect); if (mappedEffects) { - return generateTimelineObj(mappedEffects, target, duration, syncEndStateToStageState); + return generateTimelineObj(mappedEffects, target, duration); } return null; } @@ -104,73 +97,43 @@ export function getAnimateDuration(animationName: string) { return 0; } -// eslint-disable-next-line max-params -export function getEnterExitAnimation( +/** + * 取退出动画。 + * + * 入场动画不在这里产出:它由 changeFigure/changeBg 作为普通演出返回, + * 终态在演算期写入 effects,因此不需要视图层反推该播哪个动画。 + */ +export function getExitAnimation( target: string, - type: 'enter' | 'exit', isBg = false, realTarget?: string, // 用于立绘和背景移除时,以当前时间打上特殊标记 ): { duration: number; animation: IAnimationObject | null; } { - if (type === 'enter') { - let duration = DEFAULT_FIG_IN_DURATION; - if (isBg) { - duration = DEFAULT_BG_IN_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 = animationSettings?.enterAnimationName; - if (animationName && !targetEffect) { - logger.debug('取代默认进入动画', target); - animation = getAnimationObject( - animationName, - realTarget ?? target, - getAnimateDuration(animationName), - false, - !(animationSettings?.enterAnimationIgnoreDefault ?? false), - ); - duration = getAnimateDuration(animationName); - } - return { duration, animation }; - } else { - // exit - let duration = DEFAULT_FIG_OUT_DURATION; - if (isBg) { - duration = DEFAULT_BG_OUT_DURATION; - } - const animationSettings = stageStateManager - .getCalculationStageState() - .animationSettings.find((setting) => setting.target === target); - duration = animationSettings?.exitDuration ?? duration; - // 走默认动画 - let animation: IAnimationObject | null = generateUniversalSoftOffAnimationObj(realTarget ?? target, duration); - const animationName = animationSettings?.exitAnimationName; - if (animationName) { - logger.debug('取代默认退出动画', target); - animation = getAnimationObject( - animationName, - realTarget ?? target, - getAnimateDuration(animationName), - false, - !(animationSettings?.exitAnimationIgnoreDefault ?? false), - ); - duration = getAnimateDuration(animationName); - } - if (animationSettings) { - // 退出动画拿完后,删了这个设定 - stageStateManager.removeAnimationSettingsByTargetOff(target); - logger.debug('删除退出动画设定', target); - } - return { duration, animation }; + let duration = isBg ? DEFAULT_BG_OUT_DURATION : DEFAULT_FIG_OUT_DURATION; + const animationSettings = stageStateManager + .getCalculationStageState() + .animationSettings.find((setting) => setting.target === target); + duration = animationSettings?.exitDuration ?? duration; + // 走默认动画 + let animation: IAnimationObject | null = generateUniversalSoftOffAnimationObj(realTarget ?? target, duration); + const animationName = animationSettings?.exitAnimationName; + if (animationName) { + logger.debug('取代默认退出动画', target); + animation = getAnimationObject( + animationName, + realTarget ?? target, + getAnimateDuration(animationName), + false, + !(animationSettings?.exitAnimationIgnoreDefault ?? false), + ); + duration = getAnimateDuration(animationName); + } + if (animationSettings) { + // 退出动画拿完后,删了这个设定 + stageStateManager.removeAnimationSettingsByTargetOff(target); + logger.debug('删除退出动画设定', target); } + return { duration, animation }; } diff --git a/packages/webgal/src/Core/Modules/backlog.ts b/packages/webgal/src/Core/Modules/backlog.ts index 349e84829..ac24fddf8 100644 --- a/packages/webgal/src/Core/Modules/backlog.ts +++ b/packages/webgal/src/Core/Modules/backlog.ts @@ -1,7 +1,7 @@ /** * 当前的backlog */ -import { IEffect, IStageState } from '@/Core/Modules/stage/stageInterface'; +import { IStageState } from '@/Core/Modules/stage/stageInterface'; import { ISaveScene } from '@/store/userDataInterface'; import cloneDeep from 'lodash/cloneDeep'; @@ -28,10 +28,6 @@ export class BacklogManager { return this.backlog; } - public editLastBacklogItemEffect(effects: IEffect[]) { - this.backlog[this.backlog.length - 1].currentStageState.effects = effects; - } - public makeBacklogEmpty() { this.backlog.splice(0, this.backlog.length); // 清空backlog } @@ -57,6 +53,7 @@ export class BacklogManager { sceneStack: cloneDeep(this.sceneManager.sceneData.sceneStack), // 场景栈 sceneName: this.sceneManager.sceneData.currentScene.sceneName, // 场景名称 sceneUrl: this.sceneManager.sceneData.currentScene.sceneUrl, // 场景url + currentLocals: cloneDeep(this.sceneManager.sceneData.currentLocals), // 当前帧的局部变量 }, }; this.getBacklog().push(backlogElement); diff --git a/packages/webgal/src/Core/Modules/flowchart.ts b/packages/webgal/src/Core/Modules/flowchart.ts index aece2596e..3726438f4 100644 --- a/packages/webgal/src/Core/Modules/flowchart.ts +++ b/packages/webgal/src/Core/Modules/flowchart.ts @@ -75,6 +75,14 @@ export class FlowchartManager { return this.data.flowcharts; } + /** 个人线在玩家踏入之前属于剧透,主线之外的流程图要解锁到任意节点才对玩家可见 */ + public getVisibleFlowcharts() { + return this.data.flowcharts.filter( + (flowchart) => + flowchart.type === 'main' || flowchart.nodes.some((node) => this.isUnlocked(flowchart.id, node.id)), + ); + } + public getEventName() { return FLOWCHART_UPDATED_EVENT; } diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts index a9ba6969b..6531a3397 100644 --- a/packages/webgal/src/Core/Modules/perform/performController.ts +++ b/packages/webgal/src/Core/Modules/perform/performController.ts @@ -94,16 +94,8 @@ export class PerformController { }); } - public discardUncommittedNonHoldPerforms(settleDiscardedState = false) { - this.pendingPerformList = this.pendingPerformList.filter(({ perform }) => { - if (perform.isHoldOn) { - return true; - } - if (settleDiscardedState) { - perform.settleStateOnDiscard?.(); - } - return false; - }); + public discardUncommittedNonHoldPerforms() { + this.pendingPerformList = this.pendingPerformList.filter(({ perform }) => perform.isHoldOn); } public hasPendingBlockingStateCalculationPerform() { diff --git a/packages/webgal/src/Core/Modules/perform/performInterface.ts b/packages/webgal/src/Core/Modules/perform/performInterface.ts index 5e635e407..858cc4c97 100644 --- a/packages/webgal/src/Core/Modules/perform/performInterface.ts +++ b/packages/webgal/src/Core/Modules/perform/performInterface.ts @@ -23,8 +23,6 @@ export interface IPerform { blockingAuto: () => boolean; // 是否阻塞同一轮 -next 继续演算;只有需要外部输入才能确定后续状态的演出需要覆盖。 blockingStateCalculation?: () => boolean; - // 未 commit 的演出被丢弃时,将它的终态同步到演算状态 - settleStateOnDiscard?: () => void; // 演出自然结束或被卸载后触发内部继续推进;推进前仍会等待 blockingNext 解除。 goNextWhenOver?: boolean; // 跳过由 nextSentence/continueSentence 引发的非 hold 演出回收。 diff --git a/packages/webgal/src/Core/Modules/readHistory.ts b/packages/webgal/src/Core/Modules/readHistory.ts index 35faaf078..d344381cf 100644 --- a/packages/webgal/src/Core/Modules/readHistory.ts +++ b/packages/webgal/src/Core/Modules/readHistory.ts @@ -2,10 +2,10 @@ * 已读历史记录 */ -import { webgalStore } from "@/store/store"; -import { SceneManager } from "./scene"; -import { setReadHistory } from "@/store/userDataReducer"; -import { setStorage } from "../controller/storage/storageController"; +import { webgalStore } from '@/store/store'; +import { SceneManager } from './scene'; +import { setReadHistory } from '@/store/userDataReducer'; +import { setStorage } from '../controller/storage/storageController'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; let debugTextReadMode: boolean | null = null; @@ -60,10 +60,12 @@ export class ReadHistoryManager { try { const base64 = Buffer.from(bitset).toString('base64'); - webgalStore.dispatch(setReadHistory({ - key, - value: base64, - })); + webgalStore.dispatch( + setReadHistory({ + key, + value: base64, + }), + ); } catch { // 浏览器环境下没有 Buffer 时的兜底逻辑 let binary = ''; @@ -72,10 +74,12 @@ export class ReadHistoryManager { binary += String.fromCharCode(bitset[i]); } const base64 = btoa(binary); - webgalStore.dispatch(setReadHistory({ - key, - value: base64, - })); + webgalStore.dispatch( + setReadHistory({ + key, + value: base64, + }), + ); } setStorage(); } @@ -100,7 +104,7 @@ export class ReadHistoryManager { this.history.set(scenarioName, bitset); } - bitset[requiredIndex] |= (1 << (index & 7)); + bitset[requiredIndex] |= 1 << (index & 7); this.saveReadHistory(scenarioName); } diff --git a/packages/webgal/src/Core/Modules/scene.ts b/packages/webgal/src/Core/Modules/scene.ts index 10c4f7bd4..cc217b23c 100644 --- a/packages/webgal/src/Core/Modules/scene.ts +++ b/packages/webgal/src/Core/Modules/scene.ts @@ -1,10 +1,18 @@ import { ISceneData } from '@/Core/controller/scene/sceneInterface'; +import { IGameVar } from '@/Core/Modules/stage/stageInterface'; import cloneDeep from 'lodash/cloneDeep'; +/** + * 场景栈的深度上限,防止 callScene 无限递归 + */ +export const MAX_SCENE_STACK_DEPTH = 64; + export interface ISceneEntry { sceneName: string; // 场景名称 sceneUrl: string; // 场景url continueLine: number; // 继续原场景的行号 + locals?: IGameVar; // 该帧的局部变量 + writeReturnTo?: string; // 返回值写回该帧的哪个变量 } /** @@ -21,6 +29,7 @@ export const initSceneData = { assetsList: [], // 资源列表 subSceneList: [], // 子场景列表 }, + currentLocals: {}, // 当前帧的局部变量 }; export class SceneManager { @@ -34,8 +43,37 @@ export class SceneManager { this.sceneData.currentSentenceId = 0; this.sceneData.sceneStack = []; this.sceneData.currentScene = cloneDeep(initSceneData.currentScene); + this.sceneData.currentLocals = {}; this.sceneWritePromise = null; this.settledScenes.clear(); this.settledAssets.clear(); } + + /** + * 压入一个调用帧。把当前帧(调用方)存进场景栈,并切换到被调用场景的局部变量。 + * 场景栈与 currentLocals 是同一个栈的两截,必须经由本方法与 popFrame 一起变更。 + * @param locals 被调用场景的局部变量 + * @param writeReturnTo 返回值写回当前帧的哪个变量 + */ + public pushFrame(locals: IGameVar, writeReturnTo?: string) { + this.sceneData.sceneStack.push({ + sceneName: this.sceneData.currentScene.sceneName, + sceneUrl: this.sceneData.currentScene.sceneUrl, + continueLine: this.sceneData.currentSentenceId, + locals: this.sceneData.currentLocals, + writeReturnTo, + }); + this.sceneData.currentLocals = locals; + } + + /** + * 弹出一个调用帧,并恢复调用方的局部变量。栈为空时返回 undefined。 + */ + public popFrame(): ISceneEntry | undefined { + const entry = this.sceneData.sceneStack.pop(); + if (entry) { + this.sceneData.currentLocals = entry.locals ?? {}; // 旧存档的栈条目没有 locals + } + return entry; + } } diff --git a/packages/webgal/src/Core/Modules/stage/stageInterface.ts b/packages/webgal/src/Core/Modules/stage/stageInterface.ts index 217362291..a19b5e15e 100644 --- a/packages/webgal/src/Core/Modules/stage/stageInterface.ts +++ b/packages/webgal/src/Core/Modules/stage/stageInterface.ts @@ -97,8 +97,49 @@ export const baseTransform: ITransform = { radiusAlphaFilter: 0, }; +/** + * 立绘的预设位置,left / right 为靠边定位,其余均为中心定位 + */ +export const FIGURE_POSITIONS = ['center', 'left', 'right', 'left13', 'right13', 'left14', 'right14'] as const; + +export type IFigurePosition = (typeof FIGURE_POSITIONS)[number]; + +export const FIGURE_KEYS = FIGURE_POSITIONS.map((position) => `fig-${position}`); + +export const figureStateKeyByPosition = { + center: 'figName', + left: 'figNameLeft', + right: 'figNameRight', + left13: 'figNameLeft13', + right13: 'figNameRight13', + left14: 'figNameLeft14', + right14: 'figNameRight14', +} as const satisfies Record; + +/** + * 计算立绘的基准 X 坐标 + */ +export function getFigureBaseX(position: IFigurePosition, stageWidth: number, targetWidth: number): number { + switch (position) { + case 'left': + return targetWidth / 2; + case 'right': + return stageWidth - targetWidth / 2; + case 'left13': + return stageWidth / 3; + case 'right13': + return (stageWidth * 2) / 3; + case 'left14': + return stageWidth / 4; + case 'right14': + return (stageWidth * 3) / 4; + default: + return stageWidth / 2; + } +} + export interface IFreeFigure { - basePosition: 'left' | 'center' | 'right'; + basePosition: IFigurePosition; name: string; key: string; } @@ -169,6 +210,10 @@ export interface IStageState { figName: string; // 立绘_中 文件地址(相对或绝对) figNameLeft: string; // 立绘_左 文件地址(相对或绝对) figNameRight: string; // 立绘_右 文件地址(相对或绝对) + figNameLeft13: string; // 立绘_左 1/3 文件地址(相对或绝对) + figNameRight13: string; // 立绘_右 1/3 文件地址(相对或绝对) + figNameLeft14: string; // 立绘_左 1/4 文件地址(相对或绝对) + figNameRight14: string; // 立绘_右 1/4 文件地址(相对或绝对) // 自由立绘 freeFigure: Array; figureAssociatedAnimation: Array; diff --git a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts index b963d794c..8ce8b35a6 100644 --- a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts +++ b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts @@ -5,6 +5,7 @@ import { STAGE_KEYS } from '@/Core/constants'; import { baseBlinkParam, baseFocusParam } from '@/Core/live2DCore'; import { baseTransform, + FIGURE_KEYS, IEffect, IFigureMetadata, IFreeFigure, @@ -41,6 +42,10 @@ export const initState: IStageState = { figName: '', figNameLeft: '', figNameRight: '', + figNameLeft13: '', + figNameRight13: '', + figNameLeft14: '', + figNameRight14: '', freeFigure: [], figureAssociatedAnimation: [], isRead: false, @@ -143,9 +148,7 @@ export class StageStateManager { const activeTargets = [ STAGE_KEYS.STAGE_MAIN, STAGE_KEYS.BGMAIN, - STAGE_KEYS.FIG_C, - STAGE_KEYS.FIG_L, - STAGE_KEYS.FIG_R, + ...FIGURE_KEYS, ...state.freeFigure.map((figure) => figure.key), ]; if (!activeTargets.includes(target)) return; diff --git a/packages/webgal/src/Core/constants.ts b/packages/webgal/src/Core/constants.ts index c3c692857..a24cb2848 100644 --- a/packages/webgal/src/Core/constants.ts +++ b/packages/webgal/src/Core/constants.ts @@ -1,9 +1,6 @@ export const STAGE_KEYS = { STAGE_MAIN: 'stage-main', BGMAIN: 'bg-main', - FIG_C: 'fig-center', - FIG_L: 'fig-left', - FIG_R: 'fig-right', }; export const WEBGAL_NONE = 'none'; diff --git a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts index dd02b65a8..f593e5bea 100644 --- a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts +++ b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts @@ -4,7 +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 { webgalStore } from '@/store/store'; import { SYSTEM_CONFIG } from '@/config'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; diff --git a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts index 52fcbd89c..6d00bfc56 100644 --- a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts +++ b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts @@ -60,7 +60,7 @@ export const forward = (options: ForwardOptions = {}) => { return false; } - WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(WebGAL.gameplay.isFastPreview); + WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(); WebGAL.gameplay.performController.clearNonHoldPerformsFromStageState(); WebGAL.gameplay.performController.beginCollectingPerforms(); try { diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index 718e4a4e8..5bda07e97 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -1,12 +1,11 @@ import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface'; import { runScript } from './runScript'; import { logger } from '../../util/logger'; -import { restoreScene } from '../scene/restoreScene'; +import { returnFromScene } from '../scene/returnFromScene'; import { webgalStore } from '@/store/store'; import { getValueFromStateElseKey } from '@/Core/gameScripts/setVar'; import { strIf } from '@/Core/controller/gamePlay/strIf'; import cloneDeep from 'lodash/cloneDeep'; -import { ISceneEntry } from '@/Core/Modules/scene'; import { WebGAL } from '@/Core/WebGAL'; import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; @@ -61,12 +60,8 @@ export const scriptExecutor = (depth = 0, options: ScriptExecutionOptions = {}) WebGAL.sceneManager.sceneData.currentSentenceId > WebGAL.sceneManager.sceneData.currentScene.sentenceList.length - 1 ) { - if (WebGAL.sceneManager.sceneData.sceneStack.length !== 0 && !WebGAL.sceneManager.lockSceneWrite) { - const sceneToRestore: ISceneEntry | undefined = WebGAL.sceneManager.sceneData.sceneStack.pop(); - if (sceneToRestore !== undefined) { - restoreScene(sceneToRestore); - } - } + // 没有 return 语句而自然结束,返回空值 + returnFromScene(); return; } const sentenceId = WebGAL.sceneManager.sceneData.currentSentenceId; @@ -74,9 +69,7 @@ export const scriptExecutor = (depth = 0, options: ScriptExecutionOptions = {}) sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, sentenceId, }); - const currentScript: ISentence = cloneDeep( - WebGAL.sceneManager.sceneData.currentScene.sentenceList[sentenceId], - ); + const currentScript: ISentence = cloneDeep(WebGAL.sceneManager.sceneData.currentScene.sentenceList[sentenceId]); const interpolationOneItem = (content: string): string => { let retContent = content; diff --git a/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts b/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts index 5211023fb..75c6d340c 100644 --- a/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts +++ b/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts @@ -35,7 +35,7 @@ export async function continueGame() { * 重设模糊背景 */ setEbg(stageStateManager.getViewStageState().bgName); - if ((await hasFastSaveRecord())) { + if (await hasFastSaveRecord()) { webgalStore.dispatch(setVisibility({ component: 'showTitle', visibility: false })); // 恢复记录 await loadFastSaveGame(); diff --git a/packages/webgal/src/Core/controller/scene/callScene.ts b/packages/webgal/src/Core/controller/scene/callScene.ts index 47d66f0b6..26045c25a 100644 --- a/packages/webgal/src/Core/controller/scene/callScene.ts +++ b/packages/webgal/src/Core/controller/scene/callScene.ts @@ -5,25 +5,29 @@ import { continueSentence } from '@/Core/controller/gamePlay/nextSentence'; import { clearPrefetchLinks } from '@/Core/util/prefetcher/assetsPrefetcher'; import { WebGAL } from '@/Core/WebGAL'; +import { IGameVar } from '@/Core/Modules/stage/stageInterface'; +import { MAX_SCENE_STACK_DEPTH } from '@/Core/Modules/scene'; /** * 调用场景 * @param sceneUrl 场景路径 * @param sceneName 场景名称 + * @param locals 传入被调用场景的局部变量 + * @param writeReturnTo 返回值写回本场景的哪个变量 */ -export const callScene = (sceneUrl: string, sceneName: string) => { +export const callScene = (sceneUrl: string, sceneName: string, locals: IGameVar = {}, writeReturnTo?: string) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } + if (WebGAL.sceneManager.sceneData.sceneStack.length >= MAX_SCENE_STACK_DEPTH) { + logger.error(`场景调用层数超过 ${MAX_SCENE_STACK_DEPTH},可能存在 callScene 无限递归`, sceneUrl); + return; + } WebGAL.sceneManager.lockSceneWrite = true; const isFastPreviewSceneWrite = WebGAL.gameplay.isFastPreview; let shouldAutoNext = false; // 先将本场景压入场景栈 - WebGAL.sceneManager.sceneData.sceneStack.push({ - sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, - sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl, - continueLine: WebGAL.sceneManager.sceneData.currentSentenceId, - }); + WebGAL.sceneManager.pushFrame(locals, writeReturnTo); // 场景写入到运行时 const sceneWritePromise = sceneFetcher(sceneUrl) .then((rawScene) => { @@ -36,6 +40,8 @@ export const callScene = (sceneUrl: string, sceneName: string) => { shouldAutoNext = !isFastPreviewSceneWrite; }) .catch((e) => { + // 场景没写进来,之前压入的帧要弹回去,否则调用方会带着被调用方的局部变量继续跑 + WebGAL.sceneManager.popFrame(); logger.error('场景调用错误', e); }) .finally(() => { diff --git a/packages/webgal/src/Core/controller/scene/returnFromScene.ts b/packages/webgal/src/Core/controller/scene/returnFromScene.ts new file mode 100644 index 000000000..bcf00b7e6 --- /dev/null +++ b/packages/webgal/src/Core/controller/scene/returnFromScene.ts @@ -0,0 +1,24 @@ +import { restoreScene } from './restoreScene'; +import { setGameVar } from '@/Core/gameScripts/setVar'; + +import { WebGAL } from '@/Core/WebGAL'; + +/** + * 从被调用的场景返回:弹出调用帧,把返回值写回调用方,再恢复调用方场景。 + * 场景栈为空(顶层场景)或场景正在写入时不做任何事。 + * @param returnValue 返回值,没有 return 语句而自然结束时为空值 + */ +export const returnFromScene = (returnValue: string | boolean | number = '') => { + // 必须在弹栈之前判定,否则 restoreScene 提前返回会丢掉这一帧 + if (WebGAL.sceneManager.lockSceneWrite) { + return; + } + const entry = WebGAL.sceneManager.popFrame(); + if (!entry) { + return; + } + if (entry.writeReturnTo) { + setGameVar({ key: entry.writeReturnTo, value: returnValue }); + } + restoreScene(entry); +}; diff --git a/packages/webgal/src/Core/controller/scene/sceneInterface.ts b/packages/webgal/src/Core/controller/scene/sceneInterface.ts index 3e29d0ee2..ed9489aea 100644 --- a/packages/webgal/src/Core/controller/scene/sceneInterface.ts +++ b/packages/webgal/src/Core/controller/scene/sceneInterface.ts @@ -3,6 +3,7 @@ */ import { fileType } from '@/Core/util/gameAssetsAccess/assetSetter'; import { ISceneEntry } from '@/Core/Modules/scene'; +import { IGameVar } from '@/Core/Modules/stage/stageInterface'; export enum commandType { say, // 对话 @@ -40,6 +41,7 @@ export enum commandType { applyStyle, wait, callSteam, // 调用Steam功能 + return, // 从被调用的场景返回 } /** @@ -74,6 +76,8 @@ export interface ISentence { sentenceAssets: Array; // 语句携带的资源列表 subScene: Array; // 语句包含子场景列表 inlineComment: string; // 行内注释 + /** 本条是多行语句被折叠掉的续行占位,仅用于补齐行数,不是一条可停靠的语句 */ + isLineBreakHolder: boolean; } /** @@ -96,6 +100,7 @@ export interface ISceneData { currentSentenceId: number; // 当前语句ID sceneStack: Array; // 场景栈 currentScene: IScene; // 当前场景数据 + currentLocals: IGameVar; // 当前帧的局部变量 } /** diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts index da395ffcd..c7bd11f75 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts @@ -1,5 +1,11 @@ -import { IEffect, IFigureAssociatedAnimation, IFigureMetadata, ITransform } from '@/Core/Modules/stage/stageInterface'; -import { Live2D, WebGAL } from '@/Core/WebGAL'; +import { + getFigureBaseX, + IFigureAssociatedAnimation, + IFigureMetadata, + IFigurePosition, + ITransform, +} from '@/Core/Modules/stage/stageInterface'; +import { Live2D } from '@/Core/WebGAL'; import { baseBlinkParam, baseFocusParam, BlinkParam, FocusParam } from '@/Core/live2DCore'; import { isIOS } from '@/Core/initializeScript'; import { WebGALPixiContainer } from '@/Core/controller/stage/pixi/WebGALPixiContainer'; @@ -24,12 +30,9 @@ export interface IAnimationObject { } interface IStageAnimationObject { - // 唯一标识 - uuid: string; // 一般与作用目标有关 key: string; targetKey?: string; - type: 'common' | 'preset'; animationObject: IAnimationObject; } @@ -79,7 +82,6 @@ export default class PixiStage { public readonly mainStageContainer: WebGALPixiContainer; public readonly foregroundEffectsContainer: PIXI.Container; public readonly backgroundEffectsContainer: PIXI.Container; - public notUpdateBacklogEffects = false; public readonly figureContainer: PIXI.Container; public figureObjects = this.createReactiveList([]); public stageWidth = SCREEN_CONSTANTS.width; @@ -219,52 +221,13 @@ export default class PixiStage { */ public registerAnimation(animationObject: IAnimationObject | null, key: string, target = 'default') { if (!animationObject) return; - this.stageAnimations.push({ uuid: uuid(), animationObject, key: key, targetKey: target, type: 'common' }); + this.stageAnimations.push({ animationObject, key: key, targetKey: target }); // 上锁 this.lockStageObject(target); animationObject.setStartState(); this.currentApp?.ticker.add(animationObject.tickerFunc); } - /** - * 注册预设动画 - * @param animationObject - * @param key - * @param target - * @param currentEffects - */ - // eslint-disable-next-line max-params - public registerPresetAnimation( - animationObject: IAnimationObject | null, - key: string, - target = 'default', - currentEffects: IEffect[], - ) { - if (!animationObject) return; - const effect = currentEffects.find((effect) => effect.target === target); - if (effect) { - const targetPixiContainer = this.getStageObjByKey(target); - if (targetPixiContainer) { - const container = targetPixiContainer.pixiContainer; - if (container) PixiStage.assignTransform(container, effect.transform); - } - this.requestRender(); - return; - } - this.stageAnimations.push({ uuid: uuid(), animationObject, key: key, targetKey: target, type: 'preset' }); - // 上锁 - this.lockStageObject(target); - animationObject.setStartState(); - this.currentApp?.ticker.add(animationObject.tickerFunc); - } - - public stopPresetAnimationOnTarget(target: string) { - const targetPresetAnimations = this.stageAnimations.find((e) => e.targetKey === target && e.type === 'preset'); - if (targetPresetAnimations) { - this.removeAnimation(targetPresetAnimations.key); - } - } - /** * 移除动画 * @param key @@ -311,29 +274,6 @@ export default class PixiStage { } } - public removeAnimationWithSetEffects(key: string) { - const index = this.stageAnimations.findIndex((e) => e.key === key); - if (index >= 0) { - const thisTickerFunc = this.stageAnimations[index]; - this.currentApp?.ticker.remove(thisTickerFunc.animationObject.tickerFunc); - thisTickerFunc.animationObject.setEndState(); - const endStateEffect = thisTickerFunc.animationObject.getEndStateEffect?.() ?? {}; - this.unlockStageObject(thisTickerFunc.targetKey ?? 'default'); - if (thisTickerFunc.targetKey) { - const target = this.getStageObjByKey(thisTickerFunc.targetKey); - if (target) { - let effect: IEffect = { - target: thisTickerFunc.targetKey, - transform: endStateEffect, - }; - stageStateManager.updateEffect(effect); - // if (!this.notUpdateBacklogEffects) updateCurrentBacklogEffects(stageStateManager.getViewStageState().effects); - } - } - this.stageAnimations.splice(index, 1); - } - } - // eslint-disable-next-line max-params public performMouthSyncAnimation( key: string, @@ -563,7 +503,7 @@ export default class PixiStage { * @param url 立绘图片url * @param presetPosition */ - public addFigure(key: string, url: string, presetPosition: 'left' | 'center' | 'right' = 'center') { + public addFigure(key: string, url: string, presetPosition: IFigurePosition = 'center') { const loader = this.assetLoader; // 准备用于存放这个立绘的 Container const thisFigureContainer = new WebGALPixiContainer(); @@ -623,15 +563,7 @@ export default class PixiStage { if (targetHeight < this.stageHeight) { thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - if (presetPosition === 'center') { - thisFigureContainer.setBaseX(this.stageWidth / 2); - } - if (presetPosition === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } - if (presetPosition === 'right') { - thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, this.stageHeight / 2); thisFigureContainer.addChild(figureSprite); this.notifyTargetReferenceBoxChanged(key); @@ -657,7 +589,7 @@ export default class PixiStage { * @param jsonPath */ // eslint-disable-next-line max-params - public addLive2dFigure(key: string, jsonPath: string, pos: string) { + public addLive2dFigure(key: string, jsonPath: string, pos: IFigurePosition) { if (Live2D.isAvailable !== true) return; try { let stageWidth = this.stageWidth; @@ -741,13 +673,7 @@ export default class PixiStage { baseY = stageHeight / 2 + (stageHeight - targetHeight) / 2; } thisFigureContainer.setBaseY(baseY); - if (pos === 'center') { - thisFigureContainer.setBaseX(stageWidth / 2); - } else if (pos === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } else if (pos === 'right') { - thisFigureContainer.setBaseX(stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(pos, stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, stageHeight / 2); @@ -1279,14 +1205,3 @@ export default class PixiStage { }); } } - -function updateCurrentBacklogEffects(newEffects: IEffect[]) { - /** - * 更新当前 backlog 条目的 effects 记录 - */ - setTimeout(() => { - WebGAL.backlogManager.editLastBacklogItemEffect(cloneDeep(newEffects)); - }, 50); - - stageStateManager.setStageAndCommit('effects', newEffects); -} diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts index 9ae4c4c23..716c0ad41 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts @@ -14,13 +14,13 @@ export function generateTemplateAnimationObj(targetKey: string, duration: number /** * 在此书写为动画设置初态的操作 */ - function setStartState() { } + function setStartState() {} // TODO:通用终态设置 /** * 在此书写为动画设置终态的操作 */ - function setEndState() { } + function setEndState() {} /** * 在此书写动画每一帧执行的函数 diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts index 2e1e9b9b3..8c3b4890f 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts @@ -5,7 +5,6 @@ import omitBy from 'lodash/omitBy'; import isUndefined from 'lodash/isUndefined'; import PixiStage, { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController'; import { AnimationFrame } from '@/Core/Modules/animations'; -import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; /** * 动画创建模板 @@ -17,7 +16,6 @@ export function generateTimelineObj( timeline: Array, targetKey: string, duration: number, - syncEndStateToStageState = true, ): IAnimationObject { const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey); let currentDelay = 0; @@ -63,11 +61,6 @@ export function generateTimelineObj( }); } - if (syncEndStateToStageState) { - const { duration: sliceDuration, ease, ...endState } = getEndStateEffect(); - stageStateManager.updateEffect({ target: targetKey, transform: endState }); - } - /** * 在此书写为动画设置初态的操作 */ diff --git a/packages/webgal/src/Core/controller/stage/pixi/spine.ts b/packages/webgal/src/Core/controller/stage/pixi/spine.ts index 22cd07a2a..f1f34aa80 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/spine.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/spine.ts @@ -5,6 +5,7 @@ import * as PIXI from 'pixi.js'; import PixiStage from '@/Core/controller/stage/pixi/PixiController'; import { logger } from '@/Core/util/logger'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; +import { getFigureBaseX, IFigurePosition } from '@/Core/Modules/stage/stageInterface'; // utils/loadPixiSpine.ts // @ts-ignore let pixiSpineModule: typeof import('pixi-spine') | null = null; @@ -56,7 +57,7 @@ export async function addSpineFigureImpl( this: PixiStage, key: string, url: string, - presetPosition: 'left' | 'center' | 'right' = 'center', + presetPosition: IFigurePosition = 'center', ) { const spineId = `spine-${url}`; // 准备用于存放这个立绘的 Container @@ -161,15 +162,7 @@ export async function addSpineFigureImpl( if (targetHeight < this.stageHeight) { thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - if (presetPosition === 'center') { - thisFigureContainer.setBaseX(this.stageWidth / 2); - } - if (presetPosition === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } - if (presetPosition === 'right') { - thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, this.stageHeight / 2); thisFigureContainer.addChild(figureSprite); this.notifyTargetReferenceBoxChanged(key); diff --git a/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts b/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts index 4046064bc..d93f1e196 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts @@ -1,9 +1,10 @@ -import type { IEffect, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import type { IEffect, IFigurePosition, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import { FIGURE_KEYS, FIGURE_POSITIONS, figureStateKeyByPosition } from '@/Core/Modules/stage/stageInterface'; import type { IResolvedStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; -import { DEFAULT_BG_OUT_DURATION } from '@/Core/constants'; +import { DEFAULT_BG_IN_DURATION, DEFAULT_BG_OUT_DURATION, DEFAULT_FIG_IN_DURATION } from '@/Core/constants'; import { WebGAL } from '@/Core/WebGAL'; import type { IStageObject } from '@/Core/controller/stage/pixi/PixiController'; -import { getEnterExitAnimation } from '@/Core/Modules/animationFunctions'; +import { getAnimateDuration, getExitAnimation } from '@/Core/Modules/animationFunctions'; import { logger } from '@/Core/util/logger'; import { setEbg } from '@/Core/gameScripts/changeBg/setEbg'; import { applyTransformToPixiContainer } from '@/Core/controller/stage/pixi/stageEffectTransform'; @@ -11,14 +12,21 @@ import { applyTransformToPixiContainer } from '@/Core/controller/stage/pixi/stag interface ISyncFigureSlotPayload { key: string; sourceUrl: string; - position: 'left' | 'center' | 'right'; - stageState: IStageState; + position: IFigurePosition; skipAnimation: boolean; } -interface IRemoveFigOptions { - effects: IEffect[]; - skipAnimation: boolean; +/** + * 取入场过渡时长。 + * + * 入场动画本身由 changeBg/changeFigure 作为演出产出,这里只需要时长来同步其他视觉元素。 + */ +function getEnterDuration(stageState: IStageState, target: string, isBg: boolean): number { + const animationSettings = stageState.animationSettings.find((setting) => setting.target === target); + if (animationSettings?.enterAnimationName) { + return getAnimateDuration(animationSettings.enterAnimationName); + } + return animationSettings?.enterDuration ?? (isBg ? DEFAULT_BG_IN_DURATION : DEFAULT_FIG_IN_DURATION); } export function syncPixiStageState(stageState: IStageState, options: IResolvedStageCommitOptions) { @@ -75,14 +83,8 @@ function syncBg(stageState: IStageState, skipAnimation: boolean) { } addBg(thisBgKey, bgName); logger.debug('重设背景'); - const { duration, animation } = getEnterExitAnimation(thisBgKey, 'enter', true); - if (skipAnimation || WebGAL.gameplay.skipAnimation) { - setEbg(bgName, 0); - } else { - setEbg(bgName, duration); - pixiStage.registerPresetAnimation(animation, 'bg-main-softin', thisBgKey, stageState.effects); - setTimeout(() => pixiStage.removeAnimationWithSetEffects('bg-main-softin'), duration); - } + const isSkipAnimation = skipAnimation || WebGAL.gameplay.skipAnimation; + setEbg(bgName, isSkipAnimation ? 0 : getEnterDuration(stageState, thisBgKey, true)); return; } @@ -92,61 +94,52 @@ function syncBg(stageState: IStageState, skipAnimation: boolean) { } function syncFigures(stageState: IStageState, skipAnimation: boolean) { - syncFigureSlot({ key: 'fig-center', sourceUrl: stageState.figName, position: 'center', stageState, skipAnimation }); - syncFigureSlot({ key: 'fig-left', sourceUrl: stageState.figNameLeft, position: 'left', stageState, skipAnimation }); - syncFigureSlot({ - key: 'fig-right', - sourceUrl: stageState.figNameRight, - position: 'right', - stageState, - skipAnimation, - }); + for (const position of FIGURE_POSITIONS) { + syncFigureSlot({ + key: `fig-${position}`, + sourceUrl: stageState[figureStateKeyByPosition[position]], + position, + skipAnimation, + }); + } for (const fig of stageState.freeFigure) { - syncFigureSlot({ key: fig.key, sourceUrl: fig.name, position: fig.basePosition, stageState, skipAnimation }); + syncFigureSlot({ key: fig.key, sourceUrl: fig.name, position: fig.basePosition, skipAnimation }); } const currentFigures = WebGAL.gameplay.pixiStage?.getFigureObjects(); if (!currentFigures) return; const freeFigureKeys = new Set(stageState.freeFigure.map((fig) => fig.key)); for (const existFigure of [...currentFigures]) { - if ( - existFigure.key === 'fig-left' || - existFigure.key === 'fig-center' || - existFigure.key === 'fig-right' || - existFigure.key.endsWith('-off') - ) { + if (FIGURE_KEYS.includes(existFigure.key) || existFigure.key.endsWith('-off')) { continue; } if (!freeFigureKeys.has(existFigure.key)) { - removeFig(existFigure, `${existFigure.key}-softin`, { effects: stageState.effects, skipAnimation }); + removeFig(existFigure, `${existFigure.key}-softin`, skipAnimation); } } } -function syncFigureSlot({ key, sourceUrl, position, stageState, skipAnimation }: ISyncFigureSlotPayload) { +function syncFigureSlot({ key, sourceUrl, position, skipAnimation }: ISyncFigureSlotPayload) { const pixiStage = WebGAL.gameplay.pixiStage; if (!pixiStage) return; const softInAniKey = `${key}-softin`; const currentFigure = pixiStage.getStageObjByKey(key); - if (sourceUrl !== '') { + // 旧存档中可能没有新增位置的字段,这里同时容错 undefined + if (sourceUrl) { if (currentFigure?.sourceUrl === sourceUrl) return; if (currentFigure) { - removeFig(currentFigure, softInAniKey, { effects: stageState.effects, skipAnimation }); + removeFig(currentFigure, softInAniKey, skipAnimation); } + // 入场动画由 changeFigure 作为演出产出,这里只负责创建舞台对象 addFigure(key, sourceUrl, position); logger.debug(`${key} 立绘已重设`); - const { duration, animation } = getEnterExitAnimation(key, 'enter'); - if (!skipAnimation && !WebGAL.gameplay.skipAnimation) { - pixiStage.registerPresetAnimation(animation, softInAniKey, key, stageState.effects); - setTimeout(() => pixiStage.removeAnimationWithSetEffects(softInAniKey), duration); - } return; } if (currentFigure) { - removeFig(currentFigure, softInAniKey, { effects: stageState.effects, skipAnimation }); + removeFig(currentFigure, softInAniKey, skipAnimation); } } @@ -189,7 +182,7 @@ function syncFigureMetaData(stageState: IStageState) { function removeBg(bgObject: IStageObject, skipAnimation: boolean): number { const pixiStage = WebGAL.gameplay.pixiStage; if (!pixiStage) return DEFAULT_BG_OUT_DURATION; - pixiStage.removeAnimationWithSetEffects('bg-main-softin'); + pixiStage.removeAnimation('bg-main-softin'); if (skipAnimation || WebGAL.gameplay.skipAnimation) { pixiStage.removeStageObjectByKey(bgObject.key); return 0; @@ -199,7 +192,7 @@ function removeBg(bgObject: IStageObject, skipAnimation: boolean): number { const bgKey = bgObject.key; const bgAniKey = bgObject.key + '-softoff'; pixiStage.removeStageObjectByKey(oldBgKey); - const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true, bgKey); + const { duration, animation } = getExitAnimation('bg-main-off', true, bgKey); pixiStage.registerAnimation(animation, bgAniKey, bgKey); setTimeout(() => { pixiStage.removeAnimation(bgAniKey); @@ -208,11 +201,11 @@ function removeBg(bgObject: IStageObject, skipAnimation: boolean): number { return duration; } -function removeFig(figObj: IStageObject, enterTikerKey: string, options: IRemoveFigOptions) { +function removeFig(figObj: IStageObject, enterTikerKey: string, skipAnimation: boolean) { const pixiStage = WebGAL.gameplay.pixiStage; if (!pixiStage) return; - pixiStage.removeAnimationWithSetEffects(enterTikerKey); - if (options.skipAnimation || WebGAL.gameplay.skipAnimation) { + pixiStage.removeAnimation(enterTikerKey); + if (skipAnimation || WebGAL.gameplay.skipAnimation) { logger.debug('快速模式,立刻关闭立绘'); pixiStage.removeStageObjectByKey(figObj.key); return; @@ -223,8 +216,9 @@ function removeFig(figObj: IStageObject, enterTikerKey: string, options: IRemove const figKey = figObj.key; pixiStage.removeStageObjectByKey(oldFigKey); const leaveKey = figKey + '-softoff'; - const { duration, animation } = getEnterExitAnimation(figLeaveAniKey, 'exit', false, figKey); - pixiStage.registerPresetAnimation(animation, leaveKey, figKey, options.effects); + // 退出对象的 key 带时间戳,永远不在 effects 白名单里,因此与背景一样走普通动画通道即可 + const { duration, animation } = getExitAnimation(figLeaveAniKey, false, figKey); + pixiStage.registerAnimation(animation, leaveKey, figKey); setTimeout(() => { pixiStage.removeAnimation(leaveKey); pixiStage.removeStageObjectByKey(figKey); @@ -243,7 +237,7 @@ function addBg(key: string, url: string) { } } -function addFigure(key: string, url: string, position: 'left' | 'center' | 'right') { +function addFigure(key: string, url: string, position: IFigurePosition) { const pixiStage = WebGAL.gameplay.pixiStage; if (!pixiStage) return; const baseUrl = window.location.origin; diff --git a/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts b/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts index b666b6ed6..e8538299f 100644 --- a/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts +++ b/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts @@ -30,8 +30,8 @@ function dumpFastSaveToStorageSerial() { */ export async function fastSaveGame() { const showTitle = webgalStore.getState().GUI.showTitle; - if (showTitle || WebGAL.sceneManager.sceneData.currentSentenceId === 0) { - // 如果在标题界面或游戏未开始,不进行快速保存 + if (showTitle || WebGAL.sceneManager.sceneData.currentSentenceId === 0 || WebGAL.sceneManager.lockSceneWrite) { + // 如果在标题界面、游戏未开始或场景正在写入(此时状态是撕裂的),不进行快速保存 return; } const saveData: ISaveData = generateCurrentStageData(-1, false); diff --git a/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts index 2fd69d8ff..6b2139ac8 100644 --- a/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts +++ b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts @@ -54,6 +54,7 @@ export const jumpFromBacklog = (index: number, refetchScene = true) => { }); WebGAL.sceneManager.sceneData.currentSentenceId = backlogFile.saveScene.currentSentenceId; WebGAL.sceneManager.sceneData.sceneStack = cloneDeep(backlogFile.saveScene.sceneStack); + WebGAL.sceneManager.sceneData.currentLocals = cloneDeep(backlogFile.saveScene.currentLocals ?? {}); // 旧存档没有此字段 // 强制停止所有演出 stopAllPerform(); @@ -75,7 +76,7 @@ export const jumpFromBacklog = (index: number, refetchScene = true) => { stageStateManager.replaceCalculationStageState(newStageState); // 恢复演出 - setTimeout(restorePerform, 0); + restorePerform(); // 关闭backlog界面 dispatch(setVisibility({ component: 'showBacklog', visibility: false })); @@ -84,5 +85,5 @@ export const jumpFromBacklog = (index: number, refetchScene = true) => { dispatch(setVisibility({ component: 'showTextBox', visibility: true })); // 重新渲染 - setTimeout(() => WebGAL.gameplay.pixiStage?.requestRender(), 100); + WebGAL.gameplay.pixiStage?.requestRender(); }; diff --git a/packages/webgal/src/Core/controller/storage/loadGame.ts b/packages/webgal/src/Core/controller/storage/loadGame.ts index dd0900b15..d3aaf80c9 100644 --- a/packages/webgal/src/Core/controller/storage/loadGame.ts +++ b/packages/webgal/src/Core/controller/storage/loadGame.ts @@ -42,6 +42,7 @@ export function loadGameFromStageData(stageData: ISaveData) { }); WebGAL.sceneManager.sceneData.currentSentenceId = loadFile.sceneData.currentSentenceId; WebGAL.sceneManager.sceneData.sceneStack = cloneDeep(loadFile.sceneData.sceneStack); + WebGAL.sceneManager.sceneData.currentLocals = cloneDeep(loadFile.sceneData.currentLocals ?? {}); // 旧存档没有此字段 // 强制停止所有演出 stopAllPerform(); @@ -61,8 +62,7 @@ export function loadGameFromStageData(stageData: ISaveData) { stageStateManager.replaceCalculationStageState(newStageState); // 恢复演出 - setTimeout(() => restorePerform(true), 0); - // restorePerform(); + restorePerform(true); dispatch(setVisibility({ component: 'showTitle', visibility: false })); dispatch(setVisibility({ component: 'showMenuPanel', visibility: false })); diff --git a/packages/webgal/src/Core/controller/storage/saveGame.ts b/packages/webgal/src/Core/controller/storage/saveGame.ts index 6e69f07b1..9d67b4589 100644 --- a/packages/webgal/src/Core/controller/storage/saveGame.ts +++ b/packages/webgal/src/Core/controller/storage/saveGame.ts @@ -15,6 +15,11 @@ import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; * @param index 游戏的档位 */ export const saveGame = (index: number) => { + if (WebGAL.sceneManager.lockSceneWrite) { + // 场景写入期间状态是撕裂的:场景栈已变更,但当前场景与语句ID尚未切换 + logger.warn('场景切换中,忽略本次存档'); + return; + } const saveData: ISaveData = generateCurrentStageData(index); webgalStore.dispatch(saveActions.saveGame({ index, saveData })); dumpSavesToStorage(index, index); @@ -54,6 +59,7 @@ export function generateCurrentStageData(index: number, isSavePreviewImage = tru sceneStack: cloneDeep(WebGAL.sceneManager.sceneData.sceneStack), // 场景栈 sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, // 场景名称 sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl, // 场景url + currentLocals: cloneDeep(WebGAL.sceneManager.sceneData.currentLocals), // 当前帧的局部变量 }, previewImage: urlToSave, }; diff --git a/packages/webgal/src/Core/gameScripts/SCRIPT_AUTHORING.md b/packages/webgal/src/Core/gameScripts/SCRIPT_AUTHORING.md index cf69aef44..4ca48a267 100644 --- a/packages/webgal/src/Core/gameScripts/SCRIPT_AUTHORING.md +++ b/packages/webgal/src/Core/gameScripts/SCRIPT_AUTHORING.md @@ -59,61 +59,26 @@ export function someCommand(sentence: ISentence): IPerform { - `blockingNext`:是否阻塞用户下一步。 - `blockingAuto`:是否阻塞自动播放。 - `blockingStateCalculation`:是否阻塞继续演算后续状态。只有需要外部输入才能确定后续状态时才使用,例如选项和用户输入。 -- `settleStateOnDiscard`:pending perform 被“结算式丢弃”时的补偿钩子。它不是正常生命周期,不在 commit、start、stop、自然结束时执行。 -## settleStateOnDiscard +## 演算状态只允许顺序写 -`settleStateOnDiscard` 只解决一个窄问题:某条历史命令的 pending perform 在 commit 前被跳过,但它的最终状态又必须影响后续演算。 +演算状态(`calculationStageState`)的写入必须发生在命令函数阶段,顺序与语句顺序一致。 -当前触发点是 `forward()` 开头: +**不要把状态写入推迟到 `startFunction`、`stopFunction`、动画结束回调或 `setTimeout` 里。** 这类"延迟写"注册于语句 N,却执行于一个与语句顺序无关的时点,会覆盖语句 N+1 已经写好的状态。历史上 `settleStateOnDiscard` 就是为了给这种延迟写打补丁而存在的,现已连同问题一起移除。 -```ts -performController.discardUncommittedNonHoldPerforms(WebGAL.gameplay.isFastPreview); -``` - -也就是说,只有调用方传入 `settleDiscardedState = true` 时,被丢弃的非 hold pending perform 才会执行 `settleStateOnDiscard`。目前这个 true 只用于实时预览快进。普通 discard 不会执行这个钩子。 - -实现要求: - -- 必须同步执行,不能等待 loader、timer、动画帧或网络。 -- 必须幂等,重复调用不能把状态越写越偏。 -- 只写 `calculationStageState` 中可恢复、可被后续命令依赖的状态。 -- 不要操作 Pixi 对象、DOM、音频实例、ticker。 -- 不要调用 `commit()`。 - -什么时候需要实现: +正确的分工: -- 命令返回一个非 hold perform。 -- 命令的最终状态没有在命令函数阶段直接写入。 -- 这个最终状态对后续命令有意义。 -- 如果在命令函数阶段直接写入终态,会破坏当前行的正常视觉表现。 +- **命令函数阶段**:算出终态并写入演算状态。终态必须是同步可算的——如果算不出来,说明这个状态不该由这条命令负责。 +- **`startFunction`**:只做运行时动作(注册 Pixi 动画、播放媒体、挂载 UI),不写演算状态。 +- **`stopFunction`**:只清理运行时动作。 -什么时候不需要实现: +这样一条命令的 perform 无论被启动、被提前结算还是被丢弃,演算状态都一样,快速预览与正常播放自然一致。 -- 命令已经在函数阶段写好了后续命令需要的状态,例如普通 `setAnimation`、`setTransform`。 -- 命令只产生一次性声音、日志、UI 提示,后续演算不依赖它。 -- perform 是 hold,并且本来就应该保留到后续状态里。 +## 快速预览为什么仍然特殊 -## 快速预览为什么特殊 +实时预览快进会连续调用 `forward()`,中间不 commit,只在到达目标位置后提交一次。前一轮 `forward()` 收集到的非 hold pending perform,会在下一轮 `forward()` 开头被丢弃,因此不会执行 `startFunction` 和 `stopFunction`。 -实时预览快进会连续调用 `forward()`,中间不 commit,只在到达目标位置后提交一次。 - -这会带来一个差异:前一轮 `forward()` 收集到的非 hold pending perform,在下一轮 `forward()` 开头会被丢弃。被丢弃的 perform 不会执行: - -- `startFunction` -- `stopFunction` -- Pixi 注册动画后的结束回调 -- `setTimeout` 自动卸载逻辑 - -所以,如果某个命令把终态延迟到了这些阶段,快速预览历史行就会丢状态。 - -这次 `changeFigure -transform` 的问题就是这个类型: - -1. `changeFigure` 创建新立绘,并把 transform 做成进入动画。 -2. 正常播放时,进入动画由 Pixi sync 注册,终态会在动画结束或 preset 结算时写入 `effects`。 -3. 快速预览时,这条 enter perform 作为历史行被丢弃,没有机会注册进入动画。 -4. 后续 `setAnimation -parallel` 读取不到 figure 的 position,只能从默认 transform 开始算。 -5. 因此 `changeFigure` 需要在 `settleStateOnDiscard` 中把进入动画终态补写到 `calculationStageState.effects`。 +这只影响**视觉**,不影响演算状态——前提是命令遵守上面那条规则。如果某个命令把状态写进了这两个回调,快速预览就会丢状态,这是命令实现的 bug,不是预览机制的缺陷。 ## 动画命令 @@ -126,7 +91,9 @@ performController.discardUncommittedNonHoldPerforms(WebGAL.gameplay.isFastPrevie `-parallel` 下只能写动画实际控制的字段。例如只改 `scale` 的并行动画不应该把 `position` 重置成默认值。生成 timeline 或写终态时要使用局部字段合并,而不是完整覆盖目标 transform。 -新背景、新立绘的进入动画是特殊情况。当前行正常播放时不能无条件提前写入目标 `effects`,因为 `registerPresetAnimation()` 会把已有 effect 解释为目标已经结算,于是直接应用终态并跳过进入动画。它们应该在正常路径交给 Pixi preset 动画结算,在快速预览历史行被丢弃时再通过 `settleStateOnDiscard` 补写终态。 +新背景、新立绘的进入动画不是特殊情况,遵循同一条规则:`changeBg`、`changeFigure` 在命令函数阶段调用 `applyAnimationEndState()` 写入终态,进入动画由 returned perform 的 `startFunction` 用 `registerAnimation()` 注册。因此不存在"延迟结算",快速预览与正常播放读到的演算状态一致。 + +进入动画的演出名与 `setTransform` 等共用 `animation-`,同一目标上的动画冲突由演出去重统一裁决:后写的命令顶掉先写的,不需要按 `effects` 是否存在来猜测。视图层(`syncPixiStageState`)只负责创建和移除舞台对象,不再补写进入动画。 ## 参数和资源 @@ -148,7 +115,7 @@ JSON 参数必须 try/catch。解析失败时应回退到旧语义或安全默 - 后续命令需要读取的状态是否已经写入 `calculationStageState`? - perform 在快速预览历史行中被丢弃时,最终状态是否仍然正确? -- `settleStateOnDiscard` 是否只处理 pending discard 补偿,没有混入正常生命周期逻辑? +- 是否有任何演算状态的写入被推迟到了 `startFunction`、`stopFunction` 或定时器里? - `startFunction` 是否只依赖已 commit 的状态? - `stopFunction` 是否只清理已经启动过的运行时动作? - `-next`、`-continue`、`-parallel`、`-keep` 下状态是否一致? diff --git a/packages/webgal/src/Core/gameScripts/callSceneScript.ts b/packages/webgal/src/Core/gameScripts/callSceneScript.ts index e7f9a5848..da4f1a400 100644 --- a/packages/webgal/src/Core/gameScripts/callSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/callSceneScript.ts @@ -1,7 +1,19 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; +import { IGameVar } from '@/Core/Modules/stage/stageInterface'; import { callScene } from '../controller/scene/callScene'; +/** + * 还原参数值的类型。参数经过变量插值后恒为字符串,这里按解析器的规则重新判定。 + * @see packages/parser/src/scriptParser/argsParser.ts + */ +const restoreArgValueType = (value: string | boolean | number) => { + if (typeof value !== 'string') return value; + if (value === 'true' || value === 'false') return value === 'true'; + if (!isNaN(Number(value))) return Number(value); + return value; +}; + /** * 调用一个场景,在场景结束后回到调用这个场景的父场景。 * @param sentence @@ -9,6 +21,15 @@ import { callScene } from '../controller/scene/callScene'; export const callSceneScript = (sentence: ISentence): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - callScene(sentence.content, sceneName); + // 所有参数原样成为被调用场景的局部变量,包括 when、next 等通用参数,子场景用不用随意 + const locals: IGameVar = {}; + let writeReturnTo: string | undefined; + sentence.args.forEach(({ key, value }) => { + locals[key] = restoreArgValueType(value); + if (key === 'writeReturnTo' && typeof value === 'string') { + writeReturnTo = value; + } + }); + callScene(sentence.content, sceneName, locals, writeReturnTo); return createNonePerform({ isHoldOn: true }); }; diff --git a/packages/webgal/src/Core/gameScripts/changeBg/index.ts b/packages/webgal/src/Core/gameScripts/changeBg/index.ts index ded9f103c..2a6d30976 100644 --- a/packages/webgal/src/Core/gameScripts/changeBg/index.ts +++ b/packages/webgal/src/Core/gameScripts/changeBg/index.ts @@ -8,6 +8,7 @@ import { unlockCgInUserData } from '@/store/userDataReducer'; import { logger } from '@/Core/util/logger'; import { ITransform } from '@/Core/Modules/stage/stageInterface'; import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj'; +import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline'; import { AnimationFrame, IUserAnimation } from '@/Core/Modules/animations'; import cloneDeep from 'lodash/cloneDeep'; import { applyAnimationEndState, getAnimateDuration } from '@/Core/Modules/animationFunctions'; @@ -51,6 +52,8 @@ export const changeBg = (sentence: ISentence): IPerform => { * 删掉相关 Effects,因为已经移除了 */ if (isUrlChanged) { + // 必须先卸载旧的动画演出:它的 stopFunction 会写回终态,晚于清空 effects 会把旧变换复活 + WebGAL.gameplay.performController.unmountPerform(`animation-bg-main`, true); stageStateManager.removeEffectByTargetId(`bg-main`); stageStateManager.removeAnimationSettingsByTarget(`bg-main`); } @@ -109,41 +112,41 @@ export const changeBg = (sentence: ISentence): IPerform => { stageStateManager.updateAnimationSettings({ target: 'bg-main', key: 'exitDuration', value: exitDuration }); } + stageStateManager.setStage('bgName', sentence.content); + /** - * 背景状态后处理 + * 入场动画 + * + * 终态在演算期写入 effects,演出只负责视觉过渡,因此不需要任何延迟结算。 + * 与 setTransform 共用 `animation-bg-main` 演出名,同目标的动画冲突由演出去重统一裁决。 */ - function postBgStateSet() { - if (isUrlChanged) { - // 当 URL 发生变化时,清理旧的 hold 动画 - WebGAL.gameplay.performController.unmountPerform(`animation-bg-main`, true); - } - } - - postBgStateSet(); - stageStateManager.setStage('bgName', sentence.content); + const isEntering = isUrlChanged && sentence.content !== ''; + const enterAnimationSetting = isEntering + ? stageStateManager.getCalculationStageState().animationSettings.find((setting) => setting.target === 'bg-main') + : undefined; + const enterAnimationName = enterAnimationSetting?.enterAnimationName; + const enterAnimationTimeline = enterAnimationName + ? applyAnimationEndState( + enterAnimationName, + 'bg-main', + false, + !(enterAnimationSetting?.enterAnimationIgnoreDefault ?? false), + ) + : null; + const enterAnimationDuration = enterAnimationName ? getAnimateDuration(enterAnimationName) : 0; + const enterAnimationKey = 'bg-main-softin'; return { - performName: `bg-main-${sentence.content}`, + performName: isEntering ? `animation-bg-main` : `bg-main-${sentence.content}`, duration, isHoldOn: false, - settleStateOnDiscard: () => { - if (sentence.content === '' || !isUrlChanged) { - return; - } - const animationSetting = stageStateManager - .getCalculationStageState() - .animationSettings.find((setting) => setting.target === 'bg-main'); - if (animationSetting?.enterAnimationName) { - applyAnimationEndState( - animationSetting.enterAnimationName, - 'bg-main', - false, - !(animationSetting.enterAnimationIgnoreDefault ?? false), - ); - } + startFunction: () => { + if (!enterAnimationTimeline || WebGAL.gameplay.skipAnimation) return; + const animationObject = generateTimelineObj(enterAnimationTimeline, 'bg-main', enterAnimationDuration); + WebGAL.gameplay.pixiStage?.registerAnimation(animationObject, enterAnimationKey, 'bg-main'); }, stopFunction: () => { - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget('bg-main'); + WebGAL.gameplay.pixiStage?.removeAnimation(enterAnimationKey); }, blockingNext: () => false, blockingAuto: () => true, diff --git a/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts b/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts index 70922f6f9..cae95980e 100644 --- a/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts +++ b/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts @@ -29,4 +29,3 @@ function getValidBgImage(url: string): string { return `url("${url}")`; } } - diff --git a/packages/webgal/src/Core/gameScripts/changeFigure.ts b/packages/webgal/src/Core/gameScripts/changeFigure.ts index 54c07859d..cc33fca29 100644 --- a/packages/webgal/src/Core/gameScripts/changeFigure.ts +++ b/packages/webgal/src/Core/gameScripts/changeFigure.ts @@ -1,10 +1,16 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { IPerform } from '@/Core/Modules/perform/performInterface'; import cloneDeep from 'lodash/cloneDeep'; -import { getBooleanArgByKey, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; -import { IFreeFigure, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import { + getBooleanArgByKey, + getFigurePositionFromArgs, + getNumberArgByKey, + getStringArgByKey, +} from '@/Core/util/getSentenceArg'; +import { figureStateKeyByPosition, IFreeFigure } from '@/Core/Modules/stage/stageInterface'; import { AnimationFrame, IUserAnimation } from '@/Core/Modules/animations'; import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj'; +import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline'; import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter'; import { logger } from '@/Core/util/logger'; import { applyAnimationEndState, getAnimateDuration } from '@/Core/Modules/animationFunctions'; @@ -29,21 +35,7 @@ export function changeFigure(sentence: ISentence): IPerform { } // 根据参数设置指定位置 - let pos: 'center' | 'left' | 'right' = 'center'; - let mouthAnimationKey = 'mouthAnimation'; - let eyesAnimationKey = 'blinkAnimation'; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - if (leftFromArgs) { - pos = 'left'; - mouthAnimationKey = 'mouthAnimationLeft'; - eyesAnimationKey = 'blinkAnimationLeft'; - } - if (rightFromArgs) { - pos = 'right'; - mouthAnimationKey = 'mouthAnimationRight'; - eyesAnimationKey = 'blinkAnimationRight'; - } + const pos = getFigurePositionFromArgs(sentence) || 'center'; // id 与 自由立绘 let key = getStringArgByKey(sentence, 'id') ?? ''; @@ -127,27 +119,15 @@ export function changeFigure(sentence: ISentence): IPerform { isUrlChanged = false; } } - } else { - if (pos === 'center') { - if (stageStateManager.getCalculationStageState().figName === sentence.content) { - isUrlChanged = false; - } - } - if (pos === 'left') { - if (stageStateManager.getCalculationStageState().figNameLeft === sentence.content) { - isUrlChanged = false; - } - } - if (pos === 'right') { - if (stageStateManager.getCalculationStageState().figNameRight === sentence.content) { - isUrlChanged = false; - } - } + } else if (stageStateManager.getCalculationStageState()[figureStateKeyByPosition[pos]] === sentence.content) { + isUrlChanged = false; } /** * 处理 Effects */ if (isUrlChanged) { + // 必须先卸载旧的动画演出:它的 stopFunction 会写回终态,晚于清空 effects 会把旧变换复活 + WebGAL.gameplay.performController.unmountPerform(`animation-${id}`, true); stageStateManager.removeEffectByTargetId(id); stageStateManager.removeAnimationSettingsByTarget(id); const oldStageObject = WebGAL.gameplay.pixiStage?.getStageObjByKey(id); @@ -216,8 +196,6 @@ export function changeFigure(sentence: ISentence): IPerform { if (isUrlChanged) { // 当 url 发生变化时,即发生新立绘替换 // 应当赋予一些参数以默认值,防止从旧立绘的状态获取数据 - // 并且关闭一些 hold 动画 - WebGAL.gameplay.performController.unmountPerform(`animation-${key}`, true); bounds = bounds ?? [0, 0, 0, 0]; blink = blink ?? cloneDeep(baseBlinkParam); focus = focus ?? cloneDeep(baseFocusParam); @@ -265,45 +243,45 @@ export function changeFigure(sentence: ISentence): IPerform { /** * 下面的代码是设置与位置关联的立绘的 */ - const positionMap = { - center: 'fig-center', - left: 'fig-left', - right: 'fig-right', - }; - const dispatchMap: Record = { - center: 'figName', - left: 'figNameLeft', - right: 'figNameRight', - }; - - key = positionMap[pos]; + key = `fig-${pos}`; setAnimationNames(key, sentence); postFigureStateSet(); - stageStateManager.setStage(dispatchMap[pos], content); + stageStateManager.setStage(figureStateKeyByPosition[pos], content); } + /** + * 入场动画 + * + * 终态在演算期写入 effects,演出只负责视觉过渡,因此不需要任何延迟结算。 + * 与 setTransform 共用 `animation-${key}` 演出名,同目标的动画冲突由演出去重统一裁决。 + */ + const isEntering = isUrlChanged && content !== ''; + const enterAnimationSetting = isEntering + ? stageStateManager.getCalculationStageState().animationSettings.find((setting) => setting.target === key) + : undefined; + const enterAnimationName = enterAnimationSetting?.enterAnimationName; + const enterAnimationTimeline = enterAnimationName + ? applyAnimationEndState( + enterAnimationName, + key, + false, + !(enterAnimationSetting?.enterAnimationIgnoreDefault ?? false), + ) + : null; + const enterAnimationDuration = enterAnimationName ? getAnimateDuration(enterAnimationName) : 0; + const enterAnimationKey = `${key}-softin`; + return { - performName: `enter-${key}`, + performName: isEntering ? `animation-${key}` : `enter-${key}`, duration, isHoldOn: false, - settleStateOnDiscard: () => { - if (content === '' || !isUrlChanged) { - return; - } - const animationSetting = stageStateManager - .getCalculationStageState() - .animationSettings.find((setting) => setting.target === key); - if (animationSetting?.enterAnimationName) { - applyAnimationEndState( - animationSetting.enterAnimationName, - key, - false, - !(animationSetting.enterAnimationIgnoreDefault ?? false), - ); - } + startFunction: () => { + if (!enterAnimationTimeline || WebGAL.gameplay.skipAnimation) return; + const animationObject = generateTimelineObj(enterAnimationTimeline, key, enterAnimationDuration); + WebGAL.gameplay.pixiStage?.registerAnimation(animationObject, enterAnimationKey, key); }, stopFunction: () => { - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(key); + WebGAL.gameplay.pixiStage?.removeAnimation(enterAnimationKey); }, blockingNext: () => false, blockingAuto: () => true, diff --git a/packages/webgal/src/Core/gameScripts/label/jmp.ts b/packages/webgal/src/Core/gameScripts/label/jmp.ts index 53bd56a73..01fae15f0 100644 --- a/packages/webgal/src/Core/gameScripts/label/jmp.ts +++ b/packages/webgal/src/Core/gameScripts/label/jmp.ts @@ -4,6 +4,6 @@ import { jumpToLabel } from '@/Core/gameScripts/label/jumpToLabel'; export const jmp = (labelName: string, autoNext = true) => { const isJumped = jumpToLabel(labelName); if (isJumped && autoNext) { - setTimeout(continueSentence, 1); + continueSentence(); } }; diff --git a/packages/webgal/src/Core/gameScripts/parseTransformFrame.ts b/packages/webgal/src/Core/gameScripts/parseTransformFrame.ts index 0dbcdda52..07060ba29 100644 --- a/packages/webgal/src/Core/gameScripts/parseTransformFrame.ts +++ b/packages/webgal/src/Core/gameScripts/parseTransformFrame.ts @@ -5,7 +5,8 @@ export function parseTransformFrame(raw: string): AnimationFrame | null { if (source === '') return null; try { - const parsed: unknown = JSON.parse(source); + // null 视为未设置该属性,否则会被写入 pixi 容器或动画插值,导致渲染中断 + const parsed: unknown = JSON.parse(source, (_key, value) => (value === null ? undefined : value)); return isTransformFrame(parsed) ? parsed : null; } catch { return null; diff --git a/packages/webgal/src/Core/gameScripts/returnScript.ts b/packages/webgal/src/Core/gameScripts/returnScript.ts new file mode 100644 index 000000000..af636a2f8 --- /dev/null +++ b/packages/webgal/src/Core/gameScripts/returnScript.ts @@ -0,0 +1,15 @@ +import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; +import { returnFromScene } from '@/Core/controller/scene/returnFromScene'; +import { resolveSetVarValue } from './setVar'; + +/** + * 从被调用的场景返回,可携带返回值。返回值在被调用场景的作用域内求值。 + * @param sentence + */ +export const returnScript = (sentence: ISentence): IPerform => { + // 不写冒号时(`return;`)解析器会把整条命令留在 content 里,此时视为无返回值 + const valExp = sentence.content === sentence.commandRaw ? '' : sentence.content; + returnFromScene(resolveSetVarValue(valExp)); + return createNonePerform({ isHoldOn: true }); +}; diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts index 0319e8b68..aadaa00d9 100644 --- a/packages/webgal/src/Core/gameScripts/say.ts +++ b/packages/webgal/src/Core/gameScripts/say.ts @@ -4,7 +4,7 @@ import { playVocal } from './vocal'; import { webgalStore } from '@/store/store'; import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions'; import { getRandomPerformName } from '@/Core/Modules/perform/performController'; -import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; +import { getBooleanArgByKey, getFigurePositionFromArgs, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { textSize, voiceOption } from '@/store/userDataInterface'; import { WebGAL } from '@/Core/WebGAL'; import { compileSentence } from '@/Stage/TextBox/TextBox'; @@ -89,13 +89,7 @@ export const say = (sentence: ISentence): IPerform => { // 模拟说话 let performSimulateVocalTimeout: ReturnType | null = null; - let pos: '' | 'center' | 'left' | 'right' = ''; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - const centerFromArgs = getBooleanArgByKey(sentence, 'center') ?? false; - if (leftFromArgs) pos = 'left'; - if (rightFromArgs) pos = 'right'; - if (centerFromArgs) pos = 'center'; + const pos = getFigurePositionFromArgs(sentence); let key = getStringArgByKey(sentence, 'figureId') ?? ''; diff --git a/packages/webgal/src/Core/gameScripts/setAnimation.ts b/packages/webgal/src/Core/gameScripts/setAnimation.ts index 24af0fefa..3db4f43da 100644 --- a/packages/webgal/src/Core/gameScripts/setAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/setAnimation.ts @@ -34,9 +34,8 @@ export const setAnimation = (sentence: ISentence): IPerform => { if (keep && keepAnimationStopped) { return; } - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target); const animationObj: IAnimationObject | null = animationTimeline - ? generateTimelineObj(animationTimeline, target, animationDuration, false) + ? generateTimelineObj(animationTimeline, target, animationDuration) : null; if (animationObj) { logger.debug(`动画${animationName}作用在${target}`, animationDuration); @@ -49,7 +48,8 @@ export const setAnimation = (sentence: ISentence): IPerform => { keepAnimationStopped = true; return; } - WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key); + // 终态已在命令函数阶段写入 effects,这里只把容器推到终态,不回写演算状态 + WebGAL.gameplay.pixiStage?.removeAnimation(key); }; return { diff --git a/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts b/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts index 0d4435fa9..650d24c9e 100644 --- a/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts @@ -13,7 +13,6 @@ import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; * @param sentence */ export const setComplexAnimation = (sentence: ISentence): IPerform => { - const startDialogKey = stageStateManager.getCalculationStageState().currentDialogKey; const animationName = sentence.content; const animationDuration = getNumberArgByKey(sentence, 'duration') ?? 0; const target = getStringArgByKey(sentence, 'target') ?? '0'; @@ -31,13 +30,11 @@ export const setComplexAnimation = (sentence: ISentence): IPerform => { startFunction = () => { logger.debug(`动画${animationName}作用在${target}`, animationDuration); const animationObj: IAnimationObject = animationFunction(target, animationDuration); - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target); WebGAL.gameplay.pixiStage?.registerAnimation(animationObj, key, target); }; stopFunction = () => { - const endDialogKey = stageStateManager.getCalculationStageState().currentDialogKey; - const isHasNext = startDialogKey !== endDialogKey; - WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key); + // 终态已在命令函数阶段写入 effects,这里只把容器推到终态,不回写演算状态 + WebGAL.gameplay.pixiStage?.removeAnimation(key); }; } return { diff --git a/packages/webgal/src/Core/gameScripts/setTempAnimation.ts b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts index dc06c9f96..d0789ff74 100644 --- a/packages/webgal/src/Core/gameScripts/setTempAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts @@ -43,9 +43,8 @@ export const setTempAnimation = (sentence: ISentence): IPerform => { if (keep && keepAnimationStopped) { return; } - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target); const animationObj: IAnimationObject | null = animationTimeline - ? generateTimelineObj(animationTimeline, target, animationDuration, false) + ? generateTimelineObj(animationTimeline, target, animationDuration) : null; if (animationObj) { logger.debug(`动画${animationName}作用在${target}`, animationDuration); @@ -58,7 +57,8 @@ export const setTempAnimation = (sentence: ISentence): IPerform => { keepAnimationStopped = true; return; } - WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key); + // 终态已在命令函数阶段写入 effects,这里只把容器推到终态,不回写演算状态 + WebGAL.gameplay.pixiStage?.removeAnimation(key); }; return { diff --git a/packages/webgal/src/Core/gameScripts/setTransform.ts b/packages/webgal/src/Core/gameScripts/setTransform.ts index ff72934fd..b18d23142 100644 --- a/packages/webgal/src/Core/gameScripts/setTransform.ts +++ b/packages/webgal/src/Core/gameScripts/setTransform.ts @@ -49,9 +49,8 @@ export const setTransform = (sentence: ISentence): IPerform => { if (keep && keepAnimationStopped) { return; } - WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target); const animationObj: IAnimationObject | null = animationTimeline - ? generateTimelineObj(animationTimeline, target, animationDuration, false) + ? generateTimelineObj(animationTimeline, target, animationDuration) : null; if (animationObj) { logger.debug(`动画${animationName}作用在${target}`, animationDuration); @@ -64,7 +63,8 @@ export const setTransform = (sentence: ISentence): IPerform => { keepAnimationStopped = true; return; } - WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key); + // 终态已在命令函数阶段写入 effects,这里只把容器推到终态,不回写演算状态 + WebGAL.gameplay.pixiStage?.removeAnimation(key); }; return { diff --git a/packages/webgal/src/Core/gameScripts/setVar.ts b/packages/webgal/src/Core/gameScripts/setVar.ts index f86a3d356..ec88d5548 100644 --- a/packages/webgal/src/Core/gameScripts/setVar.ts +++ b/packages/webgal/src/Core/gameScripts/setVar.ts @@ -11,43 +11,81 @@ import get from 'lodash/get'; import random from 'lodash/random'; import { getBooleanArgByKey } from '../util/getSentenceArg'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; +import { WebGAL } from '@/Core/WebGAL'; + +/** + * 变量的作用域,与查找链一一对应。 + * local 是当前调用帧的局部变量,与 callScene 传入的参数同处一个命名空间,帧结束即消失。 + */ +export type VarScope = 'local' | 'stage' | 'global'; interface ISetGameVarFromExpressionPayload { key: string; value: string; - isGlobal?: boolean; + scope?: VarScope; persistGlobal?: boolean; } +/** + * 写入游戏变量。setVar 与场景返回值共用这一条写入路径。 + */ +export const setGameVar = (payload: ISetGameVar, scope: VarScope = 'stage') => { + if (scope === 'global') { + webgalStore.dispatch(setScriptManagedGlobalVar(payload)); + } else if (scope === 'local') { + WebGAL.sceneManager.sceneData.currentLocals[payload.key] = payload.value; + } else { + stageStateManager.setStageVar(payload); + } +}; + +/** + * 从参数解析写入作用域。-global 与 -local 互斥,都写时以 -global 为准。 + */ +const resolveVarScope = (sentence: ISentence): VarScope => { + const isGlobal = getBooleanArgByKey(sentence, 'global') ?? false; + const isLocal = getBooleanArgByKey(sentence, 'local') ?? false; + if (isGlobal) { + if (isLocal) { + logger.warn('setVar 同时使用了 -global 和 -local,按 -global 处理', sentence.content); + } + return 'global'; + } + return isLocal ? 'local' : 'stage'; +}; + /** * 设置变量表达式。 */ export const setGameVarFromExpression = ({ key, value, - isGlobal = false, + scope = 'stage', persistGlobal = true, }: ISetGameVarFromExpressionPayload) => { - const setGameVar = (payload: ISetGameVar) => { - if (isGlobal) { - webgalStore.dispatch(setScriptManagedGlobalVar(payload)); - } else { - stageStateManager.setStageVar(payload); - } - }; - const normalizedKey = key.trim(); if (!normalizedKey) { return; } - setGameVar({ key: normalizedKey, value: resolveSetVarValue(value) }); - if (isGlobal) { - logger.debug('设置全局变量:', { key: normalizedKey, value: webgalStore.getState().userData.globalGameVar[normalizedKey] }); + setGameVar({ key: normalizedKey, value: resolveSetVarValue(value) }, scope); + if (scope === 'global') { + logger.debug('设置全局变量:', { + key: normalizedKey, + value: webgalStore.getState().userData.globalGameVar[normalizedKey], + }); if (persistGlobal) { dumpToStorageFast(); } + } else if (scope === 'local') { + logger.debug('设置局部变量:', { + key: normalizedKey, + value: WebGAL.sceneManager.sceneData.currentLocals[normalizedKey], + }); } else { - logger.debug('设置变量:', { key: normalizedKey, value: stageStateManager.getCalculationStageState().GameVar[normalizedKey] }); + logger.debug('设置变量:', { + key: normalizedKey, + value: stageStateManager.getCalculationStageState().GameVar[normalizedKey], + }); } }; @@ -56,17 +94,19 @@ export const setGameVarFromExpression = ({ * @param sentence */ export const setVar = (sentence: ISentence): IPerform => { - const setGlobal = getBooleanArgByKey(sentence, 'global') ?? false; + const scope = resolveVarScope(sentence); if (sentence.content.match(/\s*=\s*/)) { const key = sentence.content.split(/\s*=\s*/)[0]; const valExp = sentence.content.split(/\s*=\s*/)[1]; - setGameVarFromExpression({ key, value: valExp, isGlobal: setGlobal }); + setGameVarFromExpression({ key, value: valExp, scope }); } return createNonePerform(); }; type BaseVal = string | number | boolean | undefined; +const hasOwn = (obj: object, key: string) => Object.prototype.hasOwnProperty.call(obj, key); + export function resolveSetVarValue(valExp: string): string | boolean | number { if (/^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp)) { return EvaluateExpression(valExp); @@ -125,12 +165,17 @@ function EvaluateExpression(val: string) { */ export function getValueFromState(key: string) { let ret: any; + const locals = WebGAL.sceneManager.sceneData.currentLocals; const stage = stageStateManager.getCalculationStageState(); const userData = webgalStore.getState().userData; const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突 - if (stage.GameVar.hasOwnProperty(key)) { + // 查找链:当前帧局部变量 -> 舞台变量 -> 全局变量 + // 变量名由脚本作者决定,不能用实例上的 hasOwnProperty,否则 hasOwnProperty 这种名字会把方法本身覆盖掉 + if (hasOwn(locals, key)) { + ret = locals[key]; + } else if (hasOwn(stage.GameVar, key)) { ret = stage.GameVar[key]; - } else if (userData.globalGameVar.hasOwnProperty(key)) { + } else if (hasOwn(userData.globalGameVar, key)) { ret = userData.globalGameVar[key]; } else if (key.startsWith('$')) { const propertyKey = key.replace('$', ''); diff --git a/packages/webgal/src/Core/gameScripts/showVars.ts b/packages/webgal/src/Core/gameScripts/showVars.ts index 6346c2b0c..93d8752b4 100644 --- a/packages/webgal/src/Core/gameScripts/showVars.ts +++ b/packages/webgal/src/Core/gameScripts/showVars.ts @@ -16,7 +16,9 @@ export const showVars = (sentence: ISentence): IPerform => { const stageState = stageStateManager.getCalculationStageState(); const userDataState = webgalStore.getState().userData; // 设置文本显示 + // 顺序与变量查找链一致:当前帧局部变量 -> 舞台变量 -> 全局变量 const allVar = { + localGameVar: WebGAL.sceneManager.sceneData.currentLocals, stageGameVar: stageState.GameVar, globalGameVar: userDataState.globalGameVar, }; @@ -30,9 +32,7 @@ export const showVars = (sentence: ISentence): IPerform => { duration: endDelay, isHoldOn: false, startFunction: () => { - setTimeout(() => { - WebGAL.events.textSettle.emit(); - }, 0); + WebGAL.events.textSettle.emit(); }, stopFunction: () => { WebGAL.events.textSettle.emit(); diff --git a/packages/webgal/src/Core/gameScripts/vocal/index.ts b/packages/webgal/src/Core/gameScripts/vocal/index.ts index a519f9914..2af8b5108 100644 --- a/packages/webgal/src/Core/gameScripts/vocal/index.ts +++ b/packages/webgal/src/Core/gameScripts/vocal/index.ts @@ -1,6 +1,6 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { logger } from '@/Core/util/logger'; -import { getBooleanArgByKey, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; +import { getFigurePositionFromArgs, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { IStageState } from '@/Core/Modules/stage/stageInterface'; import { audioContextWrapper, @@ -29,11 +29,7 @@ export const playVocal = (sentence: ISentence) => { let currentStageState: IStageState; currentStageState = stageStateManager.getCalculationStageState(); - let pos: 'center' | 'left' | 'right' = 'center'; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - if (leftFromArgs) pos = 'left'; - if (rightFromArgs) pos = 'right'; + let pos = getFigurePositionFromArgs(sentence) || 'center'; let key = getStringArgByKey(sentence, 'figureId') ?? ''; diff --git a/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts b/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts index 73e2bb744..baf8e737c 100644 --- a/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts @@ -24,8 +24,7 @@ export const audioContextWrapper: IAudioContextWrapper = { export const ensureAudioContextReady = async (): Promise => { if (!audioContextWrapper.audioContext) { const AudioContextCtor = - window.AudioContext ?? - (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; if (!AudioContextCtor) { return false; @@ -80,11 +79,7 @@ export const performBlinkAnimation = (params: { }; // Updated getAudioLevel function -export const getAudioLevel = ( - analyser: AnalyserNode, - dataArray: Uint8Array, - bufferLength: number, -): number => { +export const getAudioLevel = (analyser: AnalyserNode, dataArray: Uint8Array, bufferLength: number): number => { analyser.getByteFrequencyData(dataArray as any); let sum = 0; for (let i = 0; i < bufferLength; i++) { diff --git a/packages/webgal/src/Core/parser/sceneParser.ts b/packages/webgal/src/Core/parser/sceneParser.ts index 85b3ebea3..2bde9e89e 100644 --- a/packages/webgal/src/Core/parser/sceneParser.ts +++ b/packages/webgal/src/Core/parser/sceneParser.ts @@ -28,6 +28,7 @@ import { setTransition } from '@/Core/gameScripts/setTransition'; import { unlockBgm } from '@/Core/gameScripts/unlockBgm'; import { unlockCg } from '@/Core/gameScripts/unlockCg'; import { callSteam } from '@/Core/gameScripts/callSteam'; +import { returnScript } from '@/Core/gameScripts/returnScript'; import { end } from '../gameScripts/end'; import { jumpLabel } from '../gameScripts/jumpLabel'; import { pixiInit } from '../gameScripts/pixi/pixiInit'; @@ -74,6 +75,7 @@ export const SCRIPT_TAG_MAP = defineScripts({ applyStyle: ScriptConfig(commandType.applyStyle, applyStyle, { next: true }), wait: ScriptConfig(commandType.wait, wait), callSteam: ScriptConfig(commandType.callSteam, callSteam, { next: true }), + return: ScriptConfig(commandType.return, returnScript), }); export const SCRIPT_CONFIG: IConfigInterface[] = Object.values(SCRIPT_TAG_MAP); diff --git a/packages/webgal/src/Core/util/getSentenceArg.ts b/packages/webgal/src/Core/util/getSentenceArg.ts index 7d8ebf07c..629c9b9ab 100644 --- a/packages/webgal/src/Core/util/getSentenceArg.ts +++ b/packages/webgal/src/Core/util/getSentenceArg.ts @@ -1,4 +1,5 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { FIGURE_POSITIONS, IFigurePosition } from '@/Core/Modules/stage/stageInterface'; import { toSafeBoolean, toSafeNumber, toSafeString } from './toSafeType'; export function getSentenceArgByKey(sentence: ISentence, argKey: string): null | string | boolean | number { @@ -23,3 +24,10 @@ export function getStringArgByKey(sentence: ISentence, argKey: string): string | const argValue = getSentenceArgByKey(sentence, argKey); return toSafeString(argValue); } + +/** + * 从参数中获取立绘的预设位置,没有指定位置时返回空字符串 + */ +export function getFigurePositionFromArgs(sentence: ISentence): IFigurePosition | '' { + return FIGURE_POSITIONS.find((position) => getBooleanArgByKey(sentence, position)) ?? ''; +} diff --git a/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts index 6886f5f10..37696e50e 100644 --- a/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts +++ b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts @@ -43,6 +43,8 @@ const inferPrefetchAs = (assetType: fileType): string => { } }; +const inferPrefetchType = (url: string): string => (url.match(/\.opus(?:[?#].*)?$/i) ? 'audio/ogg' : ''); + const prefetchByLinkElement = (asset: IAsset) => { const newLink = document.createElement('link'); newLink.setAttribute('rel', 'prefetch'); @@ -51,6 +53,10 @@ const prefetchByLinkElement = (asset: IAsset) => { if (prefetchAs) { newLink.setAttribute('as', prefetchAs); } + const prefetchType = inferPrefetchType(asset.url); + if (prefetchType) { + newLink.setAttribute('type', prefetchType); + } const head = document.getElementsByTagName('head'); if (!head.length) { return; diff --git a/packages/webgal/src/Core/util/syncWithEditor/previewSetEffectTransform.ts b/packages/webgal/src/Core/util/syncWithEditor/previewSetEffectTransform.ts index 551fc7daf..b1b757371 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/previewSetEffectTransform.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/previewSetEffectTransform.ts @@ -3,10 +3,7 @@ import type { SetEffectPayload } from '@/types/editorPreviewProtocol'; type SetEffectTransformInput = SetEffectPayload['transform']; -export function mergeSetEffectPreviewTransform( - baseline: ITransform, - transform?: SetEffectTransformInput, -): ITransform { +export function mergeSetEffectPreviewTransform(baseline: ITransform, transform?: SetEffectTransformInput): ITransform { return { ...baseline, ...(transform ?? {}), diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewDebugVariables.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewDebugVariables.ts index 0f42cdd8a..86805fb2f 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewDebugVariables.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewDebugVariables.ts @@ -39,7 +39,7 @@ export function applyPreviewDebugVariables(debugVariables: DebugVariable[] = []) setGameVarFromExpression({ key: item.key, value: item.value, - isGlobal: item.isGlobal, + scope: item.isGlobal ? 'global' : 'stage', persistGlobal: false, }); if (!item.isGlobal) { diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts index 46dc17248..b5d77b952 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts @@ -7,6 +7,7 @@ import { commitForward, forward } from '@/Core/controller/gamePlay/nextSentence' import { stopFast } from '@/Core/controller/gamePlay/fastSkip'; import { sceneParser } from '@/Core/parser/sceneParser'; import { logger } from '@/Core/util/logger'; +import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter'; import type { FastPreviewTimeoutPayload, SyncScenePayload, SyncSceneSettleMode } from '@/types/editorPreviewProtocol'; import { applyPreviewDebugVariables } from './previewDebugVariables'; @@ -108,6 +109,7 @@ export async function runFastPreview( options: RunFastPreviewOptions = {}, ): Promise { const isLatest = () => options.isLatest?.() ?? true; + const targetSentenceId = resolveStopSentenceId(sentenceId); const fastPreviewStartTime = performance.now(); const baseSceneStackDepth = WebGAL.sceneManager.sceneData.sceneStack.length; stopFast(); @@ -125,19 +127,19 @@ export async function runFastPreview( !options.onBeforeTargetScriptExecute || didRunBeforeTargetScriptExecute || sceneName !== currentSceneName || - nextSentenceId !== sentenceId - 1 + nextSentenceId !== targetSentenceId - 1 ) { return; } - WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(true); + WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(); WebGAL.gameplay.performController.clearNonHoldPerformsFromStageState(); options.onBeforeTargetScriptExecute?.(); didRunBeforeTargetScriptExecute = true; }; try { - while (shouldContinueFastPreview(sentenceId, currentSceneName, baseSceneStackDepth)) { + while (shouldContinueFastPreview(targetSentenceId, currentSceneName, baseSceneStackDepth)) { if (!isLatest()) { return null; } @@ -198,7 +200,7 @@ export async function runFastPreview( } if (settleMode === 'immediate') { - WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(true); + WebGAL.gameplay.performController.discardUncommittedNonHoldPerforms(); WebGAL.gameplay.performController.clearNonHoldPerformsFromStageState(); } @@ -210,15 +212,15 @@ export async function runFastPreview( const forwardedLineCount = WebGAL.sceneManager.sceneData.currentScene.sceneName === currentSceneName - ? Math.min(WebGAL.sceneManager.sceneData.currentSentenceId, sentenceId) - : sentenceId; + ? Math.min(WebGAL.sceneManager.sceneData.currentSentenceId, targetSentenceId) + : targetSentenceId; const fastPreviewElapsedMs = Math.round(performance.now() - fastPreviewStartTime - suspendedElapsedMs); if (isTimedOut) { const payload: FastPreviewTimeoutPayload = { sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, sentenceId: WebGAL.sceneManager.sceneData.currentSentenceId, - targetSentenceId: sentenceId, + targetSentenceId, forwardedLineCount, elapsedMs: Math.max(timeoutElapsedMs, fastPreviewElapsedMs), maxDurationMs: FAST_PREVIEW_MAX_DURATION_MS, @@ -238,6 +240,23 @@ export async function runFastPreview( }; } +/** + * 把停止指针从多行语句的续行占位上回退到该语句首行之后。 + * + * 续行占位是带 -next 的注释,一次 forward 会沿着 -next 一路穿过它们, + * 顺带把下一条真实语句也执行掉。而编辑器不论把指针落在续行的哪一行, + * 想要的都是「执行完这条多行语句就停下」,即停在它首行之后。 + */ +function resolveStopSentenceId(sentenceId: number): number { + const sentenceList: ISentence[] = WebGAL.sceneManager.sceneData.currentScene.sentenceList; + let stopSentenceId = sentenceId; + while (stopSentenceId > 0 && sentenceList[stopSentenceId - 1]?.isLineBreakHolder) { + stopSentenceId--; + } + + return stopSentenceId; +} + function shouldContinueFastPreview(sentenceId: number, currentSceneName: string, baseSceneStackDepth: number): boolean { const sceneData = WebGAL.sceneManager.sceneData; if (sceneData.currentScene.sceneName === currentSceneName) { diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts index 38729b821..219e84909 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts @@ -1,17 +1,11 @@ import cloneDeep from 'lodash/cloneDeep'; import { STAGE_KEYS } from '@/Core/constants'; -import { baseTransform } from '@/Core/Modules/stage/stageInterface'; +import { baseTransform, FIGURE_KEYS } from '@/Core/Modules/stage/stageInterface'; import type { IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; import type { TransformBaselineQueryResultPayload } from '@/types/editorPreviewProtocol'; import type { FastPreviewResult } from './previewSyncSceneCommand'; -const FIXED_TARGETS = new Set([ - STAGE_KEYS.STAGE_MAIN, - STAGE_KEYS.BGMAIN, - STAGE_KEYS.FIG_C, - STAGE_KEYS.FIG_L, - STAGE_KEYS.FIG_R, -]); +const FIXED_TARGETS = new Set([STAGE_KEYS.STAGE_MAIN, STAGE_KEYS.BGMAIN, ...FIGURE_KEYS]); type BaselineRevisionState = | { @@ -202,7 +196,10 @@ function querySnapshot(snapshot: TargetTransformBaselineSnapshot, target: string }; } -function getSnapshotTransformOverride(snapshot: TargetTransformBaselineSnapshot, target: string): ITransform | undefined { +function getSnapshotTransformOverride( + snapshot: TargetTransformBaselineSnapshot, + target: string, +): ITransform | undefined { if (!snapshot.knownTargets.has(target)) { return undefined; } diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx index d0784def7..e69de003f 100644 --- a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -150,7 +150,9 @@ export default function IMSSTextbox(props: ITextboxProps) { diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index c0f76b8fd..36fb1b610 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -268,7 +268,7 @@ interface Segment { } function parseString(input: string): Segment[] { - const regex = /(\[(.*?)\]\((.*?)\))|([^\[\]]+)/g; + const regex = /(\[([^\]]+)\]\(([^)]+)\))|([\s\S]+?(?=\[|$))/g; const result: Segment[] = []; let match: RegExpExecArray | null; diff --git a/packages/webgal/src/UI/Backlog/Backlog.tsx b/packages/webgal/src/UI/Backlog/Backlog.tsx index 6b86c2c22..a4e0d4ec0 100644 --- a/packages/webgal/src/UI/Backlog/Backlog.tsx +++ b/packages/webgal/src/UI/Backlog/Backlog.tsx @@ -129,12 +129,10 @@ export const Backlog = () => { onClick={() => { playSeClick(); // 暂停所有 backlog 语音(包括当前;后续会重置并播放当前) - document - .querySelectorAll('[id^="backlog_audio_play_element_"]') - .forEach((audio: any) => { - audio.pause(); - audio.currentTime = 0; - }); + document.querySelectorAll('[id^="backlog_audio_play_element_"]').forEach((audio: any) => { + audio.pause(); + audio.currentTime = 0; + }); // 暂停游戏内正在播放的语音,避免与 backlog 语音混合 const currentVocal = document.getElementById('currentVocal') as HTMLAudioElement | null; if (currentVocal) { diff --git a/packages/webgal/src/UI/Extra/ExtraCgElement.tsx b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx index 84d2c2dd1..7ad92ebb4 100644 --- a/packages/webgal/src/UI/Extra/ExtraCgElement.tsx +++ b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx @@ -7,7 +7,7 @@ import { IAppreciationAsset } from '@/store/userDataInterface'; export const isVideoFile = (url: string) => { const extension = url.split('.').pop()?.toLowerCase() || ''; return ['mp4', 'webm', 'mkv'].includes(extension); -} +}; interface IProps { name: string; @@ -119,11 +119,7 @@ export function ExtraCgElement(props: IProps) { return ( <> {showFull.value && ( -
+
handleFullPreviewClick(e)}> {renderMedia(currentResource.url)}
@@ -141,7 +137,11 @@ export function ExtraCgElement(props: IProps) { {isStackPreview ? ( <> {stackResources.map((resource, index) => ( -
+
{renderMedia(resource.url)}
))} diff --git a/packages/webgal/src/UI/Flowchart/Flowchart.tsx b/packages/webgal/src/UI/Flowchart/Flowchart.tsx index 2a2217602..4e8867459 100644 --- a/packages/webgal/src/UI/Flowchart/Flowchart.tsx +++ b/packages/webgal/src/UI/Flowchart/Flowchart.tsx @@ -50,7 +50,8 @@ export const Flowchart = () => { const [isDragging, setIsDragging] = useState(false); const dragStateRef = useRef({ moved: false, scrollLeft: 0, scrollTop: 0, startX: 0, startY: 0 }); const blockClickRef = useRef(false); - const flowcharts = WebGAL.flowchartManager.getFlowcharts(); + // 每次渲染重新取,解锁事件触发的重渲染会让新解锁的个人线立刻出现在侧边栏 + const flowcharts = WebGAL.flowchartManager.getVisibleFlowcharts(); const currentFlowchart = flowcharts.find((e) => e.id === currentFlowchartId) ?? flowcharts[0]; const layout = useMemo( () => layoutFlowchart(currentFlowchart, lockedNodeVisibility), diff --git a/packages/webgal/src/UI/Menu/Options/System/About.tsx b/packages/webgal/src/UI/Menu/Options/System/About.tsx index f0c2fc5bf..1c0dbbe1a 100644 --- a/packages/webgal/src/UI/Menu/Options/System/About.tsx +++ b/packages/webgal/src/UI/Menu/Options/System/About.tsx @@ -15,19 +15,19 @@ export default function About(props: { onClose: () => void }) {
{__INFO.version}
{t('source')}
{t('contributors')}
{t('website')}
diff --git a/packages/webgal/src/UI/Menu/Options/System/System.tsx b/packages/webgal/src/UI/Menu/Options/System/System.tsx index ade8d9151..c9d047a8c 100644 --- a/packages/webgal/src/UI/Menu/Options/System/System.tsx +++ b/packages/webgal/src/UI/Menu/Options/System/System.tsx @@ -120,17 +120,24 @@ export function System() { { - dispatch(setOptionData({ key: 'skipAll', value: false })); - setStorage(); - }, () => { - dispatch(setOptionData({ key: 'skipAll', value: true })); - setStorage(); - }]} + functionList={[ + () => { + dispatch(setOptionData({ key: 'skipAll', value: false })); + setStorage(); + }, + () => { + dispatch(setOptionData({ key: 'skipAll', value: true })); + setStorage(); + }, + ]} currentChecked={userDataState.optionData.skipAll ? 1 : 0} /> - +
{ @@ -159,9 +166,7 @@ export function System() { type="button" key={key} className={`${styles.Option_select_item} ${ - value === userDataState.optionData.language - ? styles.Option_select_item_active - : '' + value === userDataState.optionData.language ? styles.Option_select_item_active : '' }`} onClick={() => { playSeSwitch(); diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts index fcec40710..f5d55bc56 100644 --- a/packages/webgal/src/store/userDataInterface.ts +++ b/packages/webgal/src/store/userDataInterface.ts @@ -58,6 +58,7 @@ export interface ISaveScene { sceneStack: Array; // 场景栈 sceneName: string; // 场景名称 sceneUrl: string; // 场景url + currentLocals?: IGameVar; // 当前帧的局部变量,旧存档没有此字段 } /** diff --git a/packages/webgal/src/translations/de.ts b/packages/webgal/src/translations/de.ts index cfd7e3488..b3efbdf91 100644 --- a/packages/webgal/src/translations/de.ts +++ b/packages/webgal/src/translations/de.ts @@ -63,8 +63,8 @@ const de = { options: { read: 'Gelesen', all: 'Alle', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/en.ts b/packages/webgal/src/translations/en.ts index e3614da57..c99ac6130 100644 --- a/packages/webgal/src/translations/en.ts +++ b/packages/webgal/src/translations/en.ts @@ -63,8 +63,8 @@ const en = { options: { read: 'Read', all: 'All', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/fr.ts b/packages/webgal/src/translations/fr.ts index a3309e227..0f217b1e6 100644 --- a/packages/webgal/src/translations/fr.ts +++ b/packages/webgal/src/translations/fr.ts @@ -63,8 +63,8 @@ const fr = { options: { read: 'Lu', all: 'Tout', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/jp.ts b/packages/webgal/src/translations/jp.ts index dcf96ba74..fb0228135 100644 --- a/packages/webgal/src/translations/jp.ts +++ b/packages/webgal/src/translations/jp.ts @@ -63,8 +63,8 @@ const jp = { options: { read: '既読', all: 'すべて', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/ko.ts b/packages/webgal/src/translations/ko.ts index 40f8b60ff..655b8a4a4 100644 --- a/packages/webgal/src/translations/ko.ts +++ b/packages/webgal/src/translations/ko.ts @@ -63,8 +63,8 @@ const ko = { options: { read: '읽은 부분', all: '전부', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/pt-br.ts b/packages/webgal/src/translations/pt-br.ts index 1fb979990..80aecac59 100644 --- a/packages/webgal/src/translations/pt-br.ts +++ b/packages/webgal/src/translations/pt-br.ts @@ -63,8 +63,8 @@ const ptBr = { options: { read: 'Leitura', all: 'Tudo', - } - } + }, + }, }, }, display: { @@ -106,7 +106,7 @@ const ptBr = { }, textPreview: { title: 'Prévia da exibição do texto', - text: "Você está vendo uma prévia da fonte, tamanho e velocidade de exibição do texto. Você pode ajustar as opções acima da forma que achar que fique melhor.", + text: 'Você está vendo uma prévia da fonte, tamanho e velocidade de exibição do texto. Você pode ajustar as opções acima da forma que achar que fique melhor.', }, }, }, diff --git a/packages/webgal/src/translations/zh-cn.ts b/packages/webgal/src/translations/zh-cn.ts index 797f95899..761a6ba80 100644 --- a/packages/webgal/src/translations/zh-cn.ts +++ b/packages/webgal/src/translations/zh-cn.ts @@ -63,8 +63,8 @@ const zhCn = { options: { read: '已读', all: '全部', - } - } + }, + }, }, }, display: { diff --git a/packages/webgal/src/translations/zh-tw.ts b/packages/webgal/src/translations/zh-tw.ts index 1cd6f8a0f..76deb3051 100644 --- a/packages/webgal/src/translations/zh-tw.ts +++ b/packages/webgal/src/translations/zh-tw.ts @@ -63,8 +63,8 @@ const zhTw = { options: { read: '已讀', all: '全部', - } - } + }, + }, }, }, display: { diff --git a/releasenote.md b/releasenote.md index 8e2afab25..67b9cc55a 100644 --- a/releasenote.md +++ b/releasenote.md @@ -8,35 +8,29 @@ #### 新功能 -新增流程图功能。玩家可以在菜单或底部控制栏查看已解锁的剧情节点,并跳转回已解锁的场景。 +新增场景调用传值。调用场景时可以传入数据,被调用的场景可以提前结束并把结果返回给调用处。 -流程图支持多条线路、节点解锁记录和未解锁节点显示控制,重置游戏数据时会一并清理流程图进度。 +新增只在当前场景内有效的变量,场景结束后自动消失。 -优化编辑器实时预览。调整背景、立绘、Spine、Live2D 和特效时,预览定位与实际画面更一致,快速切换预览目标时同步结果更稳定。 +立绘新增左侧 1/4、左侧 1/3、右侧 1/3、右侧 1/4 四个位置,对话时的立绘高亮同样支持。 -优化读档、回到流程图节点和自动继续时的画面与声音恢复,减少状态不一致。 +较长的语句可以分成多行书写,效果与写成一行相同。 -#### 修复 - -修复打开 Backlog 后自动播放仍可能继续推进的问题。 - -修复播放 Backlog 语音时,多个回想语音或当前游戏语音可能同时播放的问题。 - -修复视频播放失败时流程可能卡住,以及 skipOff 视频仍可被双击跳过的问题。 +支持 opus 格式的音频。 -修复 next 连续执行中接场景跳转时,后续流程可能失效的问题。 +优化动画衔接。登场动画与后续动画的过渡更自然,同一对象上后写的动画会覆盖先写的动画,快进预览的效果与实际游玩一致。 -修复场景跳转目标异常时可能加载无效场景文件的问题。 +#### 修复 -修复 bgm:none 无法正确停止背景音乐的问题。 +修复流程图中未解锁的支线仍会显示的问题。 -修复 changeBg / changeFigure / setTransform 的变换参数为空或格式异常时,动画表现可能不正确的问题。 +修复对话中出现方括号时后续文字可能不显示的问题。 -修复自定义文本框模板中的已读文本样式部分不生效的问题。 +修复效果设置填写不完整时画面可能停止刷新的问题。 -修复脚本注释包含多个分号时后续内容丢失的问题。 +修复快进预览时连续执行的语句画面效果可能不正确的问题。 -修复资源预加载可能重复处理同一资源,或包含无效空路径资源的问题。 +修复部分变量名取值结果不正确的问题。 ## Release Notes @@ -49,35 +43,29 @@ #### New Features -Added the flowchart feature. Players can view unlocked story nodes from the menu or bottom control panel and jump back to unlocked scenes. - -Flowcharts support multiple routes, node unlock progress, and locked-node visibility controls; resetting game data now also clears flowchart progress. +Added value passing for scene calls. Data can be passed into a called scene, and a called scene can end early and return a result to the caller. -Improved editor live preview. When adjusting backgrounds, figures, Spine, Live2D, and effects, preview positioning is closer to the actual screen, and synchronization is more stable when switching preview targets quickly. +Added variables that are valid only inside the current scene and disappear when the scene ends. -Improved screen and audio restoration after loading saves, returning to flowchart nodes, or continuing automatically, reducing state mismatches. - -#### Fixes +Added four figure positions: left 1/4, left 1/3, right 1/3, and right 1/4, also supported by dialogue figure highlighting. -Fixed autoplay possibly continuing after opening the Backlog. +Longer statements can be written across several lines, with the same result as writing them on one line. -Fixed multiple backlog voices, or backlog voice and current game voice, playing at the same time. +Added support for opus audio. -Fixed video playback failures possibly blocking progress, and fixed skipOff videos still being skippable by double-clicking. +Improved animation transitions. Entrance animations flow more naturally into following animations, a later animation on the same object replaces the earlier one, and fast preview matches actual playback. -Fixed follow-up flow possibly failing when a next chain leads into a scene jump. - -Fixed abnormal scene jump targets possibly loading invalid scene files. +#### Fixes -Fixed bgm:none not stopping background music correctly. +Fixed locked routes still being shown in the flowchart. -Fixed incorrect animation behavior when changeBg / changeFigure / setTransform receive empty or malformed transform arguments. +Fixed text after a square bracket in dialogue possibly not being displayed. -Fixed some read-text styles in custom textbox templates not taking effect. +Fixed the screen possibly stopping updates when an effect setting is incomplete. -Fixed script comments losing content after additional semicolons. +Fixed incorrect on-screen results for statements running in a row during fast preview. -Fixed resource preloading possibly processing the same resource repeatedly or including invalid empty resource paths. +Fixed some variable names returning incorrect values. ## リリースノート @@ -90,32 +78,26 @@ Fixed resource preloading possibly processing the same resource repeatedly or in #### 新機能 -フローチャート機能を追加しました。プレイヤーはメニューまたは下部コントロールから解放済みのストーリーノードを確認し、解放済みのシーンへ戻れるようになります。 +シーン呼び出しの値の受け渡しを追加しました。呼び出すシーンにデータを渡せるほか、呼び出されたシーンは途中で終了して結果を呼び出し元に返せます。 -フローチャートは複数ルート、ノード解放状態、未解放ノードの表示制御に対応しました。ゲームデータをリセットすると、フローチャートの進行状況も一緒に削除されます。 +現在のシーンの中だけで有効な変数を追加しました。シーンが終わると自動的に消えます。 -エディターのリアルタイムプレビューを改善しました。背景、立ち絵、Spine、Live2D、エフェクトを調整する際、プレビュー上の位置が実際の画面により近くなり、プレビュー対象を素早く切り替えた時の同期も安定しました。 +立ち絵の位置に左 1/4、左 1/3、右 1/3、右 1/4 を追加しました。会話時の立ち絵ハイライトも対応しています。 -ロード、フローチャートノードへの復帰、自動継続時の画面と音声の復元を改善し、状態のずれを減らしました。 - -#### 修正 +長い文を複数行に分けて書けるようになりました。1 行で書いた場合と結果は同じです。 -バックログを開いた後もオート再生が進み続ける場合がある問題を修正しました。 +opus 形式の音声に対応しました。 -バックログ音声が複数同時に再生されたり、ゲーム内の現在のボイスと重なって再生されたりする問題を修正しました。 +アニメーションの繋がりを改善しました。登場アニメーションから次のアニメーションへの移行が自然になり、同じ対象では後から指定したアニメーションが前のものを置き換え、早送りプレビューが実際のプレイと一致します。 -動画の再生に失敗した時に進行が止まる場合がある問題と、skipOff の動画をダブルクリックでスキップできてしまう問題を修正しました。 - -next の連続実行中にシーン移動が続くと、その後の進行が失敗する場合がある問題を修正しました。 - -異常なシーン移動先によって無効なシーンファイルが読み込まれる場合がある問題を修正しました。 +#### 修正 -bgm:none で BGM が正しく停止しない問題を修正しました。 +フローチャートで未解放のルートが表示される問題を修正しました。 -changeBg / changeFigure / setTransform の変換引数が空、または不正な形式の場合に、アニメーション表示が正しくならない問題を修正しました。 +会話に角括弧が含まれると、その後の文字が表示されないことがある問題を修正しました。 -カスタムテキストボックステンプレートの既読テキストスタイルが一部反映されない問題を修正しました。 +演出の設定が不完全な場合に、画面の更新が止まることがある問題を修正しました。 -スクリプトコメントに複数のセミコロンが含まれると、後続の内容が失われる問題を修正しました。 +早送りプレビューで連続して実行される文の画面表示が正しくないことがある問題を修正しました。 -リソースのプリロードで同じリソースが重複処理されたり、無効な空パスのリソースが含まれたりする問題を修正しました。 +一部の変数名で値が正しく取得できない問題を修正しました。 diff --git a/yarn.lock b/yarn.lock index eb638a574..c188ab5aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -589,10 +589,10 @@ dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.3.5": - version "3.3.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz#c131793cfc1a7b96f24a83e0a8bbd4b881558c60" - integrity sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== +"@eslint/eslintrc@^3.3.6": + version "3.3.6" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.6.tgz#d22bfd6b3a7d8e1f2c0b2f2e6de111b53ec6e13e" + integrity sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA== dependencies: ajv "^6.14.0" debug "^4.3.2" @@ -600,14 +600,14 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.1" + js-yaml "^4.3.0" minimatch "^3.1.5" strip-json-comments "^3.1.1" -"@eslint/js@9.39.4": - version "9.39.4" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.4.tgz#a3f83bfc6fd9bf33a853dfacd0b49b398eb596c1" - integrity sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== +"@eslint/js@9.39.5", "@eslint/js@^9.39.5": + version "9.39.5" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.5.tgz#6f2fbcff75500d229d535e0a949ae13472c84787" + integrity sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A== "@eslint/object-schema@^2.1.7": version "2.1.7" @@ -1422,100 +1422,100 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -"@typescript-eslint/eslint-plugin@^8.0.0": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz#ad0dcefeca9c2ecbe09f730d478063666aee010b" - integrity sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w== +"@typescript-eslint/eslint-plugin@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz#0a58df6fea8c0bf6b396f518077099bc8b762bb5" + integrity sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.57.2" - "@typescript-eslint/type-utils" "8.57.2" - "@typescript-eslint/utils" "8.57.2" - "@typescript-eslint/visitor-keys" "8.57.2" + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/type-utils" "8.65.0" + "@typescript-eslint/utils" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" ignore "^7.0.5" natural-compare "^1.4.0" - ts-api-utils "^2.4.0" + ts-api-utils "^2.5.0" -"@typescript-eslint/parser@^8.0.0": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.2.tgz#b819955e39f976c0d4f95b5ed67fe22f85cd6898" - integrity sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA== +"@typescript-eslint/parser@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.65.0.tgz#5295c1058c0a1dd746ef28baaf9c0341dbdf03dc" + integrity sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA== dependencies: - "@typescript-eslint/scope-manager" "8.57.2" - "@typescript-eslint/types" "8.57.2" - "@typescript-eslint/typescript-estree" "8.57.2" - "@typescript-eslint/visitor-keys" "8.57.2" + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" debug "^4.4.3" -"@typescript-eslint/project-service@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.2.tgz#dfbc7777f9f633f2b06b558cda3836e76f856e3c" - integrity sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw== +"@typescript-eslint/project-service@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.65.0.tgz#65fbbc9a1591abffaeab5513200f848271cb0aa5" + integrity sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.57.2" - "@typescript-eslint/types" "^8.57.2" + "@typescript-eslint/tsconfig-utils" "^8.65.0" + "@typescript-eslint/types" "^8.65.0" debug "^4.4.3" -"@typescript-eslint/scope-manager@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz#734dcde40677f430b5d963108337295bdbc09dae" - integrity sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw== +"@typescript-eslint/scope-manager@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz#9547202ce7e608e7b6283df585703b980a0ea70d" + integrity sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg== dependencies: - "@typescript-eslint/types" "8.57.2" - "@typescript-eslint/visitor-keys" "8.57.2" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" -"@typescript-eslint/tsconfig-utils@8.57.2", "@typescript-eslint/tsconfig-utils@^8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz#cf82dc11e884103ec13188a7352591efaa1a887e" - integrity sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw== +"@typescript-eslint/tsconfig-utils@8.65.0", "@typescript-eslint/tsconfig-utils@^8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz#36f168fcdbb1295f7446ff0379667f98c3cf1bf3" + integrity sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg== -"@typescript-eslint/type-utils@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz#3ec65a94e73776252991a3cf0a15d220734c28f5" - integrity sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg== +"@typescript-eslint/type-utils@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz#d316d7522d93cff4cd14f305e02f3df2d804f9c1" + integrity sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g== dependencies: - "@typescript-eslint/types" "8.57.2" - "@typescript-eslint/typescript-estree" "8.57.2" - "@typescript-eslint/utils" "8.57.2" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/utils" "8.65.0" debug "^4.4.3" - ts-api-utils "^2.4.0" - -"@typescript-eslint/types@8.57.2", "@typescript-eslint/types@^8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.2.tgz#efe0da4c28b505ed458f113aa960dce2c5c671f4" - integrity sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA== - -"@typescript-eslint/typescript-estree@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz#432e61a6cf2ab565837da387e5262c159672abea" - integrity sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA== - dependencies: - "@typescript-eslint/project-service" "8.57.2" - "@typescript-eslint/tsconfig-utils" "8.57.2" - "@typescript-eslint/types" "8.57.2" - "@typescript-eslint/visitor-keys" "8.57.2" + ts-api-utils "^2.5.0" + +"@typescript-eslint/types@8.65.0", "@typescript-eslint/types@^8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.65.0.tgz#3e86738416a777c8b8925ab46745f48ecf904c9f" + integrity sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg== + +"@typescript-eslint/typescript-estree@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz#f1f514808f6aa713e2d678ae8ff592a65e1632af" + integrity sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg== + dependencies: + "@typescript-eslint/project-service" "8.65.0" + "@typescript-eslint/tsconfig-utils" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" debug "^4.4.3" minimatch "^10.2.2" semver "^7.7.3" tinyglobby "^0.2.15" - ts-api-utils "^2.4.0" + ts-api-utils "^2.5.0" -"@typescript-eslint/utils@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.2.tgz#46a8974c24326fb8899486728428a0f1a3115014" - integrity sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg== +"@typescript-eslint/utils@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.65.0.tgz#afedd974a0c8deeef553b509df5800bafd615a72" + integrity sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.57.2" - "@typescript-eslint/types" "8.57.2" - "@typescript-eslint/typescript-estree" "8.57.2" + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" -"@typescript-eslint/visitor-keys@8.57.2": - version "8.57.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz#a5c9605774247336c0412beb7dc288ab2a07c11e" - integrity sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw== +"@typescript-eslint/visitor-keys@8.65.0": + version "8.65.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz#e3704c13cb4a1c22454c1abf28ff4737e15018c6" + integrity sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A== dependencies: - "@typescript-eslint/types" "8.57.2" + "@typescript-eslint/types" "8.65.0" eslint-visitor-keys "^5.0.0" "@vitejs/plugin-react@^4.0.4": @@ -1700,12 +1700,20 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" +array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-includes@^3.1.6, array-includes@^3.1.7: +array-includes@^3.1.6: version "3.1.8" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== @@ -1717,6 +1725,20 @@ array-includes@^3.1.6, array-includes@^3.1.7: get-intrinsic "^1.2.4" is-string "^1.0.7" +array-includes@^3.1.8: + version "3.1.9" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + get-intrinsic "^1.3.0" + is-string "^1.1.1" + math-intrinsics "^1.1.0" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1734,7 +1756,7 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array.prototype.findlast@^1.2.4: +array.prototype.findlast@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== @@ -1756,35 +1778,25 @@ array.prototype.flat@^1.3.1: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.toreversed@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" - integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== +array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" -array.prototype.tosorted@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" - integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.1.0" + es-abstract "^1.23.3" + es-errors "^1.3.0" es-shim-unscopables "^1.0.2" arraybuffer.prototype.slice@^1.0.3: @@ -1801,6 +1813,19 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -2018,6 +2043,24 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bind@^1.0.8, call-bind@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7" + integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + get-intrinsic "^1.3.0" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2277,6 +2320,15 @@ data-view-buffer@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + data-view-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" @@ -2286,6 +2338,15 @@ data-view-byte-length@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + data-view-byte-offset@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" @@ -2295,6 +2356,15 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2421,7 +2491,7 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -2489,7 +2559,77 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: +es-abstract-get@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-abstract-get/-/es-abstract-get-1.0.0.tgz#1eae87101f42bedeb6a740e8c5051271aef89088" + integrity sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg== + dependencies: + es-errors "^1.3.0" + es-object-atoms "^1.1.2" + is-callable "^1.2.7" + object-inspect "^1.13.4" + +es-abstract@^1.17.5, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.2: + version "1.24.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.2.tgz#2dbd38c180735ee983f77585140a2706a963ed9a" + integrity sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: version "1.23.2" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== @@ -2553,30 +2693,32 @@ es-define-property@^1.0.1: resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== -es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-iterator-helpers@^1.0.17: - version "1.0.18" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" - integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== +es-iterator-helpers@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz#2b4635ee3e8db2285378a47a1ea8f8dd34adb653" + integrity sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.9" + call-bound "^1.0.4" define-properties "^1.2.1" - es-abstract "^1.23.0" + es-abstract "^1.24.2" es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" + es-set-tostringtag "^2.1.0" function-bind "^1.1.2" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" + get-intrinsic "^1.3.0" + globalthis "^1.0.4" + gopd "^1.2.0" has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.2" - safe-array-concat "^1.1.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.5" + math-intrinsics "^1.1.0" es-object-atoms@^1.0.0: version "1.0.0" @@ -2592,6 +2734,13 @@ es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" @@ -2627,6 +2776,18 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-to-primitive@^1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.4.tgz#0c854291cf0d7b439d6b9e5771ea837ffaf1f991" + integrity sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw== + dependencies: + es-abstract-get "^1.0.0" + es-define-property "^1.0.1" + es-errors "^1.3.0" + is-callable "^1.2.7" + is-date-object "^1.1.0" + is-symbol "^1.1.1" + esbuild@^0.18.10, esbuild@~0.18.20: version "0.18.20" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" @@ -2704,46 +2865,41 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-alloy@^4.5.1: - version "4.9.0" - resolved "https://registry.yarnpkg.com/eslint-config-alloy/-/eslint-config-alloy-4.9.0.tgz#44ed777e8eb0ee595baabeb5d6cb568c2e6461c2" - integrity sha512-4DfiN0sXByf5WuXioZrrZJdfzZw+lgdoQfFTuQyFenJ2XeIKrsXYctArLiZAoAsTxyZnmUFYqrgwUIXx26q0gg== - eslint-config-prettier@^8.5.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" - integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== + version "8.10.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz#0642e53625ebc62c31c24726b0f050df6bd97a2e" + integrity sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A== eslint-plugin-prettier@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + version "4.2.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz#91ca3f2f01a84f1272cce04e9717550494c0fe06" + integrity sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react@^7.29.4: - version "7.34.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== +eslint-plugin-react@^7.37.5: + version "7.37.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== dependencies: - array-includes "^3.1.7" - array.prototype.findlast "^1.2.4" - array.prototype.flatmap "^1.3.2" - array.prototype.toreversed "^1.1.2" - array.prototype.tosorted "^1.1.3" + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.3" + array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" - es-iterator-helpers "^1.0.17" + es-iterator-helpers "^1.2.1" estraverse "^5.3.0" + hasown "^2.0.2" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" - object.hasown "^1.1.3" - object.values "^1.1.7" + object.entries "^1.1.9" + object.fromentries "^2.0.8" + object.values "^1.2.1" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.10" + string.prototype.matchall "^4.0.12" + string.prototype.repeat "^1.0.0" eslint-scope@^8.4.0: version "8.4.0" @@ -2768,18 +2924,18 @@ eslint-visitor-keys@^5.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^9.26.0: - version "9.39.4" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.4.tgz#855da1b2e2ad66dc5991195f35e262bcec8117b5" - integrity sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== +eslint@^9.39.5: + version "9.39.5" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.5.tgz#2a4e3c8b0f753196efae943c8ffaa8730fc6a3fa" + integrity sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" "@eslint/config-array" "^0.21.2" "@eslint/config-helpers" "^0.4.2" "@eslint/core" "^0.17.0" - "@eslint/eslintrc" "^3.3.5" - "@eslint/js" "9.39.4" + "@eslint/eslintrc" "^3.3.6" + "@eslint/js" "9.39.5" "@eslint/plugin-kit" "^0.4.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -3059,6 +3215,13 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -3151,7 +3314,7 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: +function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -3161,6 +3324,21 @@ function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: es-abstract "^1.22.1" functions-have-names "^1.2.3" +function.prototype.name@^1.1.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.2.0.tgz#758f3e84fa542672454bd5e14cb081a5ce07f70c" + integrity sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + es-define-property "^1.0.1" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + hasown "^2.0.4" + is-callable "^1.2.7" + is-document.all "^1.0.0" + functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -3192,7 +3370,7 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -get-intrinsic@^1.2.6: +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -3208,7 +3386,7 @@ get-intrinsic@^1.2.6: hasown "^2.0.2" math-intrinsics "^1.1.0" -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -3230,6 +3408,15 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-tsconfig@^4.7.2: version "4.7.3" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.3.tgz#0498163d98f7b58484dd4906999c0c9d5f103f83" @@ -3324,6 +3511,11 @@ globals@^14.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globals@^17.7.0: + version "17.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.7.0.tgz#553d55090b4dde8209ec2da42580d6e7e7d8b10d" + integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== + globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" @@ -3331,6 +3523,14 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -3398,6 +3598,13 @@ has-proto@^1.0.1, has-proto@^1.0.3: resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -3427,6 +3634,13 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + hey-listen@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" @@ -3564,6 +3778,15 @@ internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + into-stream@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" @@ -3585,6 +3808,15 @@ is-array-buffer@^3.0.4: call-bind "^1.0.2" get-intrinsic "^1.2.1" +is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -3604,6 +3836,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3619,6 +3858,14 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-builtin-module@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" @@ -3652,29 +3899,53 @@ is-data-view@^1.0.1: dependencies: is-typed-array "^1.1.13" -is-date-object@^1.0.1, is-date-object@^1.0.5: +is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" +is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-document.all@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-document.all/-/is-document.all-1.0.0.tgz#163a4bfb362c6ed7b118ce46cdecc4e37dee3195" + integrity sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g== + dependencies: + call-bound "^1.0.4" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.3" is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -3722,6 +3993,14 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -3742,6 +4021,16 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" @@ -3754,6 +4043,13 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-ssh@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.1.tgz#76de1cdbe8f92a8b905d1a172b6bc09704c20396" @@ -3773,6 +4069,14 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" @@ -3780,6 +4084,15 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + is-typed-array@^1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" @@ -3787,6 +4100,13 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" +is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" @@ -3799,6 +4119,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + is-weakset@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" @@ -3851,16 +4178,17 @@ istanbul-reports@^3.1.4: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterator.prototype@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== +iterator.prototype@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" js-binary-schema-parser@^2.0.3: version "2.0.3" @@ -3872,10 +4200,10 @@ js-binary-schema-parser@^2.0.3: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" - integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== +js-yaml@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.0.tgz#d1900572a7f7cf0b5f540c83673e60bad3436592" + integrity sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q== dependencies: argparse "^2.0.1" @@ -4318,6 +4646,11 @@ object-inspect@^1.12.2, object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -4333,16 +4666,29 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" -object.fromentries@^2.0.7: +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" + +object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -4352,16 +4698,7 @@ object.fromentries@^2.0.7: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.hasown@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" - integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== - dependencies: - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.values@^1.1.6, object.values@^1.1.7: +object.values@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -4370,6 +4707,16 @@ object.values@^1.1.6, object.values@^1.1.7: define-properties "^1.2.1" es-object-atoms "^1.0.0" +object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -4412,6 +4759,16 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" +own-keys@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.2.tgz#31448ec1f781ecb1447f6f6aa0d6534222af59de" + integrity sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg== + dependencies: + call-bound "^1.0.4" + get-intrinsic "^1.3.0" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-is-promise@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" @@ -4740,6 +5097,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== +possible-typed-array-names@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss@^8.4.27: version "8.4.38" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" @@ -4782,9 +5144,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + version "1.0.1" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz#6a31f88a4bad6c7adda253de12ba4edaea80ebcd" + integrity sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg== dependencies: fast-diff "^1.1.2" @@ -5004,18 +5366,19 @@ redux@^4.2.1: dependencies: "@babel/runtime" "^7.9.2" -reflect.getprototypeof@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== +reflect.getprototypeof@^1.0.10, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" - es-abstract "^1.23.1" + es-abstract "^1.23.9" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" regenerator-runtime@^0.14.0: version "0.14.1" @@ -5032,6 +5395,18 @@ regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" +regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5181,6 +5556,17 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-array-concat@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz#a54cc9b61a57f33b42abad3cbdda3a2b38cc5719" + integrity sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + get-intrinsic "^1.3.0" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -5191,6 +5577,14 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -5200,6 +5594,15 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -5268,7 +5671,7 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" -set-function-length@^1.2.1: +set-function-length@^1.2.1, set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== @@ -5290,6 +5693,15 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -5307,6 +5719,35 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -5317,6 +5758,17 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + siginfo@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" @@ -5429,6 +5881,14 @@ std-env@^3.3.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + stream-meter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d" @@ -5459,23 +5919,46 @@ string-width@^5.0.0: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.matchall@^4.0.10: - version "4.0.11" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" - integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== +string.prototype.matchall@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - es-abstract "^1.23.2" + es-abstract "^1.23.6" es-errors "^1.3.0" es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - regexp.prototype.flags "^1.5.2" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" set-function-name "^2.0.2" - side-channel "^1.0.6" + side-channel "^1.1.0" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.10: + version "1.2.11" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz#e6bd19cda3985d05a42dda31f3ddf4d35d3430e3" + integrity sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.24.2" + es-object-atoms "^1.1.2" + has-property-descriptors "^1.0.2" + safe-regex-test "^1.1.0" string.prototype.trim@^1.2.9: version "1.2.9" @@ -5496,7 +5979,17 @@ string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.7: +string.prototype.trimend@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz#be6bcf4f3fe0460bdeccdb2cf4f971b310f8346e" + integrity sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.2" + +string.prototype.trimstart@^1.0.7, string.prototype.trimstart@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== @@ -5686,7 +6179,7 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" -ts-api-utils@^2.4.0: +ts-api-utils@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== @@ -5753,6 +6246,15 @@ typed-array-buffer@^1.0.2: es-errors "^1.3.0" is-typed-array "^1.1.13" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + typed-array-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" @@ -5764,6 +6266,17 @@ typed-array-byte-length@^1.0.1: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + typed-array-byte-offset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" @@ -5776,6 +6289,19 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + typed-array-length@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" @@ -5788,7 +6314,29 @@ typed-array-length@^1.0.5: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typescript@^4.5.4, typescript@^4.9.3: +typed-array-length@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.8.tgz#0b70e982c9e9dafe2def6d6458ff4b3f2d2b6d70" + integrity sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g== + dependencies: + call-bind "^1.0.9" + for-each "^0.3.5" + gopd "^1.2.0" + is-typed-array "^1.1.15" + possible-typed-array-names "^1.1.0" + reflect.getprototypeof "^1.0.10" + +typescript-eslint@^8.65.0: + version "8.65.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.65.0.tgz#754c953fd6a9a3d36fa54015bdba80a6eb2a4957" + integrity sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.65.0" + "@typescript-eslint/parser" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/utils" "8.65.0" + +typescript@^4.9.3, typescript@^4.9.5: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== @@ -5808,6 +6356,16 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -6018,25 +6576,37 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" is-generator-function "^1.0.10" - is-regex "^1.1.4" + is-regex "^1.2.1" is-weakref "^1.0.2" isarray "^2.0.5" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" -which-collection@^1.0.1: +which-collection@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== @@ -6046,7 +6616,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: +which-typed-array@^1.1.14, which-typed-array@^1.1.15: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -6057,6 +6627,19 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.2" +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.22" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.22.tgz#8f3cc78aefb40b437346dd40a1dbfa5d1da43fe9" + integrity sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.9" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"