kipphi 2.0.1 → 2.1.0

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/chart.ts CHANGED
@@ -1,8 +1,8 @@
1
-
2
- import { VERSION } from "./version";
1
+ import { SCHEMA, VERSION } from "./version";
3
2
 
4
3
  import {
5
- TimeCalculator} from "./bpm";
4
+ TimeCalculator
5
+ } from "./bpm";
6
6
 
7
7
  import {
8
8
  BezierEasing,
@@ -56,10 +56,16 @@ import {
56
56
  type EventNodeSequenceDataKPA2,
57
57
  type ChartDataKPA2,
58
58
  type EventDataKPA2,
59
- InterpreteAs
59
+ InterpreteAs,
60
+ MacroEvaluatorBodyData,
61
+ MacroEvaluatorDataKPA2,
62
+ FinalEventStartNodeDataKPA2,
63
+ MacroData,
64
+ MacroLink
60
65
  } from "./chartTypes";
61
66
  import { ColorEasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type EasedEvaluatorOfType } from "./evaluator";
62
67
  import { err, ERROR_IDS, KPAError } from "./env";
68
+ import { MacroLib, EventMacroTime, EventMacroValue, EventMacro } from "./macro";
63
69
 
64
70
  /// #declaration:global
65
71
 
@@ -72,51 +78,95 @@ export type BasicEventName = "moveX" | "moveY" | "rotate" | "alpha" | "speed";
72
78
 
73
79
  export type UIName = "combo" | "combonumber" | "score" | "pause" | "bar" | "name" | "level"
74
80
 
81
+ /**
82
+ * 表示一张谱面的核心数据结构
83
+ *
84
+ * 包含了谱面的所有元素:判定线、音符、事件序列等信息
85
+ */
75
86
  export class Chart {
87
+ /** 谱面中所有的判定线列表 */
76
88
  judgeLines: JudgeLine[] = [];
77
- timeCalculator = new TimeCalculator();
89
+ /** 时间计算器,用于处理BPM变化和时间转换 */
90
+ readonly timeCalculator = new TimeCalculator();
91
+ /** 无父级的根判定线列表 */
78
92
  orphanLines: JudgeLine[] = [];
79
- // comboMapping: ComboMapping;
93
+
94
+
95
+
96
+ /** 谱面名称 */
80
97
  name: string = "unknown";
98
+ /** 谱面难度等级 */
81
99
  level: string = "unknown";
100
+ /** 曲师信息 */
82
101
  composer: string = "unknown";
102
+ /** 谱师信息 */
83
103
  charter: string = "unknown";
104
+ /** 插画师信息 */
84
105
  illustrator: string = "unknown";
106
+
107
+ /** 谱面偏移时间(秒) */
85
108
  offset: number = 0;
86
109
 
87
- templateEasingLib = new TemplateEasingLib(EventNodeSequence.newSeq<EventType.easing>, ExpressionEvaluator);
88
- sequenceMap = new Map<string, EventNodeSequence<EventValueESType>>();
89
-
110
+ /** 模板缓动库,用于管理和复用缓动函数 */
111
+ readonly templateEasingLib = new TemplateEasingLib(EventNodeSequence.newSeq<EventType.easing>, ExpressionEvaluator);
112
+ readonly macroLib = new MacroLib();
113
+
114
+ /** 事件序列映射表,通过ID索引事件序列 */
115
+ readonly sequenceMap = new Map<string, EventNodeSequence<EventValueESType>>();
116
+ /** 有效节拍数(基于谱面持续时间计算得出) */
90
117
  effectiveBeats: number;
118
+ /** 音符节点列表,用于管理谱面上的所有音符 */
91
119
  nnnList: NNNList;
92
- /** */
120
+ /** 判定线组列表,用于组织和分类判定线 */
93
121
  judgeLineGroups: JudgeLineGroup[] = [];
122
+ /** 谱面持续时间(秒) */
94
123
  duration: number;
95
124
 
96
- // 以分钟计
97
- chartingTime: number;
98
- rpeChartingTime: number;
125
+ /** 谱面制作所用时间(以秒计) */
126
+ chartingSeconds: number;
127
+ /** RPE格式的谱面制作时间(以秒计) */
128
+ rpeChartingSeconds: number;
99
129
 
100
130
 
131
+ /** 标记谱面是否已被修改 */
101
132
  modified: boolean = false;
133
+ /** 谱面最大连击数 */
102
134
  maxCombo: number = 0;
103
135
 
104
136
 
137
+ /** 暂停按钮绑定的判定线 */
105
138
  pauseAttach: JudgeLine | null = null;
139
+ /** 连击数字绑定的判定线 */
106
140
  combonumberAttach: JudgeLine | null = null;
141
+ /** 连击标识绑定的判定线 */
107
142
  comboAttach: JudgeLine | null = null;
143
+ /** 进度条绑定的判定线 */
108
144
  barAttach: JudgeLine | null = null;
145
+ /** 分数显示绑定的判定线 */
109
146
  scoreAttach: JudgeLine | null = null;
147
+ /** 歌曲名称显示绑定的判定线 */
110
148
  nameAttach: JudgeLine | null = null;
149
+ /** 难度等级显示绑定的判定线 */
111
150
  levelAttach: JudgeLine | null = null;
112
151
 
113
152
  constructor() {}
153
+
154
+ /**
155
+ * 获取有效节拍数
156
+ * @returns 基于谱面持续时间计算的有效节拍数
157
+ */
114
158
  getEffectiveBeats() {
115
159
  const effectiveBeats = this.timeCalculator.secondsToBeats(this.duration)
116
- console.log(effectiveBeats)
117
160
  this.effectiveBeats = effectiveBeats
118
161
  return this.effectiveBeats
119
162
  }
163
+
164
+ /**
165
+ * 从RPE格式的JSON数据创建谱面对象
166
+ * @param data RPE格式的谱面数据
167
+ * @param duration 谱面持续时间(秒)
168
+ * @returns 创建的Chart对象
169
+ */
120
170
  static fromRPEJSON(data: ChartDataRPE, duration: number) {
121
171
  const chart = new Chart();
122
172
  chart.judgeLineGroups = data.judgeLineGroup.map(group => new JudgeLineGroup(group));
@@ -127,11 +177,10 @@ export class Chart {
127
177
  chart.charter = data.META.charter ?? "unknown";
128
178
  chart.illustrator = data.META.illustration ?? "unknown";
129
179
  chart.duration = duration;
130
- chart.chartingTime = data.kpaChartTime
131
- chart.rpeChartingTime = data.chartTime ? Math.round(data.chartTime / 60) : 0;
132
- chart.chartingTime = 0;
180
+ chart.chartingSeconds = data.kpaChartTime ?? 0;
181
+ chart.rpeChartingSeconds = data.chartTime ?? 0;
182
+ chart.chartingSeconds = 0;
133
183
  chart.initCalculator(data.BPMList)
134
- console.log(chart, chart.getEffectiveBeats())
135
184
  chart.nnnList = new NNNList(chart.getEffectiveBeats())
136
185
 
137
186
 
@@ -149,6 +198,7 @@ export class Chart {
149
198
  const father = data.father === -1 ? null : judgeLineList[data.father];
150
199
  if (father) {
151
200
  father.children.add(line);
201
+ line.father = father;
152
202
  } else {
153
203
  chart.orphanLines.push(line);
154
204
  }
@@ -157,6 +207,11 @@ export class Chart {
157
207
  return chart
158
208
  }
159
209
 
210
+ /**
211
+ * 从KPA格式的JSON数据创建谱面对象
212
+ * @param data KPA格式的谱面数据
213
+ * @returns 创建的Chart对象
214
+ */
160
215
  static fromKPAJSON(data: ChartDataKPA | ChartDataKPA2) {
161
216
  const chart = new Chart();
162
217
 
@@ -168,9 +223,15 @@ export class Chart {
168
223
  chart.charter = data.info.charter ?? "unknown";
169
224
  chart.offset = data.offset;
170
225
  chart.judgeLineGroups = data.judgeLineGroups.map(group => new JudgeLineGroup(group));
171
- chart.chartingTime = data.chartTime ?? 0;
172
- chart.rpeChartingTime = data.rpeChartTime ?? 0;
173
-
226
+
227
+ const interpreteAsSecs = data.version && data.version >= 203;
228
+ if (interpreteAsSecs) {
229
+ chart.chartingSeconds = data.chartTime ?? 0;
230
+ chart.rpeChartingSeconds = data.rpeChartTime ?? 0;
231
+ } else {
232
+ chart.chartingSeconds = data.chartTime ? data.chartTime * 60 : 0;
233
+ chart.rpeChartingSeconds = data.rpeChartTime ? data.rpeChartTime * 60 : 0;
234
+ }
174
235
 
175
236
  chart.initCalculator(data.bpmList);
176
237
  chart.nnnList = new NNNList(chart.getEffectiveBeats());
@@ -197,11 +258,14 @@ export class Chart {
197
258
  seqData.events as EventDataKPA2<VT>[],
198
259
  chart,
199
260
  seqData.id,
200
- seqData.endValue as VT
261
+ seqData.final as FinalEventStartNodeDataKPA2<VT>
201
262
  );
202
263
  sequence.id = seqData.id;
203
264
  chart.sequenceMap.set(sequence.id, sequence);
204
265
  }
266
+
267
+ chart.macroLib.readTimeMacros((data as ChartDataKPA2).timeMacros ?? []);
268
+ chart.macroLib.readValueMacros((data as ChartDataKPA2).valueMacros ?? []);
205
269
  } else {
206
270
 
207
271
  const sequences = (data as ChartDataKPA).eventNodeSequences
@@ -250,11 +314,21 @@ export class Chart {
250
314
  }
251
315
  return chart;
252
316
  }
317
+
318
+ /**
319
+ * 初始化时间计算器
320
+ * @param bpmList BPM变化列表
321
+ */
253
322
  initCalculator(bpmList: BPMSegmentData[]) {
254
323
  this.timeCalculator.bpmList = bpmList;
255
324
  this.timeCalculator.duration = this.duration;
256
325
  this.timeCalculator.initSequence()
257
326
  }
327
+
328
+ /**
329
+ * 更新有效节拍数
330
+ * @param duration 新的持续时间
331
+ */
258
332
  updateEffectiveBeats(duration: number) {
259
333
  const EB = this.timeCalculator.secondsToBeats(duration);
260
334
  for (let i = 0; i < this.judgeLines.length; i++) {
@@ -262,6 +336,11 @@ export class Chart {
262
336
  judgeLine.updateEffectiveBeats(EB);
263
337
  }
264
338
  }
339
+
340
+ /**
341
+ * 导出为KPA格式数据
342
+ * @returns KPA格式的谱面数据对象
343
+ */
265
344
  dumpKPA(): Required<ChartDataKPA2> {
266
345
  const eventNodeSequenceCollector = new Set<EventNodeSequence>();
267
346
  const orphanLines = [];
@@ -275,10 +354,14 @@ export class Chart {
275
354
  }
276
355
  return {
277
356
  version: VERSION,
357
+ $schema: SCHEMA,
278
358
  duration: this.duration,
279
359
  bpmList: this.timeCalculator.dump(),
280
360
  templateEasings: envEasings,
281
361
  wrapperEasings: this.templateEasingLib.dumpWrapperEasings(),
362
+ macroEvaluators: this.macroLib.dumpMacroEvaluators(),
363
+ timeMacros: this.macroLib.dumpTimeMacros(),
364
+ valueMacros: this.macroLib.dumpValueMacros(),
282
365
  eventNodeSequences: eventNodeSequenceData,
283
366
  info: {
284
367
  level: this.level,
@@ -299,18 +382,25 @@ export class Chart {
299
382
  offset: this.offset,
300
383
  orphanLines: orphanLines,
301
384
  judgeLineGroups: this.judgeLineGroups.map(g => g.name),
302
- chartTime: this.chartingTime,
303
- rpeChartTime: this.rpeChartingTime
385
+ chartTime: this.chartingSeconds,
386
+ rpeChartTime: this.rpeChartingSeconds
304
387
  };
305
388
  }
389
+
390
+ /**
391
+ * 创建一个新的二级音符节点
392
+ * @param time 节点时间
393
+ * @returns 新创建的NNNode对象
394
+ */
306
395
  createNNNode(time: TimeT) {
307
396
  return new NNNode(time)
308
397
  }
398
+
309
399
  /**
310
- *
311
- * @param type
312
- * @param name
313
- * @returns
400
+ * 创建一个新的事件节点序列
401
+ * @param type 事件类型
402
+ * @param name 序列名称(ID)
403
+ * @returns 新创建的事件节点序列
314
404
  * @throws {KPAError<ERROR_IDS.SEQUENCE_NAME_OCCUPIED>}
315
405
  */
316
406
  createEventNodeSequence<T extends EventType>(type: T, name: string) {
@@ -322,6 +412,15 @@ export class Chart {
322
412
  this.sequenceMap.set(name, seq);
323
413
  return seq;
324
414
  }
415
+
416
+ registerEventNodeSequence<T extends EventType>(type: T, name: string, seq: EventNodeSequence<T>) {
417
+ if (this.sequenceMap.has(name)) {
418
+ throw err.SEQUENCE_NAME_OCCUPIED(name);
419
+ }
420
+ seq.id = name;
421
+ this.sequenceMap.set(name, seq);
422
+ }
423
+
325
424
  /**
326
425
  * 对谱面物量进行重新计数。
327
426
  *
@@ -346,6 +445,7 @@ export class Chart {
346
445
  }
347
446
  this.maxCombo = combo;
348
447
  }
448
+
349
449
  /**
350
450
  * 将UI绑定到某判定线
351
451
  * @param ui UI名称,与RPEJSON中的代号相同
@@ -360,6 +460,7 @@ export class Chart {
360
460
  this[key] = judgeLine;
361
461
  judgeLine.hasAttachUI = true;
362
462
  }
463
+
363
464
  /**
364
465
  * 移除谱面中某个UI的绑定,使UI进入未绑定状态
365
466
  * @param ui UI名称,与RPEJSON中的相同
@@ -384,6 +485,12 @@ export class Chart {
384
485
  judgeLine.hasAttachUI = false;
385
486
  }
386
487
  }
488
+
489
+ /**
490
+ * 查询指定判定线上绑定的UI组件
491
+ * @param judgeLine 目标判定线
492
+ * @returns 绑定到该判定线上的UI组件名称数组
493
+ */
387
494
  queryJudgeLineUI(judgeLine: JudgeLine): UIName[] {
388
495
  const arr: UIName[] = [];
389
496
  for (const ui of ["combo", "combonumber", "score", "pause", "bar", "name", "level"] satisfies UIName[]) {
@@ -393,11 +500,12 @@ export class Chart {
393
500
  }
394
501
  return arr;
395
502
  }
503
+
396
504
  /**
397
505
  * 扫描所有用到的判定线贴图(纹理)并返回
398
506
  *
399
507
  * 给谱面播放器的接口,谱面播放器需要在初加载时提供贴图
400
- * @returns
508
+ * @returns 所有使用的纹理名称集合
401
509
  */
402
510
  scanAllTextures() {
403
511
  const textures: Set<string> = new Set;
@@ -406,12 +514,13 @@ export class Chart {
406
514
  }
407
515
  return textures
408
516
  }
517
+
409
518
  /**
410
519
  * 使用KPA2数据创建一个缓动对象。
411
520
  *
412
521
  * 只有对贝塞尔缓动和截段缓动才会创建新对象,其他几种缓动从缓动库等中的对象池获取
413
- * @param data
414
- * @returns
522
+ * @param data 缓动数据
523
+ * @returns 创建的缓动对象
415
524
  */
416
525
  createEasingFromData(data: EasingDataKPA2) {
417
526
  switch (data.type) {
@@ -420,37 +529,43 @@ export class Chart {
420
529
  case EasingType.normal:
421
530
  return rpeEasingArray[data.identifier];
422
531
  case EasingType.segmented:
423
- return new SegmentedEasing(this.createEasingFromData(data), data.left, data.right);
532
+ return new SegmentedEasing(this.createEasingFromData(data.inner), data.left, data.right);
424
533
  case EasingType.template:
425
534
  return this.templateEasingLib.get(data.identifier);
426
535
  case EasingType.wrapper:
427
536
  return this.templateEasingLib.getWrapper(data.identifier);
428
537
  }
429
538
  }
539
+
430
540
  /**
431
541
  * 使用KPA2数据创建一个求值器对象。
432
542
  *
433
543
  * 求值器只有缓动型和表达式型两类。
434
- * @param data
435
- * @param type
436
- * @returns
544
+ * @param data 求值器数据
545
+ * @param type 事件值类型
546
+ * @returns 创建的求值器对象
437
547
  */
438
- createEvaluator<T extends EventValueESType>(data: EvaluatorDataKPA2<T>, type: EventValueTypeOfType<T>): Evaluator<T> {
548
+ bindEvaluator<T extends EventValueESType>(node: EventStartNode<T>, data: EvaluatorDataKPA2<T>, type: EventValueTypeOfType<T>, pos: string) {
439
549
  switch (data.type) {
440
550
  case EvaluatorType.eased:
441
551
  // 我管你这的那的 —— 小奶椰
442
- return this.createEasedEvaluator(data, type) as unknown as Evaluator<T>;
552
+ node.evaluator = this.createEasedEvaluator(data, type) as unknown as Evaluator<T>;
553
+ break;
443
554
  case EvaluatorType.expressionbased:
444
- return this.createExpressionEvaluator(data) as ExpressionEvaluator<T>;
555
+ node.evaluator = this.createExpressionEvaluator(data) as ExpressionEvaluator<T>;
556
+ break;
557
+ case EvaluatorType.macro:
558
+ this.bindMacroEvaluator(node, data, pos);
445
559
  }
446
560
  }
561
+
447
562
  /**
448
563
  * 使用KPA2数据创建一个缓动求值器。
449
564
  *
450
565
  * 对于普通缓动,这些求值器是从对应类构造器的静态对象池属性中获取的。
451
- * @param data
452
- * @param type
453
- * @returns
566
+ * @param data 缓动求值器数据
567
+ * @param type 事件值类型
568
+ * @returns 创建的缓动求值器对象
454
569
  */
455
570
  createEasedEvaluator<T extends EventValueESType>(data: EasedEvaluatorDataOfType<T>, type: EventValueTypeOfType<T>): EasedEvaluatorOfType<T> {
456
571
  switch (type) {
@@ -468,13 +583,14 @@ export class Chart {
468
583
  : new TextEasedEvaluator(this.createEasingFromData(data.easing), (data as TextEasedEvaluatorKPA2).interpretedAs) as EasedEvaluatorOfType<T>
469
584
  }
470
585
  }
586
+
471
587
  /**
472
588
  * 用一个缓动和事件类型获取一个缓动求值器
473
- * @param easing
474
- * @param type
475
- * @param interpreteAs
589
+ * @param easing 缓动对象
590
+ * @param type 事件值类型
591
+ * @param interpreteAs 文本解释方式
592
+ * @returns 对应类型的缓动求值器
476
593
  */
477
- getEasedEvaluator<T extends string>(easing: Easing, type: EventValueType.text, interpreteAs: InterpreteAs): TextEasedEvaluator;
478
594
  getEasedEvaluator<T extends EventValueESType>(easing: Easing, type: EventValueTypeOfType<T>, interpreteAs?: InterpreteAs): EasedEvaluatorOfType<T> {
479
595
  const easingIsNormal = easing instanceof NormalEasing;
480
596
  switch (type) {
@@ -492,22 +608,111 @@ export class Chart {
492
608
  : new TextEasedEvaluator(easing, interpreteAs) as EasedEvaluatorOfType<T>
493
609
  }
494
610
  }
611
+
612
+ /**
613
+ * 根据JavaScript表达式创建表达式求值器
614
+ * @param data 表达式求值器数据
615
+ * @returns 创建的表达式求值器对象
616
+ */
495
617
  createExpressionEvaluator<T extends EventValueESType>(data: ExpressionEvaluatorDataKPA2) {
496
618
  return new ExpressionEvaluator<T>(data.jsExpr);
497
619
  }
620
+
621
+ bindMacroEvaluator(node: EventStartNode<EventValueESType>, data: MacroEvaluatorDataKPA2, pos: string) {
622
+ const key = data.name;
623
+ if (!key) {
624
+ throw err.MISSING_MACRO_EVALUATOR_KEY(pos);
625
+ }
626
+ const evaluator = this.macroLib.macroEvaluators.get(key);
627
+ if (!evaluator) {
628
+ throw err.MACRO_EVALUATOR_NOT_FOUND(key, pos);
629
+ }
630
+ node.evaluator = evaluator;
631
+ evaluator.consumers.set(node, new ExpressionEvaluator(data.compiled || "0"));
632
+ }
633
+
498
634
  /**
499
635
  * 使用KPA2JSON创建一对面对面节点
500
- * @param data
501
- * @param type
502
- * @returns
636
+ * @param data 事件数据
637
+ * @param type 事件值类型
638
+ * @returns 包含起始节点和结束节点的元组
503
639
  */
504
- createEventFromData<VT extends EventValueESType>(data: EventDataKPA2<VT>, type: EventValueTypeOfType<VT>): [EventStartNode<VT>, EventEndNode<VT>] {
640
+ createEventFromData<VT extends EventValueESType>(data: EventDataKPA2<VT>, type: EventValueTypeOfType<VT>, pos: string): [EventStartNode<VT>, EventEndNode<VT>] {
505
641
  const start = new EventStartNode(data.startTime, data.start);
506
642
  const end = new EventEndNode(data.endTime, data.end);
507
- start.evaluator = this.createEvaluator(data.evaluator, type);
643
+ this.bindEvaluator(start, data.evaluator, type, pos);
644
+ if (data.macroStart) {
645
+ this.bindValueMacro(start, data.macroStart, pos)
646
+ }
647
+ if (data.macroEnd) {
648
+ this.bindValueMacro(end, data.macroEnd, pos)
649
+ }
650
+ if (data.macroStartTime) {
651
+ this.bindTimeMacro(start, data.macroStartTime, pos)
652
+ }
653
+ if (data.startLinkedMacro) {
654
+ for (const macroLink of data.startLinkedMacro) {
655
+ this.linkMacro(start, macroLink, pos)
656
+ }
657
+ }
658
+ if (data.endLinkedMacro) {
659
+ for (const macroLink of data.endLinkedMacro) {
660
+ this.linkMacro(end, macroLink, pos)
661
+ }
662
+ }
508
663
  EventNode.connect(start, end);
509
664
  return [start, end];
510
665
  }
666
+ createFinalEventStartNodeFromData<VT extends EventValueESType>(data: FinalEventStartNodeDataKPA2<VT>, type: EventValueTypeOfType<VT>, pos: string): EventStartNode<VT> {
667
+ const node = new EventStartNode(data.startTime, data.start);
668
+ this.bindEvaluator(node, data.evaluator, type, pos);
669
+ if (typeof data.macro === "string") {
670
+ this.bindValueMacro(node, data.macro, pos);
671
+ }
672
+ if (typeof data.macroTime === "string") {
673
+ this.bindTimeMacro(node, data.macroTime, pos);
674
+ }
675
+ if (data.linkedMacro) {
676
+ for (const macroLink of data.linkedMacro) {
677
+ this.linkMacro(node, macroLink, pos)
678
+ }
679
+ }
680
+ return node;
681
+ }
682
+ bindTimeMacro(node: EventStartNode<any>, macroData: MacroData, pos: string) {
683
+ const id = macroData[0];
684
+ const macro = this.macroLib.timeMacros.get(id);
685
+ if (typeof macro === "object" && macro instanceof EventMacroTime) {
686
+ node.macroTime = macro;
687
+ macro.bindNode(node, macroData, pos);
688
+ } else {
689
+ err.TIME_MACRO_NOT_FOUND(id, pos).warn();
690
+ }
691
+ return null;
692
+ }
693
+ bindValueMacro(node: EventNode<any>, macroData: MacroData, pos: string) {
694
+ const id = macroData[0];
695
+ const macro = this.macroLib.valueMacros.get(id);
696
+ if (typeof macro === "object" && macro instanceof EventMacroValue) {
697
+ node.macroValue = macro;
698
+ macro.bindNode(node, macroData, pos);
699
+ } else {
700
+ err.VALUE_MACRO_NOT_FOUND(id, pos).warn();
701
+ }
702
+ return null;
703
+ }
704
+ linkMacro(node: EventNode<EventValueESType>, linkData: MacroLink, pos: string) {
705
+ const prefix = linkData[0].split(":")[0] as 'time' | 'value';
706
+ const macroDict = prefix === 'time' ? this.macroLib.timeMacros : this.macroLib.valueMacros;
707
+ const realName = linkData[0].substring(prefix.length + 1);
708
+ const macro = macroDict.get(realName);
709
+ if (typeof macro === "object" && macro instanceof EventMacro) {
710
+ macro.linkProtoNode(node, linkData[1]);
711
+ } else {
712
+ err[`${prefix.toUpperCase() as 'TIME' | 'VALUE'}_MACRO_NOT_FOUND`](realName, pos).warn();
713
+ }
714
+ return null;
715
+ }
511
716
  /* 暂时不用此方法,因为谱面播放器里面还是用反解法弄的
512
717
  updateNNListsFromENS(speedENS: SpeedENS) {
513
718
 
@@ -515,14 +720,27 @@ export class Chart {
515
720
  */
516
721
  }
517
722
 
723
+ /**
724
+ * 表示一组判定线的容器
725
+ *
726
+ * 用于组织和管理具有相同属性或用途的判定线集合
727
+ */
518
728
  export class JudgeLineGroup {
519
729
  /**
520
730
  * 该只读标记只是为了防止外部修改,内部可以修改
731
+ *
732
+ * 属于该组的判定线列表,按ID升序排列
521
733
  */
522
734
  judgeLines: readonly JudgeLine[];
735
+
736
+ /**
737
+ * 创建一个新的判定线组
738
+ * @param name 组名称
739
+ */
523
740
  constructor(public name: string) {
524
741
  this.judgeLines = []
525
742
  }
743
+
526
744
  /**
527
745
  * 向判定线组添加一条判定线,并且保证其内部的判定线ID是升序排列的。
528
746
  * @param judgeLine 要添加的判定线
@@ -548,9 +766,10 @@ export class JudgeLineGroup {
548
766
  judgeLines.push(judgeLine);
549
767
 
550
768
  }
769
+
551
770
  /**
552
771
  * 从判定线组移除一条判定线
553
- * @param judgeLine
772
+ * @param judgeLine 要移除的判定线
554
773
  */
555
774
  remove(judgeLine: JudgeLine) {
556
775
  // 只读仅对外部作限制
@@ -560,8 +779,9 @@ export class JudgeLineGroup {
560
779
  judgeLines.splice(index, 1);
561
780
  }
562
781
  }
782
+
563
783
  /**
564
- *
784
+ * 检查该判定线组是否为默认组
565
785
  * @returns 该判定线组是否为默认判定线组,默认的判断标准是:名称为 "Default"(大小写不敏感)
566
786
  */
567
787
  isDefault() {