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/README.md +1 -3
- package/basic.ts +285 -0
- package/bpm.ts +332 -0
- package/chart.ts +418 -106
- package/chartType.schema.json +584 -0
- package/chartType2.schema.json +1107 -0
- package/chartTypes.ts +131 -30
- package/easing.ts +125 -90
- package/env.ts +208 -0
- package/evaluator.ts +106 -20
- package/event.ts +357 -255
- package/index.d.ts +3055 -0
- package/index.js +5530 -0
- package/index.ts +17 -11
- package/judgeline.ts +395 -94
- package/jumparray.ts +10 -11
- package/line.ts +246 -0
- package/macro.ts +215 -0
- package/note.ts +32 -55
- package/operation/basic.ts +285 -0
- package/operation/chart.ts +21 -0
- package/operation/event.ts +511 -0
- package/operation/index.ts +6 -0
- package/operation/line.ts +304 -0
- package/operation/macro.ts +60 -0
- package/operation/note.ts +457 -0
- package/package.json +7 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +2 -3
- package/util.ts +21 -1
- package/version.ts +2 -1
package/judgeline.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
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";
|
|
2
|
+
import { EventType, EventValueESType, EventValueType, JudgeLineDataKPA2, NoteType, type EventDataRPELike, type EventLayerDataKPA, type JudgeLineDataKPA, type JudgeLineDataRPE, type NNListDataKPA, type NoteDataRPE, type RGB, type TimeT, type ValueTypeOfEventType } from "./chartTypes";
|
|
3
3
|
import type { TemplateEasingLib } from "./easing";
|
|
4
|
-
import { EventEndNode, EventNodeLike, EventNodeSequence, EventStartNode } from "./event";
|
|
4
|
+
import { EventEndNode, EventNode, EventNodeLike, EventNodeSequence, EventStartNode, Monotonicity, SpeedENS } from "./event";
|
|
5
5
|
import { HNList, NNList, Note, NoteNode, NNNList } from "./note";
|
|
6
|
-
import { TimeCalculator } from "./
|
|
6
|
+
import { type TimeCalculator } from "./bpm";
|
|
7
|
+
import TC from "./time";
|
|
7
8
|
import { NodeType } from "./util";
|
|
8
|
-
import Environment from "./env";
|
|
9
|
+
import Environment, { err } from "./env";
|
|
9
10
|
/// #declaration:global
|
|
10
|
-
const TC = TimeCalculator
|
|
11
11
|
|
|
12
12
|
export interface EventLayer {
|
|
13
13
|
moveX?: EventNodeSequence;
|
|
14
14
|
moveY?: EventNodeSequence;
|
|
15
15
|
rotate?: EventNodeSequence;
|
|
16
16
|
alpha?: EventNodeSequence;
|
|
17
|
-
speed?: EventNodeSequence;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface ExtendedLayer {
|
|
@@ -34,6 +33,9 @@ const getRangeMedian = (yOffset: number) => {
|
|
|
34
33
|
const NNLIST_Y_OFFSET_HALF_SPAN = Environment.NNLIST_Y_OFFSET_HALF_SPAN
|
|
35
34
|
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
35
|
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
37
39
|
export class JudgeLine {
|
|
38
40
|
texture: string;
|
|
39
41
|
group: JudgeLineGroup;
|
|
@@ -41,19 +43,14 @@ export class JudgeLine {
|
|
|
41
43
|
hnLists = new Map<string, HNList>();
|
|
42
44
|
nnLists = new Map<string, NNList>();
|
|
43
45
|
eventLayers: EventLayer[] = [];
|
|
46
|
+
speedSequence: SpeedENS;
|
|
44
47
|
extendedLayer: ExtendedLayer = {};
|
|
45
48
|
// notePosition: Float64Array;
|
|
46
49
|
// noteSpeeds: NoteSpeeds;
|
|
47
50
|
father: JudgeLine;
|
|
48
51
|
children: Set<JudgeLine> = new Set();
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
moveY: number;
|
|
52
|
-
rotate: number;
|
|
53
|
-
alpha: number;
|
|
54
|
-
transformedX: number;
|
|
55
|
-
transformedY: number;
|
|
56
|
-
optimized: boolean = false;
|
|
53
|
+
|
|
57
54
|
|
|
58
55
|
zOrder: number = 0;
|
|
59
56
|
|
|
@@ -63,9 +60,15 @@ export class JudgeLine {
|
|
|
63
60
|
|
|
64
61
|
/**
|
|
65
62
|
* 每帧渲染时所用的变换矩阵,缓存下来用于之后的UI绑定渲染
|
|
63
|
+
*
|
|
64
|
+
* 已移除,谱面NPM包不需要这个,播放器侧如要使用,可以用同名接口扩展此类
|
|
65
|
+
* @removed since 2.0.1
|
|
66
66
|
*/
|
|
67
|
-
renderMatrix: Matrix;
|
|
67
|
+
// renderMatrix: Matrix;
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* 是否随父线旋转
|
|
71
|
+
*/
|
|
69
72
|
rotatesWithFather: boolean = false;
|
|
70
73
|
|
|
71
74
|
id: number;
|
|
@@ -79,12 +82,12 @@ export class JudgeLine {
|
|
|
79
82
|
// this.noteSpeeds = {};
|
|
80
83
|
}
|
|
81
84
|
static fromRPEJSON(chart: Chart, id: number, data: JudgeLineDataRPE, templates: TemplateEasingLib, timeCalculator: TimeCalculator) {
|
|
82
|
-
|
|
85
|
+
const line = new JudgeLine(chart)
|
|
83
86
|
line.id = id;
|
|
84
87
|
line.name = data.Name;
|
|
85
88
|
chart.judgeLineGroups[data.Group].add(line);
|
|
86
89
|
line.cover = Boolean(data.isCover);
|
|
87
|
-
line.rotatesWithFather = data.rotateWithFather;
|
|
90
|
+
line.rotatesWithFather = data.rotateWithFather ?? false;
|
|
88
91
|
line.anchor = data.anchor ?? [0.5, 0.5];
|
|
89
92
|
line.texture = data.Texture || "line.png";
|
|
90
93
|
line.zOrder = data.zOrder ?? 0;
|
|
@@ -102,15 +105,15 @@ export class JudgeLine {
|
|
|
102
105
|
if (data.notes) {
|
|
103
106
|
const holdLists = line.hnLists;
|
|
104
107
|
const noteLists = line.nnLists;
|
|
105
|
-
|
|
108
|
+
const notes = data.notes;
|
|
106
109
|
notes.sort((n1: NoteDataRPE, n2: NoteDataRPE) => {
|
|
107
|
-
if (
|
|
108
|
-
return
|
|
110
|
+
if (TC.ne(n1.startTime, n2.startTime)) {
|
|
111
|
+
return TC.gt(n1.startTime, n2.startTime) ? 1 : -1
|
|
109
112
|
}
|
|
110
|
-
return
|
|
113
|
+
return TC.gt(n1.endTime, n2.endTime) ? -1 : 1 // 这里曾经排反了(
|
|
111
114
|
})
|
|
112
115
|
const len = notes.length;
|
|
113
|
-
let lastTime: TimeT = [-1, 0, 1];
|
|
116
|
+
// let lastTime: TimeT = [-1, 0, 1];
|
|
114
117
|
// let comboInfoEntity: ComboInfoEntity;
|
|
115
118
|
|
|
116
119
|
for (let i = 0; i < len; i++) {
|
|
@@ -119,7 +122,7 @@ export class JudgeLine {
|
|
|
119
122
|
const tree = line.getNNList(note.speed, note.yOffset, note.type === NoteType.hold, false)
|
|
120
123
|
const cur = tree.currentPoint
|
|
121
124
|
const lastHoldTime: TimeT = cur.type === NodeType.HEAD ? [-1, 0, 1] : cur.startTime
|
|
122
|
-
if (
|
|
125
|
+
if (TC.eq(lastHoldTime, note.startTime)) {
|
|
123
126
|
(tree.currentPoint as NoteNode).add(note)
|
|
124
127
|
} else {
|
|
125
128
|
const node = new NoteNode(note.startTime)
|
|
@@ -130,8 +133,8 @@ export class JudgeLine {
|
|
|
130
133
|
}
|
|
131
134
|
tree.timesWithNotes++
|
|
132
135
|
}
|
|
133
|
-
for (
|
|
134
|
-
for (const [_, list] of
|
|
136
|
+
for (const lists of [holdLists, noteLists]) {
|
|
137
|
+
for (const [_, list] of lists) {
|
|
135
138
|
NoteNode.connect(list.currentPoint, list.tail)
|
|
136
139
|
list.initJump();
|
|
137
140
|
// tree.initPointers()
|
|
@@ -142,20 +145,23 @@ export class JudgeLine {
|
|
|
142
145
|
const length = eventLayers.length;
|
|
143
146
|
const createSequence = (type: EventType, events: EventDataRPELike[], index: number) => {
|
|
144
147
|
if (events) {
|
|
145
|
-
const
|
|
146
|
-
sequence
|
|
148
|
+
const seqId = `#${id}.${index}.${EventType[type]}`;
|
|
149
|
+
const sequence = EventNodeSequence.fromRPEJSON(type, events, chart, seqId);
|
|
150
|
+
sequence.id = seqId
|
|
147
151
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
148
152
|
return sequence;
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
const createExtendedSequence = <T extends EventType>(type: T, events: EventDataRPELike<ValueTypeOfEventType<T>>[]) => {
|
|
152
156
|
if (events) {
|
|
153
|
-
const
|
|
154
|
-
sequence
|
|
157
|
+
const seqId = `#${id}.ex.${EventType[type]}`;
|
|
158
|
+
const sequence = EventNodeSequence.fromRPEJSON(type, events, chart, seqId);
|
|
159
|
+
sequence.id = seqId;
|
|
155
160
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
156
161
|
return sequence;
|
|
157
162
|
}
|
|
158
163
|
}
|
|
164
|
+
const speedSequences: SpeedENS[] = [];
|
|
159
165
|
for (let index = 0; index < length; index++) {
|
|
160
166
|
const layerData = eventLayers[index];
|
|
161
167
|
if (!layerData) {
|
|
@@ -165,11 +171,21 @@ export class JudgeLine {
|
|
|
165
171
|
moveX: createSequence(EventType.moveX, layerData.moveXEvents, index),
|
|
166
172
|
moveY: createSequence(EventType.moveY, layerData.moveYEvents, index),
|
|
167
173
|
rotate: createSequence(EventType.rotate, layerData.rotateEvents, index),
|
|
168
|
-
alpha: createSequence(EventType.alpha, layerData.alphaEvents, index)
|
|
169
|
-
speed: createSequence(EventType.speed, layerData.speedEvents, index)
|
|
174
|
+
alpha: createSequence(EventType.alpha, layerData.alphaEvents, index)
|
|
170
175
|
};
|
|
176
|
+
if (layerData.speedEvents) {
|
|
177
|
+
const seq = createSequence(EventType.speed, layerData.speedEvents, index);
|
|
178
|
+
speedSequences.push(seq as SpeedENS);
|
|
179
|
+
}
|
|
171
180
|
line.eventLayers[index] = layer;
|
|
172
181
|
}
|
|
182
|
+
if (speedSequences.length > 1) {
|
|
183
|
+
line.speedSequence = EventNodeSequence.mergeSequences(speedSequences) as SpeedENS;
|
|
184
|
+
line.speedSequence.updateFloorPositionAfter(line.speedSequence.head.next, timeCalculator);
|
|
185
|
+
} else {
|
|
186
|
+
line.speedSequence = speedSequences[0]
|
|
187
|
+
chart.registerEventNodeSequence(EventType.speed, `#${id}.speed`, speedSequences[0]);
|
|
188
|
+
}
|
|
173
189
|
if (data.extended) {
|
|
174
190
|
if (data.extended.scaleXEvents) {
|
|
175
191
|
line.extendedLayer.scaleX = createExtendedSequence(EventType.scaleX, data.extended.scaleXEvents);
|
|
@@ -192,58 +208,123 @@ export class JudgeLine {
|
|
|
192
208
|
// line.computeNotePositionY(timeCalculator);
|
|
193
209
|
return line;
|
|
194
210
|
}
|
|
195
|
-
static fromKPAJSON(
|
|
196
|
-
|
|
211
|
+
static fromKPAJSON(
|
|
212
|
+
version: number,
|
|
213
|
+
chart: Chart,
|
|
214
|
+
id: number,
|
|
215
|
+
data: JudgeLineDataKPA | JudgeLineDataKPA2,
|
|
216
|
+
templates: TemplateEasingLib,
|
|
217
|
+
timeCalculator: TimeCalculator
|
|
218
|
+
)
|
|
219
|
+
{
|
|
220
|
+
const withNewEventStructure = version >= 150;
|
|
221
|
+
const independentSpeedENS = version >= 201;
|
|
222
|
+
const lowerCaseNameAndTexture = version >= 201;
|
|
223
|
+
|
|
224
|
+
const line = new JudgeLine(chart)
|
|
197
225
|
line.id = id;
|
|
198
|
-
line.name = data.Name;
|
|
199
|
-
line.rotatesWithFather = data.rotatesWithFather;
|
|
226
|
+
line.name = lowerCaseNameAndTexture ? (data as JudgeLineDataKPA2).name : (data as JudgeLineDataKPA).Name;
|
|
227
|
+
line.rotatesWithFather = data.rotatesWithFather ?? false;
|
|
200
228
|
line.anchor = data.anchor ?? [0.5, 0.5];
|
|
201
|
-
line.texture = data.Texture
|
|
229
|
+
line.texture = (lowerCaseNameAndTexture ? (data as JudgeLineDataKPA2).texture : (data as JudgeLineDataKPA).Texture) ?? "line.png";
|
|
202
230
|
line.cover = data.cover ?? true;
|
|
203
231
|
line.zOrder = data.zOrder ?? 0;
|
|
204
232
|
|
|
205
233
|
|
|
234
|
+
|
|
206
235
|
chart.judgeLineGroups[data.group].add(line);
|
|
207
236
|
const nnnList = chart.nnnList;
|
|
208
|
-
for (
|
|
237
|
+
for (const isHold of [false, true]) {
|
|
209
238
|
const key: "hnLists" | "nnLists" = `${isHold ? "hn" : "nn"}Lists`
|
|
210
|
-
const
|
|
211
|
-
for (
|
|
212
|
-
const listData =
|
|
213
|
-
if (
|
|
239
|
+
const listsData: Record<string, NNListDataKPA>= data[key];
|
|
240
|
+
for (const name in listsData) {
|
|
241
|
+
const listData = listsData[name];
|
|
242
|
+
if (withNewEventStructure) {
|
|
214
243
|
|
|
215
244
|
const list = NNList.fromKPAJSON(isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
|
|
216
245
|
list.parentLine = line;
|
|
217
|
-
list.id = name
|
|
246
|
+
list.id = name;
|
|
247
|
+
// isHold为真则lists为Map<string, HNList>,且list为HNList,能够匹配
|
|
248
|
+
// @ts-expect-error 这里我也不知道怎么让它知道这里是匹配的
|
|
218
249
|
line[key].set(name, list);
|
|
219
250
|
} else {
|
|
220
251
|
line.getNNListFromOldKPAJSON(line[key], name, isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
|
|
221
252
|
}
|
|
222
253
|
}
|
|
223
254
|
}
|
|
224
|
-
for (
|
|
225
|
-
|
|
255
|
+
for (const child of data.children) {
|
|
256
|
+
const childLine = JudgeLine.fromKPAJSON(version, chart, child.id, child, templates, timeCalculator)
|
|
257
|
+
childLine.father = line;
|
|
258
|
+
line.children.add(childLine);
|
|
259
|
+
}
|
|
260
|
+
const unwrap = <VT extends EventValueESType>(sequence: EventNodeSequence<EventValueESType>, predicate: (value: unknown) => boolean, typeStr: keyof typeof EventValueType) => {
|
|
261
|
+
const value = sequence.head.next.value
|
|
262
|
+
if (!predicate(value)) {
|
|
263
|
+
throw err.EXPECTED_TYPED_ENS(typeStr, sequence.id, value);
|
|
264
|
+
}
|
|
265
|
+
return sequence as EventNodeSequence<VT>;
|
|
226
266
|
}
|
|
227
|
-
for (
|
|
228
|
-
|
|
229
|
-
for (
|
|
267
|
+
for (const eventLayerData of data.eventLayers) {
|
|
268
|
+
const eventLayer: EventLayer = {} as EventLayer;
|
|
269
|
+
for (const key in eventLayerData) {
|
|
230
270
|
// use "fromRPEJSON" for they have the same logic
|
|
231
|
-
eventLayer[key] = chart.sequenceMap.get(eventLayerData[key]);
|
|
271
|
+
eventLayer[key] = unwrap(chart.sequenceMap.get(eventLayerData[key]), v => typeof v === "number", "numeric");
|
|
232
272
|
}
|
|
233
273
|
line.eventLayers.push(eventLayer);
|
|
234
274
|
}
|
|
275
|
+
if (independentSpeedENS) {
|
|
276
|
+
const seq = unwrap(
|
|
277
|
+
chart.sequenceMap.get((data as JudgeLineDataKPA2).speedEventNodeSeq),
|
|
278
|
+
v => typeof v === "number",
|
|
279
|
+
"numeric"
|
|
280
|
+
);
|
|
281
|
+
line.speedSequence = seq as SpeedENS;
|
|
282
|
+
} else {
|
|
283
|
+
// 合并多个层级上的速度序列,合并完以后置空
|
|
284
|
+
const oldSequences: SpeedENS[] = [];
|
|
285
|
+
for (const layer of line.eventLayers) {
|
|
286
|
+
const ly = layer as {speed: SpeedENS};
|
|
287
|
+
if (ly.speed) {
|
|
288
|
+
oldSequences.push(ly.speed);
|
|
289
|
+
ly.speed = null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
let seq: SpeedENS;
|
|
293
|
+
if (oldSequences.length > 1) {
|
|
294
|
+
|
|
295
|
+
seq = EventNodeSequence.mergeSequences(
|
|
296
|
+
oldSequences
|
|
297
|
+
) as SpeedENS;
|
|
298
|
+
seq.updateFloorPositionAfter((seq as SpeedENS).head.next, timeCalculator);
|
|
299
|
+
} else if (oldSequences.length === 1) {
|
|
300
|
+
chart.registerEventNodeSequence(EventType.speed, `#${line.id}.speed`, oldSequences[0]);
|
|
301
|
+
seq = oldSequences[0];
|
|
302
|
+
seq.updateFloorPositionAfter(seq.head.next, timeCalculator);
|
|
303
|
+
} else {
|
|
304
|
+
seq = chart.createEventNodeSequence(EventType.speed, `#${line.id}.speed`) as SpeedENS;
|
|
305
|
+
}
|
|
306
|
+
line.speedSequence = seq as SpeedENS;
|
|
307
|
+
}
|
|
235
308
|
line.extendedLayer.scaleX = data.extended?.scaleXEvents
|
|
236
|
-
? chart.sequenceMap.get(
|
|
309
|
+
? unwrap(chart.sequenceMap.get(
|
|
310
|
+
data.extended.scaleXEvents),
|
|
311
|
+
v => typeof v === "number",
|
|
312
|
+
"numeric"
|
|
313
|
+
)
|
|
237
314
|
: chart.createEventNodeSequence(EventType.scaleX, `#${line.id}.ex.scaleX`);
|
|
238
315
|
line.extendedLayer.scaleY = data.extended?.scaleYEvents
|
|
239
|
-
? chart.sequenceMap.get(
|
|
316
|
+
? unwrap(chart.sequenceMap.get(
|
|
317
|
+
data.extended.scaleYEvents),
|
|
318
|
+
v => typeof v === "number",
|
|
319
|
+
"numeric"
|
|
320
|
+
)
|
|
240
321
|
: chart.createEventNodeSequence(EventType.scaleY, `#${line.id}.ex.scaleY`);
|
|
241
322
|
if (data.extended) {
|
|
242
323
|
if (data.extended.textEvents) {
|
|
243
|
-
line.extendedLayer.text = chart.sequenceMap.get(data.extended.textEvents);
|
|
324
|
+
line.extendedLayer.text = unwrap(chart.sequenceMap.get(data.extended.textEvents), v => typeof v === "string", "text");
|
|
244
325
|
}
|
|
245
326
|
if (data.extended.colorEvents) {
|
|
246
|
-
line.extendedLayer.color = chart.sequenceMap.get(data.extended.colorEvents);
|
|
327
|
+
line.extendedLayer.color = unwrap(chart.sequenceMap.get(data.extended.colorEvents), v => Array.isArray(v), "color");
|
|
247
328
|
}
|
|
248
329
|
}
|
|
249
330
|
|
|
@@ -302,27 +383,48 @@ export class JudgeLine {
|
|
|
302
383
|
return this.eventLayers[index];
|
|
303
384
|
}
|
|
304
385
|
}
|
|
386
|
+
/*
|
|
387
|
+
应该是古老代码,现在不用了,暂时不移除
|
|
305
388
|
updateSpeedIntegralFrom(beats: number, timeCalculator: TimeCalculator) {
|
|
306
|
-
for (
|
|
389
|
+
for (const eventLayer of this.eventLayers) {
|
|
307
390
|
eventLayer?.speed?.updateNodesIntegralFrom(beats, timeCalculator);
|
|
308
391
|
}
|
|
309
392
|
}
|
|
393
|
+
//*/
|
|
394
|
+
/**
|
|
395
|
+
* 判定线当前所在的FloorPosition
|
|
396
|
+
*
|
|
397
|
+
* 可以理解为:有一个假想的充满音符的瀑布流,判定线在其中或前进或后退
|
|
398
|
+
*/
|
|
399
|
+
currentFloorPosition: number;
|
|
400
|
+
cachedFloorPositions: Float64Array;
|
|
401
|
+
computeCurrentFloorPosition(beats: number, timeCalculator: TimeCalculator) {
|
|
402
|
+
this.currentFloorPosition = this.speedSequence.getFloorPositionAt(beats, timeCalculator);
|
|
403
|
+
}
|
|
404
|
+
getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator) {
|
|
405
|
+
return this.speedSequence.getFloorPositionAt(beats, timeCalculator) - this.currentFloorPosition;
|
|
406
|
+
}
|
|
310
407
|
/**
|
|
408
|
+
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
409
|
+
*
|
|
410
|
+
* KPA内核代码中最大的一坨史山,没有之一。
|
|
411
|
+
*
|
|
412
|
+
* 谱面渲染时最耗时的函数
|
|
413
|
+
*
|
|
311
414
|
* startY and endY must not be negative
|
|
312
415
|
* @param beats
|
|
313
416
|
* @param timeCalculator
|
|
314
417
|
* @param startY
|
|
315
418
|
* @param endY
|
|
316
419
|
* @returns
|
|
317
|
-
|
|
420
|
+
* /
|
|
318
421
|
computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
|
|
319
|
-
console.log("invoked")
|
|
320
422
|
//return [[0, Infinity]]
|
|
321
423
|
//*
|
|
322
424
|
// 提取所有有变化的时间点
|
|
323
425
|
let times: number[] = [];
|
|
324
|
-
|
|
325
|
-
for (
|
|
426
|
+
const result: [number, number][] = [];
|
|
427
|
+
for (const eventLayer of this.eventLayers) {
|
|
326
428
|
const sequence = eventLayer?.speed;
|
|
327
429
|
if (!sequence) {
|
|
328
430
|
continue;
|
|
@@ -330,7 +432,7 @@ export class JudgeLine {
|
|
|
330
432
|
let node: EventStartNode = sequence.getNodeAt(beats);
|
|
331
433
|
let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
|
|
332
434
|
while (true) {
|
|
333
|
-
times.push(
|
|
435
|
+
times.push(TC.toBeats(node.time))
|
|
334
436
|
if ((endNode = node.next).type === NodeType.TAIL) {
|
|
335
437
|
break;
|
|
336
438
|
}
|
|
@@ -341,7 +443,7 @@ export class JudgeLine {
|
|
|
341
443
|
times = [...new Set(times)].sort((a, b) => a - b)
|
|
342
444
|
const len = times.length;
|
|
343
445
|
let nextTime = times[0]
|
|
344
|
-
let nextPosY = this.
|
|
446
|
+
let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
345
447
|
let nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
346
448
|
let range: [number, number] = [undefined, undefined];
|
|
347
449
|
// console.log(times)
|
|
@@ -354,14 +456,14 @@ export class JudgeLine {
|
|
|
354
456
|
thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
|
|
355
457
|
}
|
|
356
458
|
nextTime = times[i + 1]
|
|
357
|
-
nextPosY = this.
|
|
459
|
+
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
|
|
358
460
|
nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
359
461
|
// console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
|
|
360
462
|
if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
|
|
361
463
|
//debugger;
|
|
362
464
|
nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
363
465
|
nextSpeed = 0
|
|
364
|
-
nextPosY = this.
|
|
466
|
+
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
365
467
|
//debugger
|
|
366
468
|
} else {
|
|
367
469
|
// console.log("i++")
|
|
@@ -377,7 +479,7 @@ export class JudgeLine {
|
|
|
377
479
|
或a > e >= b
|
|
378
480
|
全部在区间内
|
|
379
481
|
s <= a <= b
|
|
380
|
-
|
|
482
|
+
* /
|
|
381
483
|
if (thisPosY < startY && startY <= nextPosY
|
|
382
484
|
|| thisPosY > endY && endY >= nextPosY) {
|
|
383
485
|
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
@@ -393,12 +495,6 @@ export class JudgeLine {
|
|
|
393
495
|
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
394
496
|
thisSpeed,
|
|
395
497
|
(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
498
|
result.push(range)
|
|
403
499
|
range = [undefined, undefined];
|
|
404
500
|
}
|
|
@@ -432,16 +528,203 @@ export class JudgeLine {
|
|
|
432
528
|
result.push(range)
|
|
433
529
|
}
|
|
434
530
|
}
|
|
531
|
+
return result;
|
|
532
|
+
//* /
|
|
533
|
+
}*/
|
|
534
|
+
/**
|
|
535
|
+
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
536
|
+
*
|
|
537
|
+
* KPA内核代码中最大的一坨史山,没有之一。
|
|
538
|
+
*
|
|
539
|
+
* 谜面渲染时最耗时的函数
|
|
540
|
+
*
|
|
541
|
+
* 调用此方法前需要先更新判定线当前的FP。
|
|
542
|
+
*
|
|
543
|
+
* startY and endY must not be negative
|
|
544
|
+
* @param beats
|
|
545
|
+
* @param timeCalculator
|
|
546
|
+
* @param startY
|
|
547
|
+
* @param endY
|
|
548
|
+
* @returns
|
|
549
|
+
*/
|
|
550
|
+
computeTimeRange(beats: number, timeCalculator: TimeCalculator, startY: number, endY: number): [number, number][] {
|
|
551
|
+
//return [[0, Infinity]]
|
|
552
|
+
//*
|
|
553
|
+
const result: [number, number][] = [];
|
|
554
|
+
|
|
555
|
+
// 直接使用speedSequence,不再遍历所有事件层
|
|
556
|
+
if (!this.speedSequence) {
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
const speedSequence = this.speedSequence;
|
|
560
|
+
const lineMonotonicity = speedSequence.monotonicity ?? Monotonicity.swinging;
|
|
561
|
+
|
|
562
|
+
const currentJudgeLineFloorPos = this.currentFloorPosition;
|
|
563
|
+
|
|
564
|
+
// 获取起始节点
|
|
565
|
+
let startNode: EventStartNode<number> = this.speedSequence.getNodeAt(beats);
|
|
566
|
+
let range: [number, number] = [undefined, undefined];
|
|
567
|
+
// 启用遮罩时,两个Y边界都是正数,直接返回空数组
|
|
568
|
+
if (lineMonotonicity === Monotonicity.increasing && startY >= 0 && endY > 0) {
|
|
569
|
+
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
const computeTime = (speed: number, currentPos: number, fore: number) =>
|
|
575
|
+
timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
|
|
576
|
+
|
|
577
|
+
// 遍历所有事件节点直到结尾
|
|
578
|
+
while (true) {
|
|
579
|
+
const thisTime = TC.toBeats(startNode.time);
|
|
580
|
+
const endNode = startNode.next;
|
|
581
|
+
const nextStart = endNode.next;
|
|
582
|
+
if (endNode.type === NodeType.TAIL) {
|
|
583
|
+
// 处理最后一个节点到无穷大的情况
|
|
584
|
+
const thisPosY = startNode.floorPosition - currentJudgeLineFloorPos;
|
|
585
|
+
const thisSpeed = startNode.value;
|
|
586
|
+
|
|
587
|
+
const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY);
|
|
588
|
+
|
|
589
|
+
if (range[0] === undefined) {
|
|
590
|
+
if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
|
|
591
|
+
range[0] = computeTime(
|
|
592
|
+
thisSpeed,
|
|
593
|
+
(thisPosY < inf ? startY : endY) - thisPosY,
|
|
594
|
+
thisTime)
|
|
595
|
+
} else if (thisSpeed === 0) {
|
|
596
|
+
range[0] = 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (range[0] !== undefined) {
|
|
601
|
+
if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
|
|
602
|
+
range[1] = computeTime(
|
|
603
|
+
thisSpeed,
|
|
604
|
+
(thisPosY > inf ? startY : endY) - thisPosY,
|
|
605
|
+
thisTime)
|
|
606
|
+
result.push(range)
|
|
607
|
+
} else if (thisSpeed === 0) {
|
|
608
|
+
range[1] = Infinity;
|
|
609
|
+
result.push(range)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
const nextTime = TC.toBeats(nextStart.time);
|
|
617
|
+
|
|
618
|
+
const thisPosY = startNode.floorPosition - currentJudgeLineFloorPos;
|
|
619
|
+
const nextPosY = nextStart.floorPosition - currentJudgeLineFloorPos;
|
|
620
|
+
|
|
621
|
+
let thisSpeed = startNode.value;
|
|
622
|
+
let nextSpeed = nextStart.value;
|
|
623
|
+
|
|
624
|
+
if (Math.abs(thisSpeed) < 1e-8) {
|
|
625
|
+
thisSpeed = 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (Math.abs(nextSpeed) < 1e-8) {
|
|
629
|
+
nextSpeed = 0;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// 处理速度变号的情况
|
|
633
|
+
if (thisSpeed * nextSpeed < 0) {
|
|
634
|
+
// 计算速度为0的时间点
|
|
635
|
+
const zeroTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
636
|
+
const zeroPosY = speedSequence.getFloorPositionAt(zeroTime, timeCalculator) - currentJudgeLineFloorPos;
|
|
637
|
+
const zeroSpeed = 0;
|
|
638
|
+
|
|
639
|
+
// 处理第一段(开始到零点)
|
|
640
|
+
if (range[0] === undefined) {
|
|
641
|
+
if (thisPosY < startY && startY <= zeroPosY || thisPosY > endY && endY >= zeroPosY) {
|
|
642
|
+
range[0] = thisSpeed !== zeroSpeed ? thisTime : computeTime(
|
|
643
|
+
thisSpeed,
|
|
644
|
+
(thisPosY < zeroPosY ? startY : endY) - thisPosY, thisTime
|
|
645
|
+
);
|
|
646
|
+
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
647
|
+
range[0] = thisTime;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (range[0] !== undefined) {
|
|
652
|
+
if (thisPosY < endY && endY <= zeroPosY || thisPosY > startY && startY >= zeroPosY) {
|
|
653
|
+
range[1] = thisSpeed !== zeroSpeed ? zeroTime : computeTime(
|
|
654
|
+
thisSpeed,
|
|
655
|
+
(thisPosY > zeroPosY ? startY : endY) - thisPosY, thisTime)
|
|
656
|
+
result.push(range);
|
|
657
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
658
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
range = [undefined, undefined];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 处理第二段(零点到结束)
|
|
666
|
+
if (range[0] === undefined) {
|
|
667
|
+
if (zeroPosY < startY && startY <= nextPosY || zeroPosY > endY && endY >= nextPosY) {
|
|
668
|
+
range[0] = zeroSpeed !== nextSpeed ? zeroTime : computeTime(
|
|
669
|
+
nextSpeed,
|
|
670
|
+
(zeroPosY < nextPosY ? startY : endY) - zeroPosY, zeroTime)
|
|
671
|
+
} else if (startY <= zeroPosY && zeroPosY <= endY) {
|
|
672
|
+
range[0] = zeroTime;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (range[0] !== undefined) {
|
|
677
|
+
if (zeroPosY < endY && endY <= nextPosY || zeroPosY > startY && startY >= nextPosY) {
|
|
678
|
+
range[1] = zeroSpeed !== nextSpeed ? nextTime : computeTime(
|
|
679
|
+
nextSpeed,
|
|
680
|
+
(zeroPosY > nextPosY ? startY : endY) - zeroPosY, zeroTime)
|
|
681
|
+
result.push(range)
|
|
682
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
683
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
range = [undefined, undefined];
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
// 正常情况处理
|
|
691
|
+
if (range[0] === undefined) {
|
|
692
|
+
if (thisPosY < startY && startY <= nextPosY || thisPosY > endY && endY >= nextPosY) {
|
|
693
|
+
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
694
|
+
thisSpeed,
|
|
695
|
+
(thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
696
|
+
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
697
|
+
range[0] = thisTime;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (range[0] !== undefined) {
|
|
702
|
+
if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
|
|
703
|
+
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
704
|
+
thisSpeed,
|
|
705
|
+
(thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
706
|
+
result.push(range);
|
|
707
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
708
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
711
|
+
range = [undefined, undefined];
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// 我挺希望能够显式内联上面的……复用性也太低了
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// 移动到下一个节点
|
|
718
|
+
startNode = nextStart;
|
|
719
|
+
}
|
|
720
|
+
|
|
435
721
|
return result;
|
|
436
722
|
//*/
|
|
437
723
|
}
|
|
438
|
-
|
|
439
|
-
computeLinePositionY(beats: number, timeCalculator: TimeCalculator) {
|
|
440
|
-
return this.getStackedIntegral(beats, timeCalculator)
|
|
441
|
-
}
|
|
442
|
-
*/
|
|
724
|
+
|
|
443
725
|
/**
|
|
444
726
|
*
|
|
727
|
+
* @deprecated 1.7.0
|
|
445
728
|
* @param beats
|
|
446
729
|
* @param usePrev 如果取到节点,将使用EndNode的值。默认为FALSE
|
|
447
730
|
* @returns
|
|
@@ -454,10 +737,6 @@ export class JudgeLine {
|
|
|
454
737
|
this.getStackedValue("alpha", beats, usePrev),
|
|
455
738
|
]
|
|
456
739
|
}
|
|
457
|
-
getMatrix(beats: number, usePrev = false) {
|
|
458
|
-
const base = this.father.getMatrix(beats, usePrev);
|
|
459
|
-
const x = this
|
|
460
|
-
}
|
|
461
740
|
getStackedValue(type: keyof EventLayer, beats: number, usePrev: boolean = false) {
|
|
462
741
|
const length = this.eventLayers.length;
|
|
463
742
|
let current = 0;
|
|
@@ -470,7 +749,17 @@ export class JudgeLine {
|
|
|
470
749
|
}
|
|
471
750
|
return current
|
|
472
751
|
}
|
|
473
|
-
|
|
752
|
+
/**
|
|
753
|
+
* 获取指定时间点的FloorPosition。
|
|
754
|
+
*
|
|
755
|
+
* <del>为了向后兼容,保留了多层速度事件的机制。</del>
|
|
756
|
+
*
|
|
757
|
+
* 已经删除了多层速度事件
|
|
758
|
+
* @param beats
|
|
759
|
+
* @param timeCalculator
|
|
760
|
+
* @returns
|
|
761
|
+
* /
|
|
762
|
+
getStackedFloorPosition(beats: number, timeCalculator: TimeCalculator) {
|
|
474
763
|
|
|
475
764
|
const length = this.eventLayers.length;
|
|
476
765
|
let current = 0;
|
|
@@ -479,11 +768,11 @@ export class JudgeLine {
|
|
|
479
768
|
if (!layer || !layer.speed) {
|
|
480
769
|
break;
|
|
481
770
|
}
|
|
482
|
-
current += layer.speed.
|
|
771
|
+
current += layer.speed.getFloorPositionAt(beats, timeCalculator);
|
|
483
772
|
}
|
|
484
773
|
// console.log("integral", current)
|
|
485
774
|
return current;
|
|
486
|
-
}
|
|
775
|
+
}//*/
|
|
487
776
|
/**
|
|
488
777
|
* 获取对应速度和类型的Note树,没有则创建
|
|
489
778
|
*/
|
|
@@ -495,7 +784,7 @@ export class JudgeLine {
|
|
|
495
784
|
return list;
|
|
496
785
|
}
|
|
497
786
|
}
|
|
498
|
-
const list = isHold ? new HNList(speed, medianYOffset, this.chart.
|
|
787
|
+
const list = isHold ? new HNList(speed, medianYOffset, this.chart.effectiveBeats) : new NNList(speed, medianYOffset, this.chart.effectiveBeats)
|
|
499
788
|
list.parentLine = this;
|
|
500
789
|
NoteNode.connect(list.head, list.tail)
|
|
501
790
|
if (initsJump) list.initJump();
|
|
@@ -516,17 +805,17 @@ export class JudgeLine {
|
|
|
516
805
|
* @param eventNodeSequences To Collect the sequences used in this line
|
|
517
806
|
* @returns
|
|
518
807
|
*/
|
|
519
|
-
dumpKPA(eventNodeSequences: Set<EventNodeSequence<any>>, judgeLineGroups: JudgeLineGroup[]):
|
|
520
|
-
const children:
|
|
521
|
-
for (
|
|
808
|
+
dumpKPA(eventNodeSequences: Set<EventNodeSequence<any>>, judgeLineGroups: JudgeLineGroup[]): JudgeLineDataKPA2 {
|
|
809
|
+
const children: JudgeLineDataKPA2[] = [];
|
|
810
|
+
for (const line of this.children) {
|
|
522
811
|
children.push(line.dumpKPA(eventNodeSequences, judgeLineGroups))
|
|
523
812
|
}
|
|
524
813
|
const eventLayers: EventLayerDataKPA[] = [];
|
|
525
814
|
for (let i = 0; i < this.eventLayers.length; i++) {
|
|
526
815
|
const layer = this.eventLayers[i];
|
|
527
816
|
if (!layer) continue;
|
|
528
|
-
|
|
529
|
-
for (
|
|
817
|
+
const layerData = {}
|
|
818
|
+
for (const type in layer) {
|
|
530
819
|
const sequence = layer[type as keyof EventLayer];
|
|
531
820
|
if (!sequence) continue;
|
|
532
821
|
eventNodeSequences.add(sequence);
|
|
@@ -536,10 +825,10 @@ export class JudgeLine {
|
|
|
536
825
|
}
|
|
537
826
|
const hnListsData = {};
|
|
538
827
|
const nnListsData = {};
|
|
539
|
-
for (
|
|
828
|
+
for (const [id, list] of this.hnLists) {
|
|
540
829
|
hnListsData[id] = list.dumpKPA();
|
|
541
830
|
}
|
|
542
|
-
for (
|
|
831
|
+
for (const [id, list] of this.nnLists) {
|
|
543
832
|
nnListsData[id] = list.dumpKPA();
|
|
544
833
|
}
|
|
545
834
|
const extended = {
|
|
@@ -557,15 +846,17 @@ export class JudgeLine {
|
|
|
557
846
|
if (this.extendedLayer.text) {
|
|
558
847
|
eventNodeSequences.add(this.extendedLayer.text);
|
|
559
848
|
}
|
|
849
|
+
eventNodeSequences.add(this.speedSequence); // 忘了这个,特此留念(
|
|
560
850
|
return {
|
|
561
851
|
group: judgeLineGroups.indexOf(this.group),
|
|
562
852
|
id: this.id,
|
|
563
|
-
|
|
564
|
-
|
|
853
|
+
name: this.name,
|
|
854
|
+
texture: this.texture,
|
|
565
855
|
anchor: this.anchor,
|
|
566
856
|
rotatesWithFather: this.rotatesWithFather,
|
|
567
857
|
children: children,
|
|
568
858
|
eventLayers: eventLayers,
|
|
859
|
+
speedEventNodeSeq: this.speedSequence?.id,
|
|
569
860
|
hnLists: hnListsData,
|
|
570
861
|
nnLists: nnListsData,
|
|
571
862
|
cover: this.cover,
|
|
@@ -575,26 +866,36 @@ export class JudgeLine {
|
|
|
575
866
|
}
|
|
576
867
|
}
|
|
577
868
|
|
|
869
|
+
/*
|
|
870
|
+
暂时不用此方法
|
|
871
|
+
updateNNListFloorPositions() {
|
|
872
|
+
for (const lists of [this.nnLists, this.hnLists]) {
|
|
873
|
+
for (const list of lists) {
|
|
874
|
+
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
*/
|
|
578
879
|
|
|
579
880
|
updateEffectiveBeats(EB: number) {
|
|
580
881
|
for (let i = 0; i < this.eventLayers.length; i++) {
|
|
581
882
|
const layer = this.eventLayers[i];
|
|
582
|
-
for (
|
|
883
|
+
for (const type in layer) {
|
|
583
884
|
const sequence = layer[type as keyof EventLayer];
|
|
584
885
|
sequence.effectiveBeats = EB;
|
|
585
886
|
}
|
|
586
887
|
}
|
|
587
|
-
for (
|
|
588
|
-
for (
|
|
888
|
+
for (const lists of [this.nnLists, this.hnLists]) {
|
|
889
|
+
for (const [_, list] of lists) {
|
|
589
890
|
list.effectiveBeats = EB;
|
|
590
891
|
}
|
|
591
892
|
}
|
|
592
893
|
}
|
|
593
894
|
static checkinterdependency(judgeLine: JudgeLine, toBeFather: JudgeLine) {
|
|
594
|
-
|
|
895
|
+
const descendantsAndSelf = new Set<JudgeLine>();
|
|
595
896
|
const add = (line: JudgeLine) => {
|
|
596
897
|
descendantsAndSelf.add(line);
|
|
597
|
-
for (
|
|
898
|
+
for (const child of line.children) {
|
|
598
899
|
add(child);
|
|
599
900
|
}
|
|
600
901
|
}
|