webgal-parser 4.6.3-beta.2 → 4.6.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # WebGAL Parser Changelog
2
2
 
3
+ ## [4.6.3] - 2026-08-01
4
+
5
+ 正式发布 4.6.3-beta.1 / beta.2 的多行语句支持,另含以下改动。
6
+
7
+ ### Added
8
+ - 新增 `return` 命令,用于从被 `callScene` 调用的场景提前返回
9
+ - `sceneEntry` 新增 `locals` / `writeReturnTo`,承载 `callScene` 传入的局部变量与返回值写回目标
10
+ - 语音参数识别 `.opus` 扩展名
11
+
3
12
  ## [4.6.3-beta.2] - 2026-07-31
4
13
 
5
14
  ### Fixed
@@ -40,12 +40,14 @@ var commandType;
40
40
  commandType[commandType["wait"] = 33] = "wait";
41
41
  commandType[commandType["callSteam"] = 34] = "callSteam";
42
42
  commandType[commandType["return"] = 35] = "return";
43
+ commandType[commandType["changeFigureDiff"] = 36] = "changeFigureDiff";
43
44
  })(commandType || (commandType = {}));
44
45
 
45
46
  const SCRIPT_CONFIG = [
46
47
  { scriptString: 'say', scriptType: commandType.say },
47
48
  { scriptString: 'changeBg', scriptType: commandType.changeBg },
48
49
  { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
50
+ { scriptString: 'changeFigureDiff', scriptType: commandType.changeFigureDiff },
49
51
  { scriptString: 'bgm', scriptType: commandType.bgm },
50
52
  { scriptString: 'playVideo', scriptType: commandType.video },
51
53
  { scriptString: 'pixiPerform', scriptType: commandType.pixi },
@@ -297,6 +299,7 @@ const contentParser = (contentRaw, type, assetSetter) => {
297
299
  case commandType.changeBg:
298
300
  return assetSetter(contentRaw, fileType.background);
299
301
  case commandType.changeFigure:
302
+ case commandType.changeFigureDiff:
300
303
  return assetSetter(contentRaw, fileType.figure);
301
304
  case commandType.bgm:
302
305
  return assetSetter(contentRaw, fileType.bgm);
@@ -377,7 +380,7 @@ const assetsScanner = (command, content, args, lineNumber) => {
377
380
  type: fileType.background,
378
381
  });
379
382
  }
380
- if (command === commandType.changeFigure) {
383
+ if (command === commandType.changeFigure || command === commandType.changeFigureDiff) {
381
384
  returnAssetsList.push({
382
385
  name: content,
383
386
  url: content,
@@ -441,8 +444,10 @@ const subSceneScanner = (command, content) => {
441
444
  * @param assetSetter
442
445
  * @param ADD_NEXT_ARG_LIST
443
446
  * @param SCRIPT_CONFIG_MAP
447
+ * @param lineNumber 语句所在行号
448
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
444
449
  */
445
- const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0) => {
450
+ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0, collectAssets = true) => {
446
451
  let command; // 默认为对话
447
452
  let content; // 语句内容
448
453
  let subScene; // 语句携带的子场景(可能没有)
@@ -510,8 +515,8 @@ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG
510
515
  }
511
516
  }
512
517
  content = contentParser(newSentenceRaw.trim(), command, assetSetter); // 将语句内容里的文件名转为相对或绝对路径
513
- sentenceAssets = assetsScanner(command, content, args, lineNumber); // 扫描语句携带资源
514
- subScene = subSceneScanner(command, content); // 扫描语句携带子场景
518
+ sentenceAssets = collectAssets ? assetsScanner(command, content, args, lineNumber) : []; // 扫描语句携带资源
519
+ subScene = collectAssets ? subSceneScanner(command, content) : []; // 扫描语句携带子场景
515
520
  return {
516
521
  command: command,
517
522
  commandRaw: commandRaw.trim(),
@@ -688,6 +693,8 @@ function placeholderLine(content = "") {
688
693
  * @return {IScene} 解析后的场景
689
694
  */
690
695
  const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
696
+ // 没有预取回调时不需要场景级资源列表,连同每条语句的资源扫描一起跳过
697
+ const collectAssets = assetsPrefetcher !== undefined;
691
698
  // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
692
699
  // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
693
700
  const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
@@ -698,16 +705,28 @@ const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSette
698
705
  let assetsList = []; // 场景资源列表
699
706
  let subSceneList = []; // 子场景列表
700
707
  const sentenceList = rawSentenceListWithoutEmpty.map((sentence, index) => {
701
- const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index);
702
- // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
703
- assetsList = [...assetsList, ...returnSentence.sentenceAssets];
704
- subSceneList = [...subSceneList, ...returnSentence.subScene];
708
+ const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index, collectAssets);
709
+ if (!collectAssets) {
710
+ return returnSentence;
711
+ }
712
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList。
713
+ // 必须就地追加:如果写成 [...assetsList, ...sentenceAssets],每条语句都要复制
714
+ // 一次已累积的列表,整体退化成 Θ(资源数²)——资源密集的场景脚本(几乎每条语句
715
+ // 都带 bg / figure / vocal)在数千条语句规模下会明显变慢。
716
+ for (const asset of returnSentence.sentenceAssets) {
717
+ assetsList.push(asset);
718
+ }
719
+ for (const subScene of returnSentence.subScene) {
720
+ subSceneList.push(subScene);
721
+ }
705
722
  return returnSentence;
706
723
  });
707
724
  markMultilineRanges(sentenceList, rawSentenceList);
708
- // 开始资源的预加载
709
- assetsList = deduplicateAssets(assetsList);
710
- assetsPrefetcher(assetsList);
725
+ if (collectAssets) {
726
+ // 开始资源的预加载
727
+ assetsList = deduplicateAssets(assetsList);
728
+ assetsPrefetcher(assetsList);
729
+ }
711
730
  return {
712
731
  sceneName: sceneName,
713
732
  sceneUrl: sceneUrl,
@@ -789,6 +808,10 @@ class SceneParser {
789
808
  assetSetter;
790
809
  ADD_NEXT_ARG_LIST;
791
810
  SCRIPT_CONFIG_MAP;
811
+ /**
812
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
813
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
814
+ */
792
815
  constructor(assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_INPUT) {
793
816
  this.assetsPrefetcher = assetsPrefetcher;
794
817
  this.assetSetter = assetSetter;
@@ -9,7 +9,11 @@ export default class SceneParser {
9
9
  private readonly assetSetter;
10
10
  private readonly ADD_NEXT_ARG_LIST;
11
11
  private readonly SCRIPT_CONFIG_MAP;
12
- constructor(assetsPrefetcher: (assetList: IAsset[]) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
12
+ /**
13
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
14
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
15
+ */
16
+ constructor(assetsPrefetcher: ((assetList: IAsset[]) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
13
17
  /**
14
18
  * 解析场景
15
19
  * @param rawScene 原始场景
@@ -39,7 +39,8 @@ export declare enum commandType {
39
39
  applyStyle = 32,
40
40
  wait = 33,
41
41
  callSteam = 34,
42
- return = 35
42
+ return = 35,
43
+ changeFigureDiff = 36
43
44
  }
44
45
  /**
45
46
  * 单个参数接口
@@ -12,4 +12,4 @@ import { ConfigMap } from './config/scriptConfig';
12
12
  * @param SCRIPT_CONFIG_MAP
13
13
  * @return {IScene} 解析后的场景
14
14
  */
15
- export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: (assetList: Array<IAsset>) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
15
+ export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: ((assetList: Array<IAsset>) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
@@ -6,5 +6,7 @@ import { ConfigMap } from '../config/scriptConfig';
6
6
  * @param assetSetter
7
7
  * @param ADD_NEXT_ARG_LIST
8
8
  * @param SCRIPT_CONFIG_MAP
9
+ * @param lineNumber 语句所在行号
10
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
9
11
  */
10
- export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number) => ISentence;
12
+ export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number, collectAssets?: boolean) => ISentence;
@@ -9,7 +9,11 @@ export default class SceneParser {
9
9
  private readonly assetSetter;
10
10
  private readonly ADD_NEXT_ARG_LIST;
11
11
  private readonly SCRIPT_CONFIG_MAP;
12
- constructor(assetsPrefetcher: (assetList: IAsset[]) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
12
+ /**
13
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
14
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
15
+ */
16
+ constructor(assetsPrefetcher: ((assetList: IAsset[]) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
13
17
  /**
14
18
  * 解析场景
15
19
  * @param rawScene 原始场景
package/build/es/index.js CHANGED
@@ -36,12 +36,14 @@ var commandType;
36
36
  commandType[commandType["wait"] = 33] = "wait";
37
37
  commandType[commandType["callSteam"] = 34] = "callSteam";
38
38
  commandType[commandType["return"] = 35] = "return";
39
+ commandType[commandType["changeFigureDiff"] = 36] = "changeFigureDiff";
39
40
  })(commandType || (commandType = {}));
40
41
 
41
42
  const SCRIPT_CONFIG = [
42
43
  { scriptString: 'say', scriptType: commandType.say },
43
44
  { scriptString: 'changeBg', scriptType: commandType.changeBg },
44
45
  { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
46
+ { scriptString: 'changeFigureDiff', scriptType: commandType.changeFigureDiff },
45
47
  { scriptString: 'bgm', scriptType: commandType.bgm },
46
48
  { scriptString: 'playVideo', scriptType: commandType.video },
47
49
  { scriptString: 'pixiPerform', scriptType: commandType.pixi },
@@ -293,6 +295,7 @@ const contentParser = (contentRaw, type, assetSetter) => {
293
295
  case commandType.changeBg:
294
296
  return assetSetter(contentRaw, fileType.background);
295
297
  case commandType.changeFigure:
298
+ case commandType.changeFigureDiff:
296
299
  return assetSetter(contentRaw, fileType.figure);
297
300
  case commandType.bgm:
298
301
  return assetSetter(contentRaw, fileType.bgm);
@@ -373,7 +376,7 @@ const assetsScanner = (command, content, args, lineNumber) => {
373
376
  type: fileType.background,
374
377
  });
375
378
  }
376
- if (command === commandType.changeFigure) {
379
+ if (command === commandType.changeFigure || command === commandType.changeFigureDiff) {
377
380
  returnAssetsList.push({
378
381
  name: content,
379
382
  url: content,
@@ -437,8 +440,10 @@ const subSceneScanner = (command, content) => {
437
440
  * @param assetSetter
438
441
  * @param ADD_NEXT_ARG_LIST
439
442
  * @param SCRIPT_CONFIG_MAP
443
+ * @param lineNumber 语句所在行号
444
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
440
445
  */
441
- const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0) => {
446
+ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0, collectAssets = true) => {
442
447
  let command; // 默认为对话
443
448
  let content; // 语句内容
444
449
  let subScene; // 语句携带的子场景(可能没有)
@@ -506,8 +511,8 @@ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG
506
511
  }
507
512
  }
508
513
  content = contentParser(newSentenceRaw.trim(), command, assetSetter); // 将语句内容里的文件名转为相对或绝对路径
509
- sentenceAssets = assetsScanner(command, content, args, lineNumber); // 扫描语句携带资源
510
- subScene = subSceneScanner(command, content); // 扫描语句携带子场景
514
+ sentenceAssets = collectAssets ? assetsScanner(command, content, args, lineNumber) : []; // 扫描语句携带资源
515
+ subScene = collectAssets ? subSceneScanner(command, content) : []; // 扫描语句携带子场景
511
516
  return {
512
517
  command: command,
513
518
  commandRaw: commandRaw.trim(),
@@ -684,6 +689,8 @@ function placeholderLine(content = "") {
684
689
  * @return {IScene} 解析后的场景
685
690
  */
686
691
  const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
692
+ // 没有预取回调时不需要场景级资源列表,连同每条语句的资源扫描一起跳过
693
+ const collectAssets = assetsPrefetcher !== undefined;
687
694
  // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
688
695
  // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
689
696
  const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
@@ -694,16 +701,28 @@ const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSette
694
701
  let assetsList = []; // 场景资源列表
695
702
  let subSceneList = []; // 子场景列表
696
703
  const sentenceList = rawSentenceListWithoutEmpty.map((sentence, index) => {
697
- const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index);
698
- // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
699
- assetsList = [...assetsList, ...returnSentence.sentenceAssets];
700
- subSceneList = [...subSceneList, ...returnSentence.subScene];
704
+ const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index, collectAssets);
705
+ if (!collectAssets) {
706
+ return returnSentence;
707
+ }
708
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList。
709
+ // 必须就地追加:如果写成 [...assetsList, ...sentenceAssets],每条语句都要复制
710
+ // 一次已累积的列表,整体退化成 Θ(资源数²)——资源密集的场景脚本(几乎每条语句
711
+ // 都带 bg / figure / vocal)在数千条语句规模下会明显变慢。
712
+ for (const asset of returnSentence.sentenceAssets) {
713
+ assetsList.push(asset);
714
+ }
715
+ for (const subScene of returnSentence.subScene) {
716
+ subSceneList.push(subScene);
717
+ }
701
718
  return returnSentence;
702
719
  });
703
720
  markMultilineRanges(sentenceList, rawSentenceList);
704
- // 开始资源的预加载
705
- assetsList = deduplicateAssets(assetsList);
706
- assetsPrefetcher(assetsList);
721
+ if (collectAssets) {
722
+ // 开始资源的预加载
723
+ assetsList = deduplicateAssets(assetsList);
724
+ assetsPrefetcher(assetsList);
725
+ }
707
726
  return {
708
727
  sceneName: sceneName,
709
728
  sceneUrl: sceneUrl,
@@ -785,6 +804,10 @@ class SceneParser {
785
804
  assetSetter;
786
805
  ADD_NEXT_ARG_LIST;
787
806
  SCRIPT_CONFIG_MAP;
807
+ /**
808
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
809
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
810
+ */
788
811
  constructor(assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_INPUT) {
789
812
  this.assetsPrefetcher = assetsPrefetcher;
790
813
  this.assetSetter = assetSetter;
@@ -39,7 +39,8 @@ export declare enum commandType {
39
39
  applyStyle = 32,
40
40
  wait = 33,
41
41
  callSteam = 34,
42
- return = 35
42
+ return = 35,
43
+ changeFigureDiff = 36
43
44
  }
44
45
  /**
45
46
  * 单个参数接口
@@ -12,4 +12,4 @@ import { ConfigMap } from './config/scriptConfig';
12
12
  * @param SCRIPT_CONFIG_MAP
13
13
  * @return {IScene} 解析后的场景
14
14
  */
15
- export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: (assetList: Array<IAsset>) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
15
+ export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: ((assetList: Array<IAsset>) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
@@ -6,5 +6,7 @@ import { ConfigMap } from '../config/scriptConfig';
6
6
  * @param assetSetter
7
7
  * @param ADD_NEXT_ARG_LIST
8
8
  * @param SCRIPT_CONFIG_MAP
9
+ * @param lineNumber 语句所在行号
10
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
9
11
  */
10
- export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number) => ISentence;
12
+ export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number, collectAssets?: boolean) => ISentence;
@@ -9,7 +9,11 @@ export default class SceneParser {
9
9
  private readonly assetSetter;
10
10
  private readonly ADD_NEXT_ARG_LIST;
11
11
  private readonly SCRIPT_CONFIG_MAP;
12
- constructor(assetsPrefetcher: (assetList: IAsset[]) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
12
+ /**
13
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
14
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
15
+ */
16
+ constructor(assetsPrefetcher: ((assetList: IAsset[]) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: number[], SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap);
13
17
  /**
14
18
  * 解析场景
15
19
  * @param rawScene 原始场景
@@ -39,7 +39,8 @@ export declare enum commandType {
39
39
  applyStyle = 32,
40
40
  wait = 33,
41
41
  callSteam = 34,
42
- return = 35
42
+ return = 35,
43
+ changeFigureDiff = 36
43
44
  }
44
45
  /**
45
46
  * 单个参数接口
@@ -12,4 +12,4 @@ import { ConfigMap } from './config/scriptConfig';
12
12
  * @param SCRIPT_CONFIG_MAP
13
13
  * @return {IScene} 解析后的场景
14
14
  */
15
- export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: (assetList: Array<IAsset>) => void, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
15
+ export declare const sceneParser: (rawScene: string, sceneName: string, sceneUrl: string, assetsPrefetcher: ((assetList: Array<IAsset>) => void) | undefined, assetSetter: (fileName: string, assetType: fileType) => string, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap) => IScene;
@@ -6,5 +6,7 @@ import { ConfigMap } from '../config/scriptConfig';
6
6
  * @param assetSetter
7
7
  * @param ADD_NEXT_ARG_LIST
8
8
  * @param SCRIPT_CONFIG_MAP
9
+ * @param lineNumber 语句所在行号
10
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
9
11
  */
10
- export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number) => ISentence;
12
+ export declare const scriptParser: (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: commandType[], SCRIPT_CONFIG_MAP: ConfigMap, lineNumber?: number, collectAssets?: boolean) => ISentence;
@@ -39,12 +39,14 @@ var webgalParser = (function (exports) {
39
39
  commandType[commandType["wait"] = 33] = "wait";
40
40
  commandType[commandType["callSteam"] = 34] = "callSteam";
41
41
  commandType[commandType["return"] = 35] = "return";
42
+ commandType[commandType["changeFigureDiff"] = 36] = "changeFigureDiff";
42
43
  })(commandType || (commandType = {}));
43
44
 
44
45
  const SCRIPT_CONFIG = [
45
46
  { scriptString: 'say', scriptType: commandType.say },
46
47
  { scriptString: 'changeBg', scriptType: commandType.changeBg },
47
48
  { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
49
+ { scriptString: 'changeFigureDiff', scriptType: commandType.changeFigureDiff },
48
50
  { scriptString: 'bgm', scriptType: commandType.bgm },
49
51
  { scriptString: 'playVideo', scriptType: commandType.video },
50
52
  { scriptString: 'pixiPerform', scriptType: commandType.pixi },
@@ -296,6 +298,7 @@ var webgalParser = (function (exports) {
296
298
  case commandType.changeBg:
297
299
  return assetSetter(contentRaw, fileType.background);
298
300
  case commandType.changeFigure:
301
+ case commandType.changeFigureDiff:
299
302
  return assetSetter(contentRaw, fileType.figure);
300
303
  case commandType.bgm:
301
304
  return assetSetter(contentRaw, fileType.bgm);
@@ -376,7 +379,7 @@ var webgalParser = (function (exports) {
376
379
  type: fileType.background,
377
380
  });
378
381
  }
379
- if (command === commandType.changeFigure) {
382
+ if (command === commandType.changeFigure || command === commandType.changeFigureDiff) {
380
383
  returnAssetsList.push({
381
384
  name: content,
382
385
  url: content,
@@ -440,8 +443,10 @@ var webgalParser = (function (exports) {
440
443
  * @param assetSetter
441
444
  * @param ADD_NEXT_ARG_LIST
442
445
  * @param SCRIPT_CONFIG_MAP
446
+ * @param lineNumber 语句所在行号
447
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
443
448
  */
444
- const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0) => {
449
+ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, lineNumber = 0, collectAssets = true) => {
445
450
  let command; // 默认为对话
446
451
  let content; // 语句内容
447
452
  let subScene; // 语句携带的子场景(可能没有)
@@ -509,8 +514,8 @@ var webgalParser = (function (exports) {
509
514
  }
510
515
  }
511
516
  content = contentParser(newSentenceRaw.trim(), command, assetSetter); // 将语句内容里的文件名转为相对或绝对路径
512
- sentenceAssets = assetsScanner(command, content, args, lineNumber); // 扫描语句携带资源
513
- subScene = subSceneScanner(command, content); // 扫描语句携带子场景
517
+ sentenceAssets = collectAssets ? assetsScanner(command, content, args, lineNumber) : []; // 扫描语句携带资源
518
+ subScene = collectAssets ? subSceneScanner(command, content) : []; // 扫描语句携带子场景
514
519
  return {
515
520
  command: command,
516
521
  commandRaw: commandRaw.trim(),
@@ -687,6 +692,8 @@ var webgalParser = (function (exports) {
687
692
  * @return {IScene} 解析后的场景
688
693
  */
689
694
  const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
695
+ // 没有预取回调时不需要场景级资源列表,连同每条语句的资源扫描一起跳过
696
+ const collectAssets = assetsPrefetcher !== undefined;
690
697
  // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
691
698
  // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
692
699
  const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
@@ -697,16 +704,28 @@ var webgalParser = (function (exports) {
697
704
  let assetsList = []; // 场景资源列表
698
705
  let subSceneList = []; // 子场景列表
699
706
  const sentenceList = rawSentenceListWithoutEmpty.map((sentence, index) => {
700
- const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index);
701
- // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
702
- assetsList = [...assetsList, ...returnSentence.sentenceAssets];
703
- subSceneList = [...subSceneList, ...returnSentence.subScene];
707
+ const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index, collectAssets);
708
+ if (!collectAssets) {
709
+ return returnSentence;
710
+ }
711
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList。
712
+ // 必须就地追加:如果写成 [...assetsList, ...sentenceAssets],每条语句都要复制
713
+ // 一次已累积的列表,整体退化成 Θ(资源数²)——资源密集的场景脚本(几乎每条语句
714
+ // 都带 bg / figure / vocal)在数千条语句规模下会明显变慢。
715
+ for (const asset of returnSentence.sentenceAssets) {
716
+ assetsList.push(asset);
717
+ }
718
+ for (const subScene of returnSentence.subScene) {
719
+ subSceneList.push(subScene);
720
+ }
704
721
  return returnSentence;
705
722
  });
706
723
  markMultilineRanges(sentenceList, rawSentenceList);
707
- // 开始资源的预加载
708
- assetsList = deduplicateAssets(assetsList);
709
- assetsPrefetcher(assetsList);
724
+ if (collectAssets) {
725
+ // 开始资源的预加载
726
+ assetsList = deduplicateAssets(assetsList);
727
+ assetsPrefetcher(assetsList);
728
+ }
710
729
  return {
711
730
  sceneName: sceneName,
712
731
  sceneUrl: sceneUrl,
@@ -788,6 +807,10 @@ var webgalParser = (function (exports) {
788
807
  assetSetter;
789
808
  ADD_NEXT_ARG_LIST;
790
809
  SCRIPT_CONFIG_MAP;
810
+ /**
811
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
812
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
813
+ */
791
814
  constructor(assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_INPUT) {
792
815
  this.assetsPrefetcher = assetsPrefetcher;
793
816
  this.assetSetter = assetSetter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webgal-parser",
3
- "version": "4.6.3-beta.2",
3
+ "version": "4.6.5-beta.1",
4
4
  "description": "WebGAL script parser",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -4,6 +4,7 @@ export const SCRIPT_CONFIG = [
4
4
  { scriptString: 'say', scriptType: commandType.say },
5
5
  { scriptString: 'changeBg', scriptType: commandType.changeBg },
6
6
  { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
7
+ { scriptString: 'changeFigureDiff', scriptType: commandType.changeFigureDiff },
7
8
  { scriptString: 'bgm', scriptType: commandType.bgm },
8
9
  { scriptString: 'playVideo', scriptType: commandType.video },
9
10
  { scriptString: 'pixiPerform', scriptType: commandType.pixi },
package/src/index.ts CHANGED
@@ -17,8 +17,12 @@ import {
17
17
 
18
18
  export default class SceneParser {
19
19
  private readonly SCRIPT_CONFIG_MAP: ConfigMap;
20
+ /**
21
+ * @param assetsPrefetcher 资源预取回调。省略时不收集资源:`sentenceAssets`、`subScene`、
22
+ * `assetsList`、`subSceneList` 均为空数组,适用于只需要语句结构的调用方(编辑器语法解析等)。
23
+ */
20
24
  constructor(
21
- private readonly assetsPrefetcher: (assetList: IAsset[]) => void,
25
+ private readonly assetsPrefetcher: ((assetList: IAsset[]) => void) | undefined,
22
26
  private readonly assetSetter: (
23
27
  fileName: string,
24
28
  assetType: fileType,
@@ -41,6 +41,7 @@ export enum commandType {
41
41
  wait,
42
42
  callSteam, // 调用Steam功能
43
43
  return, // 从被调用的场景返回
44
+ changeFigureDiff, // 切换等尺寸静态立绘差分;追加以保持旧指令编号
44
45
  }
45
46
 
46
47
  /**
@@ -72,8 +73,8 @@ export interface ISentence {
72
73
  commandRaw: string; // 命令的原始内容,方便调试
73
74
  content: string; // 语句内容
74
75
  args: Array<arg>; // 参数列表
75
- sentenceAssets: Array<IAsset>; // 语句携带的资源列表
76
- subScene: Array<string>; // 语句包含子场景列表
76
+ sentenceAssets: Array<IAsset>; // 语句携带的资源列表;SceneParser 未提供 assetsPrefetcher 时为空
77
+ subScene: Array<string>; // 语句包含子场景列表;未提供 assetsPrefetcher 时为空
77
78
  inlineComment: string; // 行内注释
78
79
  /**
79
80
  * 本语句在原始场景文件中占据的行范围(0-based,含首尾)。
@@ -94,8 +95,8 @@ export interface IScene {
94
95
  sceneName: string; // 场景名称
95
96
  sceneUrl: string; // 场景url
96
97
  sentenceList: Array<ISentence>; // 语句列表
97
- assetsList: Array<IAsset>; // 资源列表
98
- subSceneList: Array<string>; // 子场景的url列表
98
+ assetsList: Array<IAsset>; // 资源列表;未提供 assetsPrefetcher 时为空
99
+ subSceneList: Array<string>; // 子场景的url列表;未提供 assetsPrefetcher 时为空
99
100
  }
100
101
 
101
102
  /**
@@ -27,11 +27,14 @@ export const sceneParser = (
27
27
  rawScene: string,
28
28
  sceneName: string,
29
29
  sceneUrl: string,
30
- assetsPrefetcher: (assetList: Array<IAsset>) => void,
30
+ assetsPrefetcher: ((assetList: Array<IAsset>) => void) | undefined,
31
31
  assetSetter: (fileName: string, assetType: fileType) => string,
32
32
  ADD_NEXT_ARG_LIST: commandType[],
33
33
  SCRIPT_CONFIG_MAP: ConfigMap,
34
34
  ): IScene => {
35
+ // 没有预取回调时不需要场景级资源列表,连同每条语句的资源扫描一起跳过
36
+ const collectAssets = assetsPrefetcher !== undefined;
37
+
35
38
  // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
36
39
  // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
37
40
  const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
@@ -50,19 +53,32 @@ export const sceneParser = (
50
53
  ADD_NEXT_ARG_LIST,
51
54
  SCRIPT_CONFIG_MAP,
52
55
  index,
56
+ collectAssets,
53
57
  );
54
- // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
55
- assetsList = [...assetsList, ...returnSentence.sentenceAssets];
56
- subSceneList = [...subSceneList, ...returnSentence.subScene];
58
+ if (!collectAssets) {
59
+ return returnSentence;
60
+ }
61
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList。
62
+ // 必须就地追加:如果写成 [...assetsList, ...sentenceAssets],每条语句都要复制
63
+ // 一次已累积的列表,整体退化成 Θ(资源数²)——资源密集的场景脚本(几乎每条语句
64
+ // 都带 bg / figure / vocal)在数千条语句规模下会明显变慢。
65
+ for (const asset of returnSentence.sentenceAssets) {
66
+ assetsList.push(asset);
67
+ }
68
+ for (const subScene of returnSentence.subScene) {
69
+ subSceneList.push(subScene);
70
+ }
57
71
  return returnSentence;
58
72
  },
59
73
  );
60
74
 
61
75
  markMultilineRanges(sentenceList, rawSentenceList);
62
76
 
63
- // 开始资源的预加载
64
- assetsList = deduplicateAssets(assetsList);
65
- assetsPrefetcher(assetsList);
77
+ if (collectAssets) {
78
+ // 开始资源的预加载
79
+ assetsList = deduplicateAssets(assetsList);
80
+ assetsPrefetcher(assetsList);
81
+ }
66
82
 
67
83
  return {
68
84
  sceneName: sceneName, // 场景名称
@@ -41,7 +41,7 @@ export const assetsScanner = (
41
41
  type: fileType.background,
42
42
  });
43
43
  }
44
- if (command === commandType.changeFigure) {
44
+ if (command === commandType.changeFigure || command === commandType.changeFigureDiff) {
45
45
  returnAssetsList.push({
46
46
  name: content,
47
47
  url: content,
@@ -22,6 +22,7 @@ export const contentParser = (
22
22
  case commandType.changeBg:
23
23
  return assetSetter(contentRaw, fileType.background);
24
24
  case commandType.changeFigure:
25
+ case commandType.changeFigureDiff:
25
26
  return assetSetter(contentRaw, fileType.figure);
26
27
  case commandType.bgm:
27
28
  return assetSetter(contentRaw, fileType.bgm);
@@ -18,6 +18,8 @@ import { ConfigMap } from '../config/scriptConfig';
18
18
  * @param assetSetter
19
19
  * @param ADD_NEXT_ARG_LIST
20
20
  * @param SCRIPT_CONFIG_MAP
21
+ * @param lineNumber 语句所在行号
22
+ * @param collectAssets 是否扫描语句携带的资源与子场景;为 false 时两者都返回空数组
21
23
  */
22
24
  export const scriptParser = (
23
25
  sentenceRaw: string,
@@ -25,6 +27,7 @@ export const scriptParser = (
25
27
  ADD_NEXT_ARG_LIST: commandType[],
26
28
  SCRIPT_CONFIG_MAP: ConfigMap,
27
29
  lineNumber = 0,
30
+ collectAssets = true,
28
31
  ): ISentence => {
29
32
  let command: commandType; // 默认为对话
30
33
  let content: string; // 语句内容
@@ -109,8 +112,8 @@ export const scriptParser = (
109
112
  }
110
113
 
111
114
  content = contentParser(newSentenceRaw.trim(), command, assetSetter); // 将语句内容里的文件名转为相对或绝对路径
112
- sentenceAssets = assetsScanner(command, content, args, lineNumber); // 扫描语句携带资源
113
- subScene = subSceneScanner(command, content); // 扫描语句携带子场景
115
+ sentenceAssets = collectAssets ? assetsScanner(command, content, args, lineNumber) : []; // 扫描语句携带资源
116
+ subScene = collectAssets ? subSceneScanner(command, content) : []; // 扫描语句携带子场景
114
117
  return {
115
118
  command: command, // 语句类型
116
119
  commandRaw: commandRaw.trim(), // 命令原始内容,方便调试