kipphi 2.0.0 → 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,14 +1,13 @@
1
-
2
- import { VERSION } from "./version";
1
+ import { SCHEMA, VERSION } from "./version";
3
2
 
4
3
  import {
5
- TimeCalculator,
6
- BPMSequence
7
- } from "./time";
4
+ TimeCalculator
5
+ } from "./bpm";
8
6
 
9
7
  import {
10
8
  BezierEasing,
11
9
  Easing,
10
+ NormalEasing,
12
11
  rpeEasingArray,
13
12
  SegmentedEasing,
14
13
  TemplateEasingLib,
@@ -16,13 +15,10 @@ import {
16
15
 
17
16
  import {
18
17
  EventNodeSequence,
19
- type AnyEN,
20
- type ENOrHead,
21
- type ENOrTail,
22
- EventNodeLike,
23
18
  EventStartNode,
24
19
  EventEndNode,
25
20
  EventNode,
21
+ SpeedENS,
26
22
  } from "./event";
27
23
 
28
24
  import {
@@ -34,19 +30,13 @@ import {
34
30
  } from "./judgeline";
35
31
 
36
32
  import {
37
- Note,
38
- NNList,
39
33
  NNNList,
40
- type NNNOrHead,
41
34
  type NNNOrTail,
42
35
  NNNode
43
36
  } from "./note";
44
37
 
45
38
  import {
46
- type RGB,
47
39
  type TimeT,
48
- type EventType,
49
- type EventNodeSequenceDataKPA,
50
40
  type ValueTypeOfEventType,
51
41
  type BPMSegmentData,
52
42
  type ChartDataRPE,
@@ -57,6 +47,7 @@ import {
57
47
  type EvaluatorDataKPA2,
58
48
  EvaluatorType,
59
49
  type EasedEvaluatorDataOfType,
50
+ EventType,
60
51
  EventValueType,
61
52
  type EventValueTypeOfType,
62
53
  type EventValueESType,
@@ -64,11 +55,17 @@ import {
64
55
  type TextEasedEvaluatorKPA2,
65
56
  type EventNodeSequenceDataKPA2,
66
57
  type ChartDataKPA2,
67
- type EventDataKPA2
58
+ type EventDataKPA2,
59
+ InterpreteAs,
60
+ MacroEvaluatorBodyData,
61
+ MacroEvaluatorDataKPA2,
62
+ FinalEventStartNodeDataKPA2,
63
+ MacroData,
64
+ MacroLink
68
65
  } from "./chartTypes";
69
- import { ProgramUpdateLevel } from "typescript";
70
66
  import { ColorEasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type EasedEvaluatorOfType } from "./evaluator";
71
-
67
+ import { err, ERROR_IDS, KPAError } from "./env";
68
+ import { MacroLib, EventMacroTime, EventMacroValue, EventMacro } from "./macro";
72
69
 
73
70
  /// #declaration:global
74
71
 
@@ -76,80 +73,103 @@ import { ColorEasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvalua
76
73
  export type BasicEventName = "moveX" | "moveY" | "rotate" | "alpha" | "speed";
77
74
 
78
75
 
79
- type Plain<T> = Record<string, T>;
80
76
 
81
77
 
82
- /**
83
- * 相当于 Python 推导式
84
- * @param obj
85
- * @param expr
86
- * @param guard
87
- * @returns
88
- */
89
- function dictForIn<T, RT>(obj: Plain<T>, expr: (v: T) => RT, guard?: (v: T) => boolean): Plain<RT> {
90
- let ret: Plain<RT> = {}
91
- for (let key in obj) {
92
- const each = obj[key]
93
- if (!guard || guard && guard(each)) {
94
- ret[key] = expr(each)
95
- }
96
- }
97
- return ret;
98
- }
99
-
100
78
 
101
79
  export type UIName = "combo" | "combonumber" | "score" | "pause" | "bar" | "name" | "level"
102
80
 
81
+ /**
82
+ * 表示一张谱面的核心数据结构
83
+ *
84
+ * 包含了谱面的所有元素:判定线、音符、事件序列等信息
85
+ */
103
86
  export class Chart {
87
+ /** 谱面中所有的判定线列表 */
104
88
  judgeLines: JudgeLine[] = [];
105
- bpmList: BPMSegmentData[] = [];
106
- timeCalculator = new TimeCalculator();
89
+ /** 时间计算器,用于处理BPM变化和时间转换 */
90
+ readonly timeCalculator = new TimeCalculator();
91
+ /** 无父级的根判定线列表 */
107
92
  orphanLines: JudgeLine[] = [];
108
- // comboMapping: ComboMapping;
93
+
94
+
95
+
96
+ /** 谱面名称 */
109
97
  name: string = "unknown";
98
+ /** 谱面难度等级 */
110
99
  level: string = "unknown";
100
+ /** 曲师信息 */
111
101
  composer: string = "unknown";
102
+ /** 谱师信息 */
112
103
  charter: string = "unknown";
104
+ /** 插画师信息 */
113
105
  illustrator: string = "unknown";
106
+
107
+ /** 谱面偏移时间(秒) */
114
108
  offset: number = 0;
115
109
 
116
- templateEasingLib = new TemplateEasingLib;
117
- sequenceMap = new Map<string, EventNodeSequence<any>>();
118
-
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
+ /** 有效节拍数(基于谱面持续时间计算得出) */
119
117
  effectiveBeats: number;
118
+ /** 音符节点列表,用于管理谱面上的所有音符 */
120
119
  nnnList: NNNList;
121
- /** */
120
+ /** 判定线组列表,用于组织和分类判定线 */
122
121
  judgeLineGroups: JudgeLineGroup[] = [];
122
+ /** 谱面持续时间(秒) */
123
123
  duration: number;
124
124
 
125
- // 以分钟计
126
- chartingTime: number;
127
- rpeChartingTime: number;
125
+ /** 谱面制作所用时间(以秒计) */
126
+ chartingSeconds: number;
127
+ /** RPE格式的谱面制作时间(以秒计) */
128
+ rpeChartingSeconds: number;
128
129
 
129
130
 
131
+ /** 标记谱面是否已被修改 */
130
132
  modified: boolean = false;
133
+ /** 谱面最大连击数 */
131
134
  maxCombo: number = 0;
132
135
 
133
136
 
137
+ /** 暂停按钮绑定的判定线 */
134
138
  pauseAttach: JudgeLine | null = null;
139
+ /** 连击数字绑定的判定线 */
135
140
  combonumberAttach: JudgeLine | null = null;
141
+ /** 连击标识绑定的判定线 */
136
142
  comboAttach: JudgeLine | null = null;
143
+ /** 进度条绑定的判定线 */
137
144
  barAttach: JudgeLine | null = null;
145
+ /** 分数显示绑定的判定线 */
138
146
  scoreAttach: JudgeLine | null = null;
147
+ /** 歌曲名称显示绑定的判定线 */
139
148
  nameAttach: JudgeLine | null = null;
149
+ /** 难度等级显示绑定的判定线 */
140
150
  levelAttach: JudgeLine | null = null;
141
151
 
142
152
  constructor() {}
153
+
154
+ /**
155
+ * 获取有效节拍数
156
+ * @returns 基于谱面持续时间计算的有效节拍数
157
+ */
143
158
  getEffectiveBeats() {
144
159
  const effectiveBeats = this.timeCalculator.secondsToBeats(this.duration)
145
- console.log(effectiveBeats)
146
160
  this.effectiveBeats = effectiveBeats
147
161
  return this.effectiveBeats
148
162
  }
163
+
164
+ /**
165
+ * 从RPE格式的JSON数据创建谱面对象
166
+ * @param data RPE格式的谱面数据
167
+ * @param duration 谱面持续时间(秒)
168
+ * @returns 创建的Chart对象
169
+ */
149
170
  static fromRPEJSON(data: ChartDataRPE, duration: number) {
150
171
  const chart = new Chart();
151
172
  chart.judgeLineGroups = data.judgeLineGroup.map(group => new JudgeLineGroup(group));
152
- chart.bpmList = data.BPMList;
153
173
  chart.name = data.META.name;
154
174
  chart.level = data.META.level;
155
175
  chart.offset = data.META.offset;
@@ -157,21 +177,14 @@ export class Chart {
157
177
  chart.charter = data.META.charter ?? "unknown";
158
178
  chart.illustrator = data.META.illustration ?? "unknown";
159
179
  chart.duration = duration;
160
- chart.chartingTime = data.kpaChartTime
161
- chart.rpeChartingTime = data.chartTime ? Math.round(data.chartTime / 60) : 0;
162
- chart.chartingTime = 0;
163
- chart.updateCalculator()
164
- console.log(chart, chart.getEffectiveBeats())
180
+ chart.chartingSeconds = data.kpaChartTime ?? 0;
181
+ chart.rpeChartingSeconds = data.chartTime ?? 0;
182
+ chart.chartingSeconds = 0;
183
+ chart.initCalculator(data.BPMList)
165
184
  chart.nnnList = new NNNList(chart.getEffectiveBeats())
166
185
 
167
- /*
168
- if (data.envEasings) {
169
- chart.templateEasingLib.add(...data.envEasings)
170
-
171
- }
172
- */
173
186
 
174
- // let line = data.judgeLineList[0];
187
+
175
188
  const judgeLineDataList: JudgeLineDataRPE[] = <JudgeLineDataRPE[]>data.judgeLineList;
176
189
  const judgeLineList: JudgeLine[] = judgeLineDataList.map(
177
190
  (lineData, id) =>
@@ -185,6 +198,7 @@ export class Chart {
185
198
  const father = data.father === -1 ? null : judgeLineList[data.father];
186
199
  if (father) {
187
200
  father.children.add(line);
201
+ line.father = father;
188
202
  } else {
189
203
  chart.orphanLines.push(line);
190
204
  }
@@ -193,10 +207,14 @@ export class Chart {
193
207
  return chart
194
208
  }
195
209
 
210
+ /**
211
+ * 从KPA格式的JSON数据创建谱面对象
212
+ * @param data KPA格式的谱面数据
213
+ * @returns 创建的Chart对象
214
+ */
196
215
  static fromKPAJSON(data: ChartDataKPA | ChartDataKPA2) {
197
216
  const chart = new Chart();
198
217
 
199
- chart.bpmList = data.bpmList;
200
218
  chart.duration = data.duration;
201
219
  chart.name = data.info.name;
202
220
  chart.level = data.info.level;
@@ -205,46 +223,80 @@ export class Chart {
205
223
  chart.charter = data.info.charter ?? "unknown";
206
224
  chart.offset = data.offset;
207
225
  chart.judgeLineGroups = data.judgeLineGroups.map(group => new JudgeLineGroup(group));
208
- chart.chartingTime = data.chartTime ?? 0;
209
- chart.rpeChartingTime = data.rpeChartTime ?? 0;
210
- chart.updateCalculator()
211
- chart.nnnList = new NNNList(chart.getEffectiveBeats())
212
- const envEasings = data.envEasings;
213
- const len = envEasings.length
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
+ }
235
+
236
+ chart.initCalculator(data.bpmList);
237
+ chart.nnnList = new NNNList(chart.getEffectiveBeats());
238
+ /**
239
+ * 影响事件的格式、是否存在“求值器”这一中间层以及判定线的属性名
240
+ */
241
+ const isKPA2 = data.version >= 200;
242
+ const templateEasings = isKPA2 ? (data as ChartDataKPA2).templateEasings : (data as ChartDataKPA).envEasings;
243
+ const len = templateEasings.length
214
244
  for (let i = 0; i < len; i++) {
215
- const easingData = envEasings[i];
245
+ const easingData = templateEasings[i];
216
246
  chart.templateEasingLib.require(easingData.name);
217
247
  }
218
248
 
219
- if (data.version >= 200) {
249
+ if (isKPA2) {
250
+ chart.templateEasingLib.readWrapperEasings((data as ChartDataKPA2).wrapperEasings);
220
251
  const sequences = (data as ChartDataKPA2).eventNodeSequences;
221
252
  const length = sequences.length;
222
253
  for (let i = 0; i < length; i++) {
223
254
  const seqData = sequences[i];
224
- const sequence = EventNodeSequence.fromRPEJSON<typeof seqData.type, ValueTypeOfEventType<typeof seqData.type>>(seqData.type, seqData.events, chart, seqData.endValue);
255
+ type VT = ValueTypeOfEventType<typeof seqData.type>
256
+ const sequence = EventNodeSequence.fromKPA2JSON<typeof seqData.type, VT>(
257
+ seqData.type,
258
+ seqData.events as EventDataKPA2<VT>[],
259
+ chart,
260
+ seqData.id,
261
+ seqData.final as FinalEventStartNodeDataKPA2<VT>
262
+ );
225
263
  sequence.id = seqData.id;
226
264
  chart.sequenceMap.set(sequence.id, sequence);
227
265
  }
266
+
267
+ chart.macroLib.readTimeMacros((data as ChartDataKPA2).timeMacros ?? []);
268
+ chart.macroLib.readValueMacros((data as ChartDataKPA2).valueMacros ?? []);
228
269
  } else {
229
270
 
230
271
  const sequences = (data as ChartDataKPA).eventNodeSequences
231
272
  const length = sequences.length
232
273
  for (let i = 0; i < length; i++) {
233
274
  const seqData = sequences[i];
234
- const sequence = EventNodeSequence.fromRPEJSON<typeof seqData.type, ValueTypeOfEventType<typeof seqData.type>>(seqData.type, seqData.events, chart, seqData.endValue);
275
+ const sequence = EventNodeSequence.fromRPEJSON<typeof seqData.type, ValueTypeOfEventType<typeof seqData.type>>(
276
+ seqData.type,
277
+ seqData.events,
278
+ chart,
279
+ seqData.id,
280
+ seqData.endValue);
235
281
  sequence.id = seqData.id;
236
282
  chart.sequenceMap.set(sequence.id, sequence);
237
283
  }
238
284
  }
239
285
 
240
286
  for (let i = 0; i < len; i++) {
241
- const easingData = envEasings[i];
242
- chart.templateEasingLib.implement(easingData.name, chart.sequenceMap.get(easingData.content));
287
+ const easingData = templateEasings[i];
288
+ const sequence = chart.sequenceMap.get(easingData.content);
289
+ if (sequence.type !== EventType.easing) {
290
+ throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_EASING_ENS(easingData.name);
291
+ }
292
+ if (typeof sequence.head.next.value !== "number") {
293
+ throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_NUMERIC_ENS(easingData.name);
294
+ }
295
+ chart.templateEasingLib.implement(easingData.name, sequence as EventNodeSequence<number>);
243
296
  }
244
297
  chart.templateEasingLib.check()
245
- const isOld = !data.version || data.version < 150
246
- for (let lineData of data.orphanLines) {
247
- const line: JudgeLine = JudgeLine.fromKPAJSON(isOld, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
298
+ for (const lineData of data.orphanLines) {
299
+ const line: JudgeLine = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
248
300
  chart.orphanLines.push(line)
249
301
  }
250
302
  chart.judgeLines.sort((a, b) => a.id - b.id);
@@ -262,11 +314,21 @@ export class Chart {
262
314
  }
263
315
  return chart;
264
316
  }
265
- updateCalculator() {
266
- this.timeCalculator.bpmList = this.bpmList;
317
+
318
+ /**
319
+ * 初始化时间计算器
320
+ * @param bpmList BPM变化列表
321
+ */
322
+ initCalculator(bpmList: BPMSegmentData[]) {
323
+ this.timeCalculator.bpmList = bpmList;
267
324
  this.timeCalculator.duration = this.duration;
268
- this.timeCalculator.update()
325
+ this.timeCalculator.initSequence()
269
326
  }
327
+
328
+ /**
329
+ * 更新有效节拍数
330
+ * @param duration 新的持续时间
331
+ */
270
332
  updateEffectiveBeats(duration: number) {
271
333
  const EB = this.timeCalculator.secondsToBeats(duration);
272
334
  for (let i = 0; i < this.judgeLines.length; i++) {
@@ -274,22 +336,32 @@ export class Chart {
274
336
  judgeLine.updateEffectiveBeats(EB);
275
337
  }
276
338
  }
339
+
340
+ /**
341
+ * 导出为KPA格式数据
342
+ * @returns KPA格式的谱面数据对象
343
+ */
277
344
  dumpKPA(): Required<ChartDataKPA2> {
278
345
  const eventNodeSequenceCollector = new Set<EventNodeSequence>();
279
346
  const orphanLines = [];
280
- for (let line of this.orphanLines) {
347
+ for (const line of this.orphanLines) {
281
348
  orphanLines.push(line.dumpKPA(eventNodeSequenceCollector, this.judgeLineGroups));
282
349
  }
283
350
  const envEasings = this.templateEasingLib.dump(eventNodeSequenceCollector);
284
- const eventNodeSequenceData: EventNodeSequenceDataKPA2<any>[] = [];
285
- for (let sequence of eventNodeSequenceCollector) {
351
+ const eventNodeSequenceData: EventNodeSequenceDataKPA2<EventValueESType>[] = [];
352
+ for (const sequence of eventNodeSequenceCollector) {
286
353
  eventNodeSequenceData.push(sequence.dump());
287
354
  }
288
355
  return {
289
356
  version: VERSION,
357
+ $schema: SCHEMA,
290
358
  duration: this.duration,
291
359
  bpmList: this.timeCalculator.dump(),
292
- envEasings: envEasings,
360
+ templateEasings: envEasings,
361
+ wrapperEasings: this.templateEasingLib.dumpWrapperEasings(),
362
+ macroEvaluators: this.macroLib.dumpMacroEvaluators(),
363
+ timeMacros: this.macroLib.dumpTimeMacros(),
364
+ valueMacros: this.macroLib.dumpValueMacros(),
293
365
  eventNodeSequences: eventNodeSequenceData,
294
366
  info: {
295
367
  level: this.level,
@@ -310,23 +382,51 @@ export class Chart {
310
382
  offset: this.offset,
311
383
  orphanLines: orphanLines,
312
384
  judgeLineGroups: this.judgeLineGroups.map(g => g.name),
313
- chartTime: this.chartingTime,
314
- rpeChartTime: this.rpeChartingTime
385
+ chartTime: this.chartingSeconds,
386
+ rpeChartTime: this.rpeChartingSeconds
315
387
  };
316
388
  }
389
+
390
+ /**
391
+ * 创建一个新的二级音符节点
392
+ * @param time 节点时间
393
+ * @returns 新创建的NNNode对象
394
+ */
317
395
  createNNNode(time: TimeT) {
318
- return new NNNode(time)
396
+ return new NNNode(time)
319
397
  }
398
+
399
+ /**
400
+ * 创建一个新的事件节点序列
401
+ * @param type 事件类型
402
+ * @param name 序列名称(ID)
403
+ * @returns 新创建的事件节点序列
404
+ * @throws {KPAError<ERROR_IDS.SEQUENCE_NAME_OCCUPIED>}
405
+ */
320
406
  createEventNodeSequence<T extends EventType>(type: T, name: string) {
321
407
  if (this.sequenceMap.has(name)) {
322
- throw new Error(`The name ${name} is occupied.`)
408
+ throw err.SEQUENCE_NAME_OCCUPIED(name);
323
409
  }
324
410
  const seq = EventNodeSequence.newSeq(type, this.getEffectiveBeats());
325
411
  seq.id = name;
326
412
  this.sequenceMap.set(name, seq);
327
413
  return seq;
328
414
  }
329
- countMaxCombo() {
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
+
424
+ /**
425
+ * 对谱面物量进行重新计数。
426
+ *
427
+ * 不会返回值,谱面物量存储在 `this.maxCombo` 中。
428
+ */
429
+ countMaxCombo(): void {
330
430
  let combo = 0;
331
431
  const nnnlist = this.nnnList;
332
432
  for (let node: NNNOrTail = nnnlist.head.next; node.type !== NodeType.TAIL; node = node.next) {
@@ -345,14 +445,27 @@ export class Chart {
345
445
  }
346
446
  this.maxCombo = combo;
347
447
  }
448
+
449
+ /**
450
+ * 将UI绑定到某判定线
451
+ * @param ui UI名称,与RPEJSON中的代号相同
452
+ * @param judgeLine 所要绑定的目标判定线
453
+ * @throws {KPAError<ERROR_IDS.UI_OCCUPIED>}
454
+ */
348
455
  attachUIToLine(ui: UIName, judgeLine: JudgeLine) {
349
456
  const key = `${ui}Attach` satisfies keyof Chart;
350
457
  if (this[key]) {
351
- throw new Error(`UI ${ui} is occupied`);
458
+ throw err.UI_OCCUPIED(ui);
352
459
  }
353
460
  this[key] = judgeLine;
354
461
  judgeLine.hasAttachUI = true;
355
462
  }
463
+
464
+ /**
465
+ * 移除谱面中某个UI的绑定,使UI进入未绑定状态
466
+ * @param ui UI名称,与RPEJSON中的相同
467
+ * @returns
468
+ */
356
469
  detachUI(ui: UIName) {
357
470
  const key = `${ui}Attach` satisfies keyof Chart;
358
471
  const judgeLine = this[key];
@@ -372,6 +485,12 @@ export class Chart {
372
485
  judgeLine.hasAttachUI = false;
373
486
  }
374
487
  }
488
+
489
+ /**
490
+ * 查询指定判定线上绑定的UI组件
491
+ * @param judgeLine 目标判定线
492
+ * @returns 绑定到该判定线上的UI组件名称数组
493
+ */
375
494
  queryJudgeLineUI(judgeLine: JudgeLine): UIName[] {
376
495
  const arr: UIName[] = [];
377
496
  for (const ui of ["combo", "combonumber", "score", "pause", "bar", "name", "level"] satisfies UIName[]) {
@@ -381,6 +500,13 @@ export class Chart {
381
500
  }
382
501
  return arr;
383
502
  }
503
+
504
+ /**
505
+ * 扫描所有用到的判定线贴图(纹理)并返回
506
+ *
507
+ * 给谱面播放器的接口,谱面播放器需要在初加载时提供贴图
508
+ * @returns 所有使用的纹理名称集合
509
+ */
384
510
  scanAllTextures() {
385
511
  const textures: Set<string> = new Set;
386
512
  for (const line of this.judgeLines) {
@@ -388,6 +514,14 @@ export class Chart {
388
514
  }
389
515
  return textures
390
516
  }
517
+
518
+ /**
519
+ * 使用KPA2数据创建一个缓动对象。
520
+ *
521
+ * 只有对贝塞尔缓动和截段缓动才会创建新对象,其他几种缓动从缓动库等中的对象池获取
522
+ * @param data 缓动数据
523
+ * @returns 创建的缓动对象
524
+ */
391
525
  createEasingFromData(data: EasingDataKPA2) {
392
526
  switch (data.type) {
393
527
  case EasingType.bezier:
@@ -395,19 +529,44 @@ export class Chart {
395
529
  case EasingType.normal:
396
530
  return rpeEasingArray[data.identifier];
397
531
  case EasingType.segmented:
398
- return new SegmentedEasing(this.createEasingFromData(data), data.left, data.right);
532
+ return new SegmentedEasing(this.createEasingFromData(data.inner), data.left, data.right);
399
533
  case EasingType.template:
400
534
  return this.templateEasingLib.get(data.identifier);
535
+ case EasingType.wrapper:
536
+ return this.templateEasingLib.getWrapper(data.identifier);
401
537
  }
402
538
  }
403
- createEvaluator<T extends EventValueESType>(data: EvaluatorDataKPA2<T>, type: EventValueTypeOfType<T>): Evaluator<T> {
539
+
540
+ /**
541
+ * 使用KPA2数据创建一个求值器对象。
542
+ *
543
+ * 求值器只有缓动型和表达式型两类。
544
+ * @param data 求值器数据
545
+ * @param type 事件值类型
546
+ * @returns 创建的求值器对象
547
+ */
548
+ bindEvaluator<T extends EventValueESType>(node: EventStartNode<T>, data: EvaluatorDataKPA2<T>, type: EventValueTypeOfType<T>, pos: string) {
404
549
  switch (data.type) {
405
550
  case EvaluatorType.eased:
406
- return this.createEasedEvaluator(data, type);
551
+ // 我管你这的那的 —— 小奶椰
552
+ node.evaluator = this.createEasedEvaluator(data, type) as unknown as Evaluator<T>;
553
+ break;
407
554
  case EvaluatorType.expressionbased:
408
- 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);
409
559
  }
410
560
  }
561
+
562
+ /**
563
+ * 使用KPA2数据创建一个缓动求值器。
564
+ *
565
+ * 对于普通缓动,这些求值器是从对应类构造器的静态对象池属性中获取的。
566
+ * @param data 缓动求值器数据
567
+ * @param type 事件值类型
568
+ * @returns 创建的缓动求值器对象
569
+ */
411
570
  createEasedEvaluator<T extends EventValueESType>(data: EasedEvaluatorDataOfType<T>, type: EventValueTypeOfType<T>): EasedEvaluatorOfType<T> {
412
571
  switch (type) {
413
572
  case EventValueType.numeric:
@@ -424,54 +583,207 @@ export class Chart {
424
583
  : new TextEasedEvaluator(this.createEasingFromData(data.easing), (data as TextEasedEvaluatorKPA2).interpretedAs) as EasedEvaluatorOfType<T>
425
584
  }
426
585
  }
586
+
587
+ /**
588
+ * 用一个缓动和事件类型获取一个缓动求值器
589
+ * @param easing 缓动对象
590
+ * @param type 事件值类型
591
+ * @param interpreteAs 文本解释方式
592
+ * @returns 对应类型的缓动求值器
593
+ */
594
+ getEasedEvaluator<T extends EventValueESType>(easing: Easing, type: EventValueTypeOfType<T>, interpreteAs?: InterpreteAs): EasedEvaluatorOfType<T> {
595
+ const easingIsNormal = easing instanceof NormalEasing;
596
+ switch (type) {
597
+ case EventValueType.numeric:
598
+ return easingIsNormal
599
+ ? NumericEasedEvaluator.evaluatorsOfNormalEasing[easing.rpeId] as EasedEvaluatorOfType<T>
600
+ : new NumericEasedEvaluator(easing) as EasedEvaluatorOfType<T>;
601
+ case EventValueType.color:
602
+ return easingIsNormal
603
+ ? ColorEasedEvaluator.evaluatorsOfNormalEasing[easing.rpeId] as EasedEvaluatorOfType<T>
604
+ : new ColorEasedEvaluator(easing) as EasedEvaluatorOfType<T>;
605
+ case EventValueType.text:
606
+ return easingIsNormal
607
+ ? TextEasedEvaluator.evaluatorsOfNoEzAndItpAs[easing.rpeId][interpreteAs] as EasedEvaluatorOfType<T>
608
+ : new TextEasedEvaluator(easing, interpreteAs) as EasedEvaluatorOfType<T>
609
+ }
610
+ }
611
+
612
+ /**
613
+ * 根据JavaScript表达式创建表达式求值器
614
+ * @param data 表达式求值器数据
615
+ * @returns 创建的表达式求值器对象
616
+ */
427
617
  createExpressionEvaluator<T extends EventValueESType>(data: ExpressionEvaluatorDataKPA2) {
428
618
  return new ExpressionEvaluator<T>(data.jsExpr);
429
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
+
430
634
  /**
431
635
  * 使用KPA2JSON创建一对面对面节点
432
- * @param data
433
- * @param type
434
- * @returns
636
+ * @param data 事件数据
637
+ * @param type 事件值类型
638
+ * @returns 包含起始节点和结束节点的元组
435
639
  */
436
- 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>] {
437
641
  const start = new EventStartNode(data.startTime, data.start);
438
642
  const end = new EventEndNode(data.endTime, data.end);
439
- 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
+ }
440
663
  EventNode.connect(start, end);
441
664
  return [start, end];
442
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
+ }
716
+ /* 暂时不用此方法,因为谱面播放器里面还是用反解法弄的
717
+ updateNNListsFromENS(speedENS: SpeedENS) {
718
+
719
+ }
720
+ */
443
721
  }
444
722
 
723
+ /**
724
+ * 表示一组判定线的容器
725
+ *
726
+ * 用于组织和管理具有相同属性或用途的判定线集合
727
+ */
445
728
  export class JudgeLineGroup {
446
- judgeLines: JudgeLine[];
729
+ /**
730
+ * 该只读标记只是为了防止外部修改,内部可以修改
731
+ *
732
+ * 属于该组的判定线列表,按ID升序排列
733
+ */
734
+ judgeLines: readonly JudgeLine[];
735
+
736
+ /**
737
+ * 创建一个新的判定线组
738
+ * @param name 组名称
739
+ */
447
740
  constructor(public name: string) {
448
741
  this.judgeLines = []
449
742
  }
743
+
744
+ /**
745
+ * 向判定线组添加一条判定线,并且保证其内部的判定线ID是升序排列的。
746
+ * @param judgeLine 要添加的判定线
747
+ * @returns
748
+ */
450
749
  add(judgeLine: JudgeLine) {
451
750
  // 加入之前已经按照ID升序排列
452
751
  // 加入时将新判定线插入到正确位置
752
+ const judgeLines = this.judgeLines as JudgeLine[];
453
753
  if (judgeLine.group) {
454
754
  judgeLine.group.remove(judgeLine);
455
755
  }
456
756
  judgeLine.group = this;
457
757
 
458
758
  // 找到正确的位置插入,保持按ID升序排列
459
- for (let i = 0; i < this.judgeLines.length; i++) {
460
- if (this.judgeLines[i].id > judgeLine.id) {
461
- this.judgeLines.splice(i, 0, judgeLine);
759
+ for (let i = 0; i < judgeLines.length; i++) {
760
+ if (judgeLines[i].id > judgeLine.id) {
761
+ judgeLines.splice(i, 0, judgeLine);
462
762
  return;
463
763
  }
464
764
  }
465
765
  // 如果没有找到比它大的ID,则插入到末尾
466
- this.judgeLines.push(judgeLine);
766
+ judgeLines.push(judgeLine);
467
767
 
468
768
  }
769
+
770
+ /**
771
+ * 从判定线组移除一条判定线
772
+ * @param judgeLine 要移除的判定线
773
+ */
469
774
  remove(judgeLine: JudgeLine) {
470
- const index = this.judgeLines.indexOf(judgeLine);
775
+ // 只读仅对外部作限制
776
+ const judgeLines = this.judgeLines as JudgeLine[];
777
+ const index = judgeLines.indexOf(judgeLine);
471
778
  if (index !== -1) {
472
- this.judgeLines.splice(index, 1);
779
+ judgeLines.splice(index, 1);
473
780
  }
474
781
  }
782
+
783
+ /**
784
+ * 检查该判定线组是否为默认组
785
+ * @returns 该判定线组是否为默认判定线组,默认的判断标准是:名称为 "Default"(大小写不敏感)
786
+ */
475
787
  isDefault() {
476
788
  return this.name.toLowerCase() === "default";
477
789
  }