kipphi 2.0.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/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from "./src/chartTypes";
2
+ export * from "./src/easing";
3
+ export * from "./src/evaluator";
4
+ export * from "./src/event";
5
+ export * from "./src/note";
6
+ export * from "./src/judgeline";
7
+ export * from "./src/time";
8
+ export * from "./src/util";
9
+ export * from "./src/chart";
10
+ export * from "./src/jumparray";
11
+ export { default as Environment } from "./src/env";
package/judgeline.ts ADDED
@@ -0,0 +1,605 @@
1
+ import type { Chart, JudgeLineGroup } from "./chart";
2
+ import { EventType, NoteType, type EventDataRPELike, type EventLayerDataKPA, type JudgeLineDataKPA, type JudgeLineDataRPE, type NNListDataKPA, type NoteDataRPE, type RGB, type TimeT, type ValueTypeOfEventType } from "./chartTypes";
3
+ import type { TemplateEasingLib } from "./easing";
4
+ import { EventEndNode, EventNodeLike, EventNodeSequence, EventStartNode } from "./event";
5
+ import { HNList, NNList, Note, NoteNode, NNNList } from "./note";
6
+ import { TimeCalculator } from "./time";
7
+ import { NodeType } from "./util";
8
+ import Environment from "./env";
9
+ /// #declaration:global
10
+ const TC = TimeCalculator
11
+
12
+ export interface EventLayer {
13
+ moveX?: EventNodeSequence;
14
+ moveY?: EventNodeSequence;
15
+ rotate?: EventNodeSequence;
16
+ alpha?: EventNodeSequence;
17
+ speed?: EventNodeSequence;
18
+ }
19
+
20
+ export interface ExtendedLayer {
21
+ scaleX?: EventNodeSequence;
22
+ scaleY?: EventNodeSequence;
23
+ text?: EventNodeSequence<string>;
24
+ color?: EventNodeSequence<RGB>;
25
+ }
26
+
27
+
28
+ /**
29
+ * 奇谱发生器使用中心来表示一个NNList的y值偏移范围,这个函数根据yOffset算出对应中心值
30
+ * @param yOffset
31
+ * @returns
32
+ */
33
+ const getRangeMedian = (yOffset: number) => {
34
+ const NNLIST_Y_OFFSET_HALF_SPAN = Environment.NNLIST_Y_OFFSET_HALF_SPAN
35
+ return (Math.floor((Math.abs(yOffset) - NNLIST_Y_OFFSET_HALF_SPAN) / NNLIST_Y_OFFSET_HALF_SPAN / 2) * (NNLIST_Y_OFFSET_HALF_SPAN * 2) + NNLIST_Y_OFFSET_HALF_SPAN * 2) * Math.sign(yOffset);
36
+ }
37
+ export class JudgeLine {
38
+ texture: string;
39
+ group: JudgeLineGroup;
40
+ cover: boolean;
41
+ hnLists = new Map<string, HNList>();
42
+ nnLists = new Map<string, NNList>();
43
+ eventLayers: EventLayer[] = [];
44
+ extendedLayer: ExtendedLayer = {};
45
+ // notePosition: Float64Array;
46
+ // noteSpeeds: NoteSpeeds;
47
+ father: JudgeLine;
48
+ children: Set<JudgeLine> = new Set();
49
+
50
+ moveX: number;
51
+ moveY: number;
52
+ rotate: number;
53
+ alpha: number;
54
+ transformedX: number;
55
+ transformedY: number;
56
+ optimized: boolean = false;
57
+
58
+ zOrder: number = 0;
59
+
60
+ anchor: [number, number] = [0.5, 0.5];
61
+
62
+ hasAttachUI: boolean = false;
63
+
64
+ /**
65
+ * 每帧渲染时所用的变换矩阵,缓存下来用于之后的UI绑定渲染
66
+ */
67
+ renderMatrix: Matrix;
68
+
69
+ rotatesWithFather: boolean = false;
70
+
71
+ id: number;
72
+ name: string = "Untitled";
73
+ readonly chart: Chart;
74
+ constructor(chart: Chart) {
75
+ //this.notes = [];
76
+ this.chart = chart;
77
+ this.texture = "line.png";
78
+ this.cover = true;
79
+ // this.noteSpeeds = {};
80
+ }
81
+ static fromRPEJSON(chart: Chart, id: number, data: JudgeLineDataRPE, templates: TemplateEasingLib, timeCalculator: TimeCalculator) {
82
+ let line = new JudgeLine(chart)
83
+ line.id = id;
84
+ line.name = data.Name;
85
+ chart.judgeLineGroups[data.Group].add(line);
86
+ line.cover = Boolean(data.isCover);
87
+ line.rotatesWithFather = data.rotateWithFather;
88
+ line.anchor = data.anchor ?? [0.5, 0.5];
89
+ line.texture = data.Texture || "line.png";
90
+ line.zOrder = data.zOrder ?? 0;
91
+
92
+ // Process UI
93
+ if (data.attachUI) {
94
+ // Must use template string, otherwise TypeScript would not recognize it as `keyof Chart`
95
+ // because the type is broadened to `string`
96
+ // And you cannot assign it to a variable
97
+ chart[`${data.attachUI}Attach` satisfies keyof Chart] = line;
98
+ line.hasAttachUI = true;
99
+ }
100
+
101
+ const noteNodeTree = chart.nnnList;
102
+ if (data.notes) {
103
+ const holdLists = line.hnLists;
104
+ const noteLists = line.nnLists;
105
+ let notes = data.notes;
106
+ notes.sort((n1: NoteDataRPE, n2: NoteDataRPE) => {
107
+ if (TimeCalculator.ne(n1.startTime, n2.startTime)) {
108
+ return TimeCalculator.gt(n1.startTime, n2.startTime) ? 1 : -1
109
+ }
110
+ return TimeCalculator.gt(n1.endTime, n2.endTime) ? -1 : 1 // 这里曾经排反了(
111
+ })
112
+ const len = notes.length;
113
+ let lastTime: TimeT = [-1, 0, 1];
114
+ // let comboInfoEntity: ComboInfoEntity;
115
+
116
+ for (let i = 0; i < len; i++) {
117
+ const note: Note = new Note(notes[i]);
118
+ note.computeVisibleBeats(timeCalculator);
119
+ const tree = line.getNNList(note.speed, note.yOffset, note.type === NoteType.hold, false)
120
+ const cur = tree.currentPoint
121
+ const lastHoldTime: TimeT = cur.type === NodeType.HEAD ? [-1, 0, 1] : cur.startTime
122
+ if (TimeCalculator.eq(lastHoldTime, note.startTime)) {
123
+ (tree.currentPoint as NoteNode).add(note)
124
+ } else {
125
+ const node = new NoteNode(note.startTime)
126
+ node.add(note); // 这里之前没写,特此留念!
127
+ NoteNode.connect(tree.currentPoint, node)
128
+ tree.currentPoint = node;
129
+ noteNodeTree.addNoteNode(node);
130
+ }
131
+ tree.timesWithNotes++
132
+ }
133
+ for (let trees of [holdLists, noteLists]) {
134
+ for (const [_, list] of trees) {
135
+ NoteNode.connect(list.currentPoint, list.tail)
136
+ list.initJump();
137
+ // tree.initPointers()
138
+ }
139
+ }
140
+ }
141
+ const eventLayers = data.eventLayers;
142
+ const length = eventLayers.length;
143
+ const createSequence = (type: EventType, events: EventDataRPELike[], index: number) => {
144
+ if (events) {
145
+ const sequence = EventNodeSequence.fromRPEJSON(type, events, chart);
146
+ sequence.id = `#${id}.${index}.${EventType[type]}`;
147
+ chart.sequenceMap.set(sequence.id, sequence);
148
+ return sequence;
149
+ }
150
+ }
151
+ const createExtendedSequence = <T extends EventType>(type: T, events: EventDataRPELike<ValueTypeOfEventType<T>>[]) => {
152
+ if (events) {
153
+ const sequence = EventNodeSequence.fromRPEJSON(type, events, chart);
154
+ sequence.id = `#${id}.ex.${EventType[type]}`;
155
+ chart.sequenceMap.set(sequence.id, sequence);
156
+ return sequence;
157
+ }
158
+ }
159
+ for (let index = 0; index < length; index++) {
160
+ const layerData = eventLayers[index];
161
+ if (!layerData) {
162
+ continue;
163
+ }
164
+ const layer: EventLayer = {
165
+ moveX: createSequence(EventType.moveX, layerData.moveXEvents, index),
166
+ moveY: createSequence(EventType.moveY, layerData.moveYEvents, index),
167
+ rotate: createSequence(EventType.rotate, layerData.rotateEvents, index),
168
+ alpha: createSequence(EventType.alpha, layerData.alphaEvents, index),
169
+ speed: createSequence(EventType.speed, layerData.speedEvents, index)
170
+ };
171
+ line.eventLayers[index] = layer;
172
+ }
173
+ if (data.extended) {
174
+ if (data.extended.scaleXEvents) {
175
+ line.extendedLayer.scaleX = createExtendedSequence(EventType.scaleX, data.extended.scaleXEvents);
176
+ } else {
177
+ line.extendedLayer.scaleX = chart.createEventNodeSequence(EventType.scaleX, `#${id}.ex.scaleX`);
178
+ }
179
+ if (data.extended.scaleYEvents) {
180
+ line.extendedLayer.scaleY = createExtendedSequence(EventType.scaleY, data.extended.scaleYEvents);
181
+ } else {
182
+ line.extendedLayer.scaleY = chart.createEventNodeSequence(EventType.scaleY, `#${id}.ex.scaleY`);
183
+ }
184
+ if (data.extended.textEvents) {
185
+ line.extendedLayer.text = createExtendedSequence(EventType.text, data.extended.textEvents);
186
+ }
187
+ if (data.extended.colorEvents) {
188
+ line.extendedLayer.color = createExtendedSequence(EventType.color, data.extended.colorEvents);
189
+ }
190
+ }
191
+ // line.updateNoteSpeeds();
192
+ // line.computeNotePositionY(timeCalculator);
193
+ return line;
194
+ }
195
+ static fromKPAJSON(isOld: boolean, chart: Chart, id: number, data: JudgeLineDataKPA, templates: TemplateEasingLib, timeCalculator: TimeCalculator) {
196
+ let line = new JudgeLine(chart)
197
+ line.id = id;
198
+ line.name = data.Name;
199
+ line.rotatesWithFather = data.rotatesWithFather;
200
+ line.anchor = data.anchor ?? [0.5, 0.5];
201
+ line.texture = data.Texture || "line.png";
202
+ line.cover = data.cover ?? true;
203
+ line.zOrder = data.zOrder ?? 0;
204
+
205
+
206
+ chart.judgeLineGroups[data.group].add(line);
207
+ const nnnList = chart.nnnList;
208
+ for (let isHold of [false, true]) {
209
+ const key: "hnLists" | "nnLists" = `${isHold ? "hn" : "nn"}Lists`
210
+ const lists: Record<string, NNListDataKPA>= data[key];
211
+ for (let name in lists) {
212
+ const listData = lists[name];
213
+ if (!isOld) {
214
+
215
+ const list = NNList.fromKPAJSON(isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
216
+ list.parentLine = line;
217
+ list.id = name
218
+ line[key].set(name, list);
219
+ } else {
220
+ line.getNNListFromOldKPAJSON(line[key], name, isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
221
+ }
222
+ }
223
+ }
224
+ for (let child of data.children) {
225
+ line.children.add(JudgeLine.fromKPAJSON(isOld, chart, child.id, child, templates, timeCalculator));
226
+ }
227
+ for (let eventLayerData of data.eventLayers) {
228
+ let eventLayer: EventLayer = {} as EventLayer;
229
+ for (let key in eventLayerData) {
230
+ // use "fromRPEJSON" for they have the same logic
231
+ eventLayer[key] = chart.sequenceMap.get(eventLayerData[key]);
232
+ }
233
+ line.eventLayers.push(eventLayer);
234
+ }
235
+ line.extendedLayer.scaleX = data.extended?.scaleXEvents
236
+ ? chart.sequenceMap.get(data.extended.scaleXEvents)
237
+ : chart.createEventNodeSequence(EventType.scaleX, `#${line.id}.ex.scaleX`);
238
+ line.extendedLayer.scaleY = data.extended?.scaleYEvents
239
+ ? chart.sequenceMap.get(data.extended.scaleYEvents)
240
+ : chart.createEventNodeSequence(EventType.scaleY, `#${line.id}.ex.scaleY`);
241
+ if (data.extended) {
242
+ if (data.extended.textEvents) {
243
+ line.extendedLayer.text = chart.sequenceMap.get(data.extended.textEvents);
244
+ }
245
+ if (data.extended.colorEvents) {
246
+ line.extendedLayer.color = chart.sequenceMap.get(data.extended.colorEvents);
247
+ }
248
+ }
249
+
250
+ chart.judgeLines.push(line);
251
+ return line;
252
+ }
253
+ getNNListFromOldKPAJSON(lists: Map<string, NNList>, namePrefix: string, isHold: boolean, effectiveBeats: number, listData: NNListDataKPA, nnnList: NNNList, timeCalculator: TimeCalculator) {
254
+ const speed = listData.speed;
255
+ const constructor = isHold ? HNList : NNList;
256
+ const createdLists = new Set<NNList>();
257
+ const getOrCreateNNList = (median: number, name: string) => {
258
+ if (lists.has(name)) {
259
+ return lists.get(name);
260
+ }
261
+ const list: NNList = new constructor(speed, median, effectiveBeats);
262
+ list.id = name;
263
+ list.parentLine = this;
264
+ lists.set(name, list);
265
+ createdLists.add(list);
266
+ return list;
267
+ };
268
+ const nns = listData.noteNodes;
269
+ const len = nns.length;
270
+ for (let i = 0; i < len; i++) {
271
+ const nodeData = nns[i];
272
+ const l = nodeData.notes.length;
273
+ for (let j = 0; j < l; j++) {
274
+ const noteData = nodeData.notes[j];
275
+ const note = new Note(noteData);
276
+ const median = getRangeMedian(note.yOffset)
277
+ const list = getOrCreateNNList(median, namePrefix + "o" + median);
278
+ const cur = list.currentPoint;
279
+ if (!note.visibleBeats) {
280
+ note.computeVisibleBeats(timeCalculator)
281
+ }
282
+ if (!(cur.type === NodeType.HEAD) && TC.eq(noteData.startTime, cur.startTime)) {
283
+ cur.add(note);
284
+ } else {
285
+ const node = new NoteNode(noteData.startTime);
286
+ node.add(note);
287
+ NoteNode.connect(cur, node);
288
+ nnnList.addNoteNode(node);
289
+ list.currentPoint = node;
290
+ }
291
+ }
292
+ }
293
+ for (const list of createdLists) {
294
+ NoteNode.connect(list.currentPoint, list.tail);
295
+ list.initJump();
296
+ }
297
+ }
298
+ getLayer(index: "0" | "1" | "2" | "3" | "ex") {
299
+ if (index === "ex") {
300
+ return this.extendedLayer;
301
+ } else {
302
+ return this.eventLayers[index];
303
+ }
304
+ }
305
+ updateSpeedIntegralFrom(beats: number, timeCalculator: TimeCalculator) {
306
+ for (let eventLayer of this.eventLayers) {
307
+ eventLayer?.speed?.updateNodesIntegralFrom(beats, timeCalculator);
308
+ }
309
+ }
310
+ /**
311
+ * startY and endY must not be negative
312
+ * @param beats
313
+ * @param timeCalculator
314
+ * @param startY
315
+ * @param endY
316
+ * @returns
317
+ */
318
+ computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
319
+ console.log("invoked")
320
+ //return [[0, Infinity]]
321
+ //*
322
+ // 提取所有有变化的时间点
323
+ let times: number[] = [];
324
+ let result: [number, number][] = [];
325
+ for (let eventLayer of this.eventLayers) {
326
+ const sequence = eventLayer?.speed;
327
+ if (!sequence) {
328
+ continue;
329
+ }
330
+ let node: EventStartNode = sequence.getNodeAt(beats);
331
+ let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
332
+ while (true) {
333
+ times.push(TimeCalculator.toBeats(node.time))
334
+ if ((endNode = node.next).type === NodeType.TAIL) {
335
+ break;
336
+ }
337
+
338
+ node = endNode.next
339
+ }
340
+ }
341
+ times = [...new Set(times)].sort((a, b) => a - b)
342
+ const len = times.length;
343
+ let nextTime = times[0]
344
+ let nextPosY = this.getStackedIntegral(nextTime, timeCalculator)
345
+ let nextSpeed = this.getStackedValue("speed", nextTime, true)
346
+ let range: [number, number] = [undefined, undefined];
347
+ // console.log(times)
348
+ const computeTime = (speed: number, currentPos: number, fore: number) => timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
349
+ for (let i = 0; i < len - 1;) {
350
+ const thisTime = nextTime;
351
+ const thisPosY = nextPosY;
352
+ let thisSpeed = this.getStackedValue("speed", thisTime);
353
+ if (Math.abs(thisSpeed) < 1e-8) {
354
+ thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
355
+ }
356
+ nextTime = times[i + 1]
357
+ nextPosY = this.getStackedIntegral(nextTime, timeCalculator);
358
+ nextSpeed = this.getStackedValue("speed", nextTime, true)
359
+ // console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
360
+ if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
361
+ //debugger;
362
+ nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
363
+ nextSpeed = 0
364
+ nextPosY = this.getStackedIntegral(nextTime, timeCalculator)
365
+ //debugger
366
+ } else {
367
+ // console.log("i++")
368
+ i++
369
+ }
370
+ if (range[0] === undefined) {
371
+ // 变速区间直接全部囊括,匀速要算一下,因为好算
372
+ /*
373
+ 设两个时间点的位置为a,b
374
+ 开始结束点为s,e
375
+ 选中小段一部分在区间内:
376
+ a < s <= b
377
+ 或a > e >= b
378
+ 全部在区间内
379
+ s <= a <= b
380
+ */
381
+ if (thisPosY < startY && startY <= nextPosY
382
+ || thisPosY > endY && endY >= nextPosY) {
383
+ range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
384
+ thisSpeed,
385
+ (thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
386
+ } else if (startY <= thisPosY && thisPosY <= endY) {
387
+ range[0] = thisTime;
388
+ }
389
+ }
390
+ // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
391
+ if (range[0] !== undefined) {
392
+ if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
393
+ range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
394
+ thisSpeed,
395
+ (thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
396
+ if (range[0] > range[1]){
397
+ console.error("range start should be smaller than range end.")
398
+ console.log("\nRange is:", range, "thisTime:", thisTime, "thisSpeed:", thisSpeed, "thisPosY:", thisPosY,
399
+ "\nstartY:", startY, "endY:", endY, "nextTime:", nextTime, "nextPosY:", nextPosY, "nextSpeed:", nextSpeed,
400
+ "\njudgeLine:", this)
401
+ }
402
+ result.push(range)
403
+ range = [undefined, undefined];
404
+ }
405
+ }
406
+ }
407
+ const thisPosY = nextPosY;
408
+ const thisTime = nextTime;
409
+ const thisSpeed = this.getStackedValue("speed", thisTime);
410
+ const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY)
411
+ if (range[0] === undefined) {
412
+ // 变速区间直接全部囊括,匀速要算一下,因为好算
413
+ if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
414
+ range[0] = computeTime(
415
+ thisSpeed,
416
+ (thisPosY < inf ? startY : endY) - thisPosY,
417
+ thisTime)
418
+ } else if (thisSpeed === 0) {
419
+ range[0] = 0;
420
+ }
421
+ }
422
+ // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
423
+ if (range[0] !== undefined) {
424
+ if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
425
+ range[1] = computeTime(
426
+ thisSpeed,
427
+ (thisPosY > inf ? startY : endY) - thisPosY,
428
+ thisTime)
429
+ result.push(range)
430
+ } else if (thisSpeed === 0) {
431
+ range[1] = Infinity;
432
+ result.push(range)
433
+ }
434
+ }
435
+ return result;
436
+ //*/
437
+ }
438
+ /*
439
+ computeLinePositionY(beats: number, timeCalculator: TimeCalculator) {
440
+ return this.getStackedIntegral(beats, timeCalculator)
441
+ }
442
+ */
443
+ /**
444
+ *
445
+ * @param beats
446
+ * @param usePrev 如果取到节点,将使用EndNode的值。默认为FALSE
447
+ * @returns
448
+ */
449
+ getValues(beats: number, usePrev: boolean=false): [x: number, y: number, theta: number, alpha: number] {
450
+ return [
451
+ this.getStackedValue("moveX", beats, usePrev),
452
+ this.getStackedValue("moveY", beats, usePrev),
453
+ this.getStackedValue("rotate", beats, usePrev) / 180 * Math.PI, // 转换为弧度制
454
+ this.getStackedValue("alpha", beats, usePrev),
455
+ ]
456
+ }
457
+ getMatrix(beats: number, usePrev = false) {
458
+ const base = this.father.getMatrix(beats, usePrev);
459
+ const x = this
460
+ }
461
+ getStackedValue(type: keyof EventLayer, beats: number, usePrev: boolean = false) {
462
+ const length = this.eventLayers.length;
463
+ let current = 0;
464
+ for (let index = 0; index < length; index++) {
465
+ const layer = this.eventLayers[index];
466
+ if (!layer || !layer[type]) {
467
+ break;
468
+ }
469
+ current += layer[type].getValueAt(beats, usePrev);
470
+ }
471
+ return current
472
+ }
473
+ getStackedIntegral(beats: number, timeCalculator: TimeCalculator) {
474
+
475
+ const length = this.eventLayers.length;
476
+ let current = 0;
477
+ for (let index = 0; index < length; index++) {
478
+ const layer = this.eventLayers[index];
479
+ if (!layer || !layer.speed) {
480
+ break;
481
+ }
482
+ current += layer.speed.getIntegral(beats, timeCalculator);
483
+ }
484
+ // console.log("integral", current)
485
+ return current;
486
+ }
487
+ /**
488
+ * 获取对应速度和类型的Note树,没有则创建
489
+ */
490
+ getNNList(speed: number, yOffset: number, isHold: boolean, initsJump: boolean) {
491
+ const lists = isHold ? this.hnLists : this.nnLists;
492
+ const medianYOffset = getRangeMedian(yOffset);
493
+ for (const [_, list] of lists) {
494
+ if (list.speed === speed && list.medianYOffset === medianYOffset) {
495
+ return list;
496
+ }
497
+ }
498
+ const list = isHold ? new HNList(speed, medianYOffset, this.chart.timeCalculator.duration) : new NNList(speed, medianYOffset, this.chart.timeCalculator.duration)
499
+ list.parentLine = this;
500
+ NoteNode.connect(list.head, list.tail)
501
+ if (initsJump) list.initJump();
502
+ const id = (isHold ? "$" : "#") + speed + "o" + medianYOffset;
503
+ lists.set(id, list);
504
+ list.id = id;
505
+ return list;
506
+ }
507
+ getNode(note: Note, initsJump: boolean) {
508
+ const speed = note.speed;
509
+ const yOffset = note.yOffset;
510
+ const isHold = note.type === NoteType.hold;
511
+ const tree = this.getNNList(speed, yOffset, isHold, initsJump);
512
+ return tree.getNodeOf(note.startTime);
513
+ }
514
+ /**
515
+ *
516
+ * @param eventNodeSequences To Collect the sequences used in this line
517
+ * @returns
518
+ */
519
+ dumpKPA(eventNodeSequences: Set<EventNodeSequence<any>>, judgeLineGroups: JudgeLineGroup[]): JudgeLineDataKPA {
520
+ const children: JudgeLineDataKPA[] = [];
521
+ for (let line of this.children) {
522
+ children.push(line.dumpKPA(eventNodeSequences, judgeLineGroups))
523
+ }
524
+ const eventLayers: EventLayerDataKPA[] = [];
525
+ for (let i = 0; i < this.eventLayers.length; i++) {
526
+ const layer = this.eventLayers[i];
527
+ if (!layer) continue;
528
+ let layerData = {}
529
+ for (let type in layer) {
530
+ const sequence = layer[type as keyof EventLayer];
531
+ if (!sequence) continue;
532
+ eventNodeSequences.add(sequence);
533
+ layerData[type] = sequence.id;
534
+ }
535
+ eventLayers.push(layerData as EventLayerDataKPA);
536
+ }
537
+ const hnListsData = {};
538
+ const nnListsData = {};
539
+ for (let [id, list] of this.hnLists) {
540
+ hnListsData[id] = list.dumpKPA();
541
+ }
542
+ for (let [id, list] of this.nnLists) {
543
+ nnListsData[id] = list.dumpKPA();
544
+ }
545
+ const extended = {
546
+ scaleXEvents: this.extendedLayer.scaleX?.id,
547
+ scaleYEvents: this.extendedLayer.scaleY?.id,
548
+ textEvents: this.extendedLayer.text?.id,
549
+ colorEvents: this.extendedLayer.color?.id,
550
+ };
551
+ eventNodeSequences.add(this.extendedLayer.scaleX)
552
+ eventNodeSequences.add(this.extendedLayer.scaleY)
553
+
554
+ if (this.extendedLayer.color) {
555
+ eventNodeSequences.add(this.extendedLayer.color);
556
+ }
557
+ if (this.extendedLayer.text) {
558
+ eventNodeSequences.add(this.extendedLayer.text);
559
+ }
560
+ return {
561
+ group: judgeLineGroups.indexOf(this.group),
562
+ id: this.id,
563
+ Name: this.name,
564
+ Texture: this.texture,
565
+ anchor: this.anchor,
566
+ rotatesWithFather: this.rotatesWithFather,
567
+ children: children,
568
+ eventLayers: eventLayers,
569
+ hnLists: hnListsData,
570
+ nnLists: nnListsData,
571
+ cover: this.cover,
572
+ extended: extended,
573
+ zOrder: this.zOrder === 0 ? undefined : this.zOrder
574
+
575
+ }
576
+ }
577
+
578
+
579
+ updateEffectiveBeats(EB: number) {
580
+ for (let i = 0; i < this.eventLayers.length; i++) {
581
+ const layer = this.eventLayers[i];
582
+ for (let type in layer) {
583
+ const sequence = layer[type as keyof EventLayer];
584
+ sequence.effectiveBeats = EB;
585
+ }
586
+ }
587
+ for (let lists of [this.nnLists, this.hnLists]) {
588
+ for (let [_, list] of lists) {
589
+ list.effectiveBeats = EB;
590
+ }
591
+ }
592
+ }
593
+ static checkinterdependency(judgeLine: JudgeLine, toBeFather: JudgeLine) {
594
+ let descendantsAndSelf = new Set<JudgeLine>();
595
+ const add = (line: JudgeLine) => {
596
+ descendantsAndSelf.add(line);
597
+ for (let child of line.children) {
598
+ add(child);
599
+ }
600
+ }
601
+ add(judgeLine);
602
+ return descendantsAndSelf.has(toBeFather);
603
+ }
604
+ }
605
+ /// #enddeclaration