kipphi 2.1.3-beta.2 → 2.1.3
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/event.ts +1 -1
- package/index.d.ts +5 -128
- package/index.js +116 -8
- package/judgeline.ts +0 -127
- package/note.ts +6 -1
- package/operation/event.ts +9 -1
- package/package.json +1 -1
- package/rpeChartCompiler.ts +47 -9
- package/util.ts +50 -0
package/event.ts
CHANGED
|
@@ -391,7 +391,7 @@ export class EventStartNode<VT extends EventValueESType = number> extends EventN
|
|
|
391
391
|
return super.clone(offset) as EventStartNode<VT>;
|
|
392
392
|
};
|
|
393
393
|
clonePair(offset: TimeT): EventStartNode<VT> {
|
|
394
|
-
const endNode = this.previous.type !== NodeType.HEAD ? this.previous.clone(offset) :
|
|
394
|
+
const endNode = this.previous.type !== NodeType.HEAD ? this.previous.clone(offset) : null;
|
|
395
395
|
const startNode = this.clone(offset);
|
|
396
396
|
EventNode.connect(endNode, startNode);
|
|
397
397
|
return startNode;
|
package/index.d.ts
CHANGED
|
@@ -1081,133 +1081,6 @@ declare module "judgeline" {
|
|
|
1081
1081
|
cachedFloorPositions: Float64Array;
|
|
1082
1082
|
computeCurrentFloorPosition(beats: number, timeCalculator: TimeCalculator): void;
|
|
1083
1083
|
getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator): number;
|
|
1084
|
-
/**
|
|
1085
|
-
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
1086
|
-
*
|
|
1087
|
-
* KPA内核代码中最大的一坨史山,没有之一。
|
|
1088
|
-
*
|
|
1089
|
-
* 谱面渲染时最耗时的函数
|
|
1090
|
-
*
|
|
1091
|
-
* startY and endY must not be negative
|
|
1092
|
-
* @param beats
|
|
1093
|
-
* @param timeCalculator
|
|
1094
|
-
* @param startY
|
|
1095
|
-
* @param endY
|
|
1096
|
-
* @returns
|
|
1097
|
-
* /
|
|
1098
|
-
computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
|
|
1099
|
-
//return [[0, Infinity]]
|
|
1100
|
-
//*
|
|
1101
|
-
// 提取所有有变化的时间点
|
|
1102
|
-
let times: number[] = [];
|
|
1103
|
-
const result: [number, number][] = [];
|
|
1104
|
-
for (const eventLayer of this.eventLayers) {
|
|
1105
|
-
const sequence = eventLayer?.speed;
|
|
1106
|
-
if (!sequence) {
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
let node: EventStartNode = sequence.getNodeAt(beats);
|
|
1110
|
-
let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
|
|
1111
|
-
while (true) {
|
|
1112
|
-
times.push(TC.toBeats(node.time))
|
|
1113
|
-
if ((endNode = node.next).type === NodeType.TAIL) {
|
|
1114
|
-
break;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
node = endNode.next
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
times = [...new Set(times)].sort((a, b) => a - b)
|
|
1121
|
-
const len = times.length;
|
|
1122
|
-
let nextTime = times[0]
|
|
1123
|
-
let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
1124
|
-
let nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
1125
|
-
let range: [number, number] = [undefined, undefined];
|
|
1126
|
-
// console.log(times)
|
|
1127
|
-
const computeTime = (speed: number, currentPos: number, fore: number) => timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
|
|
1128
|
-
for (let i = 0; i < len - 1;) {
|
|
1129
|
-
const thisTime = nextTime;
|
|
1130
|
-
const thisPosY = nextPosY;
|
|
1131
|
-
let thisSpeed = this.getStackedValue("speed", thisTime);
|
|
1132
|
-
if (Math.abs(thisSpeed) < 1e-8) {
|
|
1133
|
-
thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
|
|
1134
|
-
}
|
|
1135
|
-
nextTime = times[i + 1]
|
|
1136
|
-
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
|
|
1137
|
-
nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
1138
|
-
// console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
|
|
1139
|
-
if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
|
|
1140
|
-
//debugger;
|
|
1141
|
-
nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
1142
|
-
nextSpeed = 0
|
|
1143
|
-
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
1144
|
-
//debugger
|
|
1145
|
-
} else {
|
|
1146
|
-
// console.log("i++")
|
|
1147
|
-
i++
|
|
1148
|
-
}
|
|
1149
|
-
if (range[0] === undefined) {
|
|
1150
|
-
// 变速区间直接全部囊括,匀速要算一下,因为好算
|
|
1151
|
-
/*
|
|
1152
|
-
设两个时间点的位置为a,b
|
|
1153
|
-
开始结束点为s,e
|
|
1154
|
-
选中小段一部分在区间内:
|
|
1155
|
-
a < s <= b
|
|
1156
|
-
或a > e >= b
|
|
1157
|
-
全部在区间内
|
|
1158
|
-
s <= a <= b
|
|
1159
|
-
* /
|
|
1160
|
-
if (thisPosY < startY && startY <= nextPosY
|
|
1161
|
-
|| thisPosY > endY && endY >= nextPosY) {
|
|
1162
|
-
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
1163
|
-
thisSpeed,
|
|
1164
|
-
(thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
1165
|
-
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
1166
|
-
range[0] = thisTime;
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
// 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
|
|
1170
|
-
if (range[0] !== undefined) {
|
|
1171
|
-
if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
|
|
1172
|
-
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
1173
|
-
thisSpeed,
|
|
1174
|
-
(thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
1175
|
-
result.push(range)
|
|
1176
|
-
range = [undefined, undefined];
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
const thisPosY = nextPosY;
|
|
1181
|
-
const thisTime = nextTime;
|
|
1182
|
-
const thisSpeed = this.getStackedValue("speed", thisTime);
|
|
1183
|
-
const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY)
|
|
1184
|
-
if (range[0] === undefined) {
|
|
1185
|
-
// 变速区间直接全部囊括,匀速要算一下,因为好算
|
|
1186
|
-
if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
|
|
1187
|
-
range[0] = computeTime(
|
|
1188
|
-
thisSpeed,
|
|
1189
|
-
(thisPosY < inf ? startY : endY) - thisPosY,
|
|
1190
|
-
thisTime)
|
|
1191
|
-
} else if (thisSpeed === 0) {
|
|
1192
|
-
range[0] = 0;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
// 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
|
|
1196
|
-
if (range[0] !== undefined) {
|
|
1197
|
-
if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
|
|
1198
|
-
range[1] = computeTime(
|
|
1199
|
-
thisSpeed,
|
|
1200
|
-
(thisPosY > inf ? startY : endY) - thisPosY,
|
|
1201
|
-
thisTime)
|
|
1202
|
-
result.push(range)
|
|
1203
|
-
} else if (thisSpeed === 0) {
|
|
1204
|
-
range[1] = Infinity;
|
|
1205
|
-
result.push(range)
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
return result;
|
|
1209
|
-
//* /
|
|
1210
|
-
}*/
|
|
1211
1084
|
/**
|
|
1212
1085
|
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
1213
1086
|
*
|
|
@@ -2608,6 +2481,9 @@ declare module "operation/event" {
|
|
|
2608
2481
|
overlapping: boolean;
|
|
2609
2482
|
constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP?: boolean);
|
|
2610
2483
|
}
|
|
2484
|
+
export class EventNodePairAutoInsertOperation<VT extends EventValueESType> extends EventNodePairInsertOperation<VT> {
|
|
2485
|
+
constructor(node: EventStartNode<VT>, parentSeq: EventNodeSequence<VT>, updatesFP?: boolean);
|
|
2486
|
+
}
|
|
2611
2487
|
export class EventNodeValueChangeOperation<VT extends EventValueESType> extends Operation {
|
|
2612
2488
|
updatesEditor: boolean;
|
|
2613
2489
|
node: EventNode<VT>;
|
|
@@ -3082,7 +2958,7 @@ declare module "rpeChartCompiler" {
|
|
|
3082
2958
|
import type { Chart } from "chart";
|
|
3083
2959
|
import { type TimeT, type ChartDataRPE, type JudgeLineDataRPE, type EventDataRPELike, type NoteDataRPE, type EventValueESType } from "chartTypes";
|
|
3084
2960
|
import { type EasedEvaluatorOfType } from "evaluator";
|
|
3085
|
-
import { EventEndNode, EventNodeSequence, EventStartNode } from "event";
|
|
2961
|
+
import { EventEndNode, EventNodeSequence, EventStartNode, SpeedENS } from "event";
|
|
3086
2962
|
import type { JudgeLine } from "judgeline";
|
|
3087
2963
|
import type { NNList, HNList } from "note";
|
|
3088
2964
|
/**
|
|
@@ -3100,6 +2976,7 @@ declare module "rpeChartCompiler" {
|
|
|
3100
2976
|
evaluator: EasedEvaluatorOfType<VT>;
|
|
3101
2977
|
}, getValue: (node: EventStartNode<VT> | EventEndNode<VT>) => VT): EventDataRPELike<VT>;
|
|
3102
2978
|
dumpEventNodeSequence<VT extends EventValueESType>(sequence: EventNodeSequence<VT>): EventDataRPELike<VT>[];
|
|
2979
|
+
dumpSpeedENS(seq: SpeedENS): EventDataRPELike<number>[];
|
|
3103
2980
|
compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[];
|
|
3104
2981
|
/**
|
|
3105
2982
|
* 倒序转换为数组
|
package/index.js
CHANGED
|
@@ -24,6 +24,61 @@ var checkType = (value, type) => {
|
|
|
24
24
|
if (Array.isArray(type)) {
|
|
25
25
|
return Array.isArray(value) && value.length === type.length && type.every((t, i) => checkType(value[i], t));
|
|
26
26
|
} else if (typeof type === "string") {
|
|
27
|
+
if (type.startsWith("int")) {
|
|
28
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const match = type.match(/^int(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
|
|
32
|
+
if (!match) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const [, leftBrac, left, right, rightBrac] = match;
|
|
36
|
+
if (!leftBrac) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const leftN = left === "-" ? -Infinity : Number(left);
|
|
40
|
+
const rightN = right === "+" ? Infinity : Number(right);
|
|
41
|
+
if (value < leftN) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (leftBrac === "(" && value === leftN) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (value > rightN) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (rightBrac === ")" && value === rightN) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
} else if (type.startsWith("number")) {
|
|
55
|
+
if (typeof value !== "number") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const match = type.match(/^number(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const [, leftBrac, left, right, rightBrac] = match;
|
|
63
|
+
if (!leftBrac) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const leftN = left === "-" ? -Infinity : Number(left);
|
|
67
|
+
const rightN = right === "+" ? Infinity : Number(right);
|
|
68
|
+
if (value < leftN) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (leftBrac === "(" && value === leftN) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (value > rightN) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (rightBrac === ")" && value === rightN) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
27
82
|
return typeof value === type;
|
|
28
83
|
} else {
|
|
29
84
|
return value instanceof type;
|
|
@@ -1645,7 +1700,7 @@ class EventStartNode extends EventNode {
|
|
|
1645
1700
|
return super.clone(offset);
|
|
1646
1701
|
}
|
|
1647
1702
|
clonePair(offset) {
|
|
1648
|
-
const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) :
|
|
1703
|
+
const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) : null;
|
|
1649
1704
|
const startNode = this.clone(offset);
|
|
1650
1705
|
EventNode.connect(endNode, startNode);
|
|
1651
1706
|
return startNode;
|
|
@@ -2280,7 +2335,12 @@ class NoteNode extends NoteNodeLike {
|
|
|
2280
2335
|
}
|
|
2281
2336
|
}
|
|
2282
2337
|
remove(note) {
|
|
2283
|
-
this.notes.
|
|
2338
|
+
const index = this.notes.indexOf(note);
|
|
2339
|
+
if (index === -1) {
|
|
2340
|
+
console.warn("Note not found in this node!");
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
this.notes.splice(index, 1);
|
|
2284
2344
|
note.parentNode = null;
|
|
2285
2345
|
}
|
|
2286
2346
|
static disconnect(note1, note2) {
|
|
@@ -3857,6 +3917,7 @@ __export(exports_operation, {
|
|
|
3857
3917
|
EventNodePairRemoveOperation: () => EventNodePairRemoveOperation,
|
|
3858
3918
|
EventNodePairInsertOrOverwriteOperation: () => EventNodePairInsertOrOverwriteOperation,
|
|
3859
3919
|
EventNodePairInsertOperation: () => EventNodePairInsertOperation,
|
|
3920
|
+
EventNodePairAutoInsertOperation: () => EventNodePairAutoInsertOperation,
|
|
3860
3921
|
EventNodeMacroTimeReevaluateOperation: () => EventNodeMacroTimeReevaluateOperation,
|
|
3861
3922
|
EventNodeEvaluatorChangeOperation: () => EventNodeEvaluatorChangeOperation,
|
|
3862
3923
|
EventInterpolationOperation: () => EventInterpolationOperation,
|
|
@@ -4192,6 +4253,12 @@ class EventNodePairInsertOrOverwriteOperation extends UnionOperation {
|
|
|
4192
4253
|
}
|
|
4193
4254
|
}
|
|
4194
4255
|
|
|
4256
|
+
class EventNodePairAutoInsertOperation extends EventNodePairInsertOperation {
|
|
4257
|
+
constructor(node, parentSeq, updatesFP = true) {
|
|
4258
|
+
super(node, parentSeq.getNodeAt(TC2.toBeats(node.time)), updatesFP);
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4195
4262
|
class EventNodeValueChangeOperation extends Operation {
|
|
4196
4263
|
updatesEditor = true;
|
|
4197
4264
|
node;
|
|
@@ -4404,6 +4471,9 @@ class EncapsuleOperation extends ComplexOperation {
|
|
|
4404
4471
|
const sequence = easing.eventNodeSequence;
|
|
4405
4472
|
sequence.effectiveBeats = TC2.toBeats(nodeArray[nodeArray.length - 1].time);
|
|
4406
4473
|
new MultiNodeAddOperation(nodeArray, sequence).do();
|
|
4474
|
+
const first = sequence.head.next;
|
|
4475
|
+
first.evaluator = nodeArray[0].evaluator;
|
|
4476
|
+
first.value = nodeArray[0].value;
|
|
4407
4477
|
return new EncapsuleOperation(oldArray, easing);
|
|
4408
4478
|
}
|
|
4409
4479
|
}
|
|
@@ -5343,8 +5413,20 @@ class RPEChartCompiler {
|
|
|
5343
5413
|
compileChart() {
|
|
5344
5414
|
const chart2 = this.chart;
|
|
5345
5415
|
const judgeLineGroups = chart2.judgeLineGroups.map((group) => group.name);
|
|
5416
|
+
const hasNotes = (nnList) => {
|
|
5417
|
+
let node = nnList.head.next;
|
|
5418
|
+
while (true) {
|
|
5419
|
+
if (node.type === 1 /* TAIL */) {
|
|
5420
|
+
return false;
|
|
5421
|
+
}
|
|
5422
|
+
if (node.notes.length > 0) {
|
|
5423
|
+
return true;
|
|
5424
|
+
}
|
|
5425
|
+
node = node.next;
|
|
5426
|
+
}
|
|
5427
|
+
};
|
|
5346
5428
|
const filter = this.deletesEmptyLines ? (line2) => {
|
|
5347
|
-
return line2.nnLists.
|
|
5429
|
+
return [...line2.nnLists].some(([_, l]) => hasNotes(l)) || [...line2.hnLists].some(([_, l]) => hasNotes(l)) || line2.eventLayers.length > 0 && ["moveX", "moveY", "rotate", "alpha"].some((evType) => {
|
|
5348
5430
|
const seq = line2.eventLayers[0][evType];
|
|
5349
5431
|
let node = seq.head.next;
|
|
5350
5432
|
for (let i = 0;i < 2; i++) {
|
|
@@ -5427,7 +5509,7 @@ class RPEChartCompiler {
|
|
|
5427
5509
|
moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : undefined,
|
|
5428
5510
|
rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : undefined,
|
|
5429
5511
|
alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : undefined,
|
|
5430
|
-
speedEvents: index === 0 ? this.
|
|
5512
|
+
speedEvents: index === 0 ? this.dumpSpeedENS(judgeLine.speedSequence) : undefined
|
|
5431
5513
|
})),
|
|
5432
5514
|
extended: {
|
|
5433
5515
|
scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : undefined,
|
|
@@ -5525,6 +5607,32 @@ class RPEChartCompiler {
|
|
|
5525
5607
|
nodes.push(this.compileEasedEvent(newStart, getValue));
|
|
5526
5608
|
return nodes;
|
|
5527
5609
|
}
|
|
5610
|
+
dumpSpeedENS(seq) {
|
|
5611
|
+
const ret = [];
|
|
5612
|
+
let node = seq.head.next;
|
|
5613
|
+
while (true) {
|
|
5614
|
+
const end = node.next;
|
|
5615
|
+
if (end.type === 1 /* TAIL */) {
|
|
5616
|
+
break;
|
|
5617
|
+
}
|
|
5618
|
+
ret.push({
|
|
5619
|
+
start: node.value,
|
|
5620
|
+
end: end.value,
|
|
5621
|
+
startTime: node.time,
|
|
5622
|
+
endTime: end.time,
|
|
5623
|
+
linkgroup: 0
|
|
5624
|
+
});
|
|
5625
|
+
node = end.next;
|
|
5626
|
+
}
|
|
5627
|
+
ret.push({
|
|
5628
|
+
start: node.value,
|
|
5629
|
+
end: node.value,
|
|
5630
|
+
startTime: node.time,
|
|
5631
|
+
endTime: TC2.vadd(node.time, [1, 0, 1]),
|
|
5632
|
+
linkgroup: 0
|
|
5633
|
+
});
|
|
5634
|
+
return ret;
|
|
5635
|
+
}
|
|
5528
5636
|
compileNNLists(nnLists, hnLists) {
|
|
5529
5637
|
const noteLists = nnLists.map((list) => this.nnListToArray(list));
|
|
5530
5638
|
const holdLists = hnLists.map((list) => this.nnListToArray(list));
|
|
@@ -5608,8 +5716,8 @@ class RPEChartCompiler {
|
|
|
5608
5716
|
srcStart = srcSeq.head.next.value;
|
|
5609
5717
|
srcEnd = srcSeq.tail.previous.value;
|
|
5610
5718
|
leftDividedNodeSrc = srcSeq.head.next;
|
|
5611
|
-
rightDividedNodeSrc = srcSeq.tail.previous;
|
|
5612
|
-
toStopAt =
|
|
5719
|
+
rightDividedNodeSrc = srcSeq.tail.previous.previous.previous;
|
|
5720
|
+
toStopAt = srcSeq.tail.previous;
|
|
5613
5721
|
srcStartTime = srcSeq.head.next.time;
|
|
5614
5722
|
srcTimeDelta = TC2.sub(srcSeq.tail.previous.time, srcStartTime);
|
|
5615
5723
|
}
|
|
@@ -5656,13 +5764,13 @@ class RPEChartCompiler {
|
|
|
5656
5764
|
if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
5657
5765
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
5658
5766
|
} else {
|
|
5659
|
-
|
|
5767
|
+
prev.evaluator = evaluator.deriveWithEasing(new SegmentedEasing(rightDividedNodeSrc.evaluator.easing, 0, newRight));
|
|
5660
5768
|
}
|
|
5661
5769
|
} else {
|
|
5662
5770
|
if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
5663
5771
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
5664
5772
|
} else {
|
|
5665
|
-
|
|
5773
|
+
prev.evaluator = evaluator.deriveWithEasing(rightDividedNodeSrc.evaluator.easing);
|
|
5666
5774
|
}
|
|
5667
5775
|
}
|
|
5668
5776
|
const endNode2 = currentNode.next.clone();
|
package/judgeline.ts
CHANGED
|
@@ -405,133 +405,6 @@ export class JudgeLine {
|
|
|
405
405
|
getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator) {
|
|
406
406
|
return this.speedSequence.getFloorPositionAt(beats, timeCalculator) - this.currentFloorPosition;
|
|
407
407
|
}
|
|
408
|
-
/**
|
|
409
|
-
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
410
|
-
*
|
|
411
|
-
* KPA内核代码中最大的一坨史山,没有之一。
|
|
412
|
-
*
|
|
413
|
-
* 谱面渲染时最耗时的函数
|
|
414
|
-
*
|
|
415
|
-
* startY and endY must not be negative
|
|
416
|
-
* @param beats
|
|
417
|
-
* @param timeCalculator
|
|
418
|
-
* @param startY
|
|
419
|
-
* @param endY
|
|
420
|
-
* @returns
|
|
421
|
-
* /
|
|
422
|
-
computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
|
|
423
|
-
//return [[0, Infinity]]
|
|
424
|
-
//*
|
|
425
|
-
// 提取所有有变化的时间点
|
|
426
|
-
let times: number[] = [];
|
|
427
|
-
const result: [number, number][] = [];
|
|
428
|
-
for (const eventLayer of this.eventLayers) {
|
|
429
|
-
const sequence = eventLayer?.speed;
|
|
430
|
-
if (!sequence) {
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
let node: EventStartNode = sequence.getNodeAt(beats);
|
|
434
|
-
let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
|
|
435
|
-
while (true) {
|
|
436
|
-
times.push(TC.toBeats(node.time))
|
|
437
|
-
if ((endNode = node.next).type === NodeType.TAIL) {
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
node = endNode.next
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
times = [...new Set(times)].sort((a, b) => a - b)
|
|
445
|
-
const len = times.length;
|
|
446
|
-
let nextTime = times[0]
|
|
447
|
-
let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
448
|
-
let nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
449
|
-
let range: [number, number] = [undefined, undefined];
|
|
450
|
-
// console.log(times)
|
|
451
|
-
const computeTime = (speed: number, currentPos: number, fore: number) => timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
|
|
452
|
-
for (let i = 0; i < len - 1;) {
|
|
453
|
-
const thisTime = nextTime;
|
|
454
|
-
const thisPosY = nextPosY;
|
|
455
|
-
let thisSpeed = this.getStackedValue("speed", thisTime);
|
|
456
|
-
if (Math.abs(thisSpeed) < 1e-8) {
|
|
457
|
-
thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
|
|
458
|
-
}
|
|
459
|
-
nextTime = times[i + 1]
|
|
460
|
-
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
|
|
461
|
-
nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
462
|
-
// console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
|
|
463
|
-
if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
|
|
464
|
-
//debugger;
|
|
465
|
-
nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
466
|
-
nextSpeed = 0
|
|
467
|
-
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
468
|
-
//debugger
|
|
469
|
-
} else {
|
|
470
|
-
// console.log("i++")
|
|
471
|
-
i++
|
|
472
|
-
}
|
|
473
|
-
if (range[0] === undefined) {
|
|
474
|
-
// 变速区间直接全部囊括,匀速要算一下,因为好算
|
|
475
|
-
/*
|
|
476
|
-
设两个时间点的位置为a,b
|
|
477
|
-
开始结束点为s,e
|
|
478
|
-
选中小段一部分在区间内:
|
|
479
|
-
a < s <= b
|
|
480
|
-
或a > e >= b
|
|
481
|
-
全部在区间内
|
|
482
|
-
s <= a <= b
|
|
483
|
-
* /
|
|
484
|
-
if (thisPosY < startY && startY <= nextPosY
|
|
485
|
-
|| thisPosY > endY && endY >= nextPosY) {
|
|
486
|
-
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
487
|
-
thisSpeed,
|
|
488
|
-
(thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
489
|
-
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
490
|
-
range[0] = thisTime;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
// 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
|
|
494
|
-
if (range[0] !== undefined) {
|
|
495
|
-
if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
|
|
496
|
-
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
497
|
-
thisSpeed,
|
|
498
|
-
(thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
499
|
-
result.push(range)
|
|
500
|
-
range = [undefined, undefined];
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
const thisPosY = nextPosY;
|
|
505
|
-
const thisTime = nextTime;
|
|
506
|
-
const thisSpeed = this.getStackedValue("speed", thisTime);
|
|
507
|
-
const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY)
|
|
508
|
-
if (range[0] === undefined) {
|
|
509
|
-
// 变速区间直接全部囊括,匀速要算一下,因为好算
|
|
510
|
-
if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
|
|
511
|
-
range[0] = computeTime(
|
|
512
|
-
thisSpeed,
|
|
513
|
-
(thisPosY < inf ? startY : endY) - thisPosY,
|
|
514
|
-
thisTime)
|
|
515
|
-
} else if (thisSpeed === 0) {
|
|
516
|
-
range[0] = 0;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
|
|
520
|
-
if (range[0] !== undefined) {
|
|
521
|
-
if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
|
|
522
|
-
range[1] = computeTime(
|
|
523
|
-
thisSpeed,
|
|
524
|
-
(thisPosY > inf ? startY : endY) - thisPosY,
|
|
525
|
-
thisTime)
|
|
526
|
-
result.push(range)
|
|
527
|
-
} else if (thisSpeed === 0) {
|
|
528
|
-
range[1] = Infinity;
|
|
529
|
-
result.push(range)
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return result;
|
|
533
|
-
//* /
|
|
534
|
-
}*/
|
|
535
408
|
/**
|
|
536
409
|
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
537
410
|
*
|
package/note.ts
CHANGED
|
@@ -307,7 +307,12 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
remove(note: Note) {
|
|
310
|
-
this.notes.
|
|
310
|
+
const index = this.notes.indexOf(note);
|
|
311
|
+
if (index === -1) {
|
|
312
|
+
console.warn("Note not found in this node!")
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.notes.splice(index, 1)
|
|
311
316
|
note.parentNode = null
|
|
312
317
|
}
|
|
313
318
|
static disconnect(note1: NNOrHead, note2: NNOrTail) {
|
package/operation/event.ts
CHANGED
|
@@ -117,7 +117,11 @@ extends UnionOperation<LazyOperation<typeof EventNodePairInsertOperation<VT>> |
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
export class EventNodePairAutoInsertOperation<VT extends EventValueESType> extends EventNodePairInsertOperation<VT> {
|
|
121
|
+
constructor(node: EventStartNode<VT>, parentSeq: EventNodeSequence<VT>, updatesFP = true) {
|
|
122
|
+
super(node, parentSeq.getNodeAt(TC.toBeats(node.time)), updatesFP);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
121
125
|
|
|
122
126
|
export class EventNodeValueChangeOperation <VT extends EventValueESType> extends Operation {
|
|
123
127
|
updatesEditor = true
|
|
@@ -362,6 +366,10 @@ export class EncapsuleOperation extends ComplexOperation<[MultiNodeDeleteOperati
|
|
|
362
366
|
// 直接do,这个不需要做成可撤销的
|
|
363
367
|
// @ts-expect-error 这里序列类型确定,为easing,不需要传入谱面
|
|
364
368
|
new MultiNodeAddOperation(nodeArray, sequence).do();
|
|
369
|
+
// 上面这个操作不能顶替原来的第一个startnode,所以手动来一遍
|
|
370
|
+
const first = sequence.head.next;
|
|
371
|
+
first.evaluator = nodeArray[0].evaluator;
|
|
372
|
+
first.value = nodeArray[0].value;
|
|
365
373
|
|
|
366
374
|
return new EncapsuleOperation(oldArray, easing);
|
|
367
375
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kipphi",
|
|
3
3
|
"description": "Parse your Phigros Chart(.rpe.json or .kpa.json) into an editor-friendly format.",
|
|
4
|
-
"version": "2.1.3
|
|
4
|
+
"version": "2.1.3",
|
|
5
5
|
"author": "Team Zincs (https://github.com/TeamZincs)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/rpeChartCompiler.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type TimeT, type ChartDataRPE, type MetaData, type JudgeLineDataRPE, ty
|
|
|
4
4
|
import { SegmentedEasing, BezierEasing, NormalEasing, fixedEasing, TemplateEasing, Easing } from "./easing";
|
|
5
5
|
import { err } from "./env";
|
|
6
6
|
import { EasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type EasedEvaluatorConstructorOfType, type EasedEvaluatorOfType } from "./evaluator";
|
|
7
|
-
import { EventEndNode, EventNode, EventNodeSequence, EventStartNode, type EventNodeLike } from "./event";
|
|
7
|
+
import { EventEndNode, EventNode, EventNodeSequence, EventStartNode, SpeedENS, type EventNodeLike } from "./event";
|
|
8
8
|
import type { JudgeLine } from "./judgeline";
|
|
9
9
|
import type { NNList, HNList, NNOrHead } from "./note";
|
|
10
10
|
import TC from "./time";
|
|
@@ -31,11 +31,23 @@ export class RPEChartCompiler {
|
|
|
31
31
|
// console.time("compileChart")
|
|
32
32
|
const chart = this.chart;
|
|
33
33
|
const judgeLineGroups = chart.judgeLineGroups.map(group => group.name);
|
|
34
|
+
const hasNotes = (nnList: NNList) => {
|
|
35
|
+
let node = nnList.head.next;
|
|
36
|
+
while (true) {
|
|
37
|
+
if (node.type === NodeType.TAIL) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (node.notes.length > 0) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
node = node.next;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
34
46
|
const filter = this.deletesEmptyLines ? (line: JudgeLine) => {
|
|
35
|
-
return line.nnLists.
|
|
36
|
-
|| line.hnLists.
|
|
47
|
+
return [...line.nnLists].some(([_, l]) => hasNotes(l))
|
|
48
|
+
|| [...line.hnLists].some(([_, l]) => hasNotes(l))
|
|
37
49
|
|| line.eventLayers.length > 0
|
|
38
|
-
|
|
50
|
+
&& (["moveX", "moveY", "rotate", "alpha"] as const).some((evType) => {
|
|
39
51
|
const seq = line.eventLayers[0][evType];
|
|
40
52
|
let node = seq.head.next;
|
|
41
53
|
for (let i = 0; i < 2; i++) {
|
|
@@ -128,7 +140,7 @@ export class RPEChartCompiler {
|
|
|
128
140
|
moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : undefined,
|
|
129
141
|
rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : undefined,
|
|
130
142
|
alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : undefined,
|
|
131
|
-
speedEvents: index === 0 ? this.
|
|
143
|
+
speedEvents: index === 0 ? this.dumpSpeedENS(judgeLine.speedSequence) : undefined
|
|
132
144
|
})),
|
|
133
145
|
extended: {
|
|
134
146
|
scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : undefined,
|
|
@@ -249,6 +261,32 @@ export class RPEChartCompiler {
|
|
|
249
261
|
|
|
250
262
|
return nodes
|
|
251
263
|
}
|
|
264
|
+
dumpSpeedENS(seq: SpeedENS): EventDataRPELike<number>[] {
|
|
265
|
+
const ret: EventDataRPELike<number>[] = [];
|
|
266
|
+
let node = seq.head.next;
|
|
267
|
+
while (true) {
|
|
268
|
+
const end = node.next;
|
|
269
|
+
if (end.type === NodeType.TAIL) {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
ret.push({
|
|
273
|
+
start: node.value,
|
|
274
|
+
end: end.value,
|
|
275
|
+
startTime: node.time,
|
|
276
|
+
endTime: end.time,
|
|
277
|
+
linkgroup: 0
|
|
278
|
+
} as EventDataRPELike<number>);
|
|
279
|
+
node = end.next;
|
|
280
|
+
}
|
|
281
|
+
ret.push({
|
|
282
|
+
start: node.value,
|
|
283
|
+
end: node.value,
|
|
284
|
+
startTime: node.time,
|
|
285
|
+
endTime: TC.vadd(node.time, [1, 0, 1]),
|
|
286
|
+
linkgroup: 0
|
|
287
|
+
} as EventDataRPELike<number>);
|
|
288
|
+
return ret;
|
|
289
|
+
}
|
|
252
290
|
|
|
253
291
|
compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[] {
|
|
254
292
|
const noteLists = nnLists.map(list => this.nnListToArray(list));
|
|
@@ -367,8 +405,8 @@ export class RPEChartCompiler {
|
|
|
367
405
|
srcStart = srcSeq.head.next!.value;
|
|
368
406
|
srcEnd = srcSeq.tail.previous!.value;
|
|
369
407
|
leftDividedNodeSrc = srcSeq.head.next!;
|
|
370
|
-
rightDividedNodeSrc = srcSeq.tail.previous!;
|
|
371
|
-
toStopAt =
|
|
408
|
+
rightDividedNodeSrc = srcSeq.tail.previous!.previous.previous!;
|
|
409
|
+
toStopAt = srcSeq.tail.previous!;
|
|
372
410
|
srcStartTime = srcSeq.head.next!.time;
|
|
373
411
|
srcTimeDelta = TC.sub(srcSeq.tail.previous!.time, srcStartTime);
|
|
374
412
|
}
|
|
@@ -450,7 +488,7 @@ export class RPEChartCompiler {
|
|
|
450
488
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
451
489
|
} else {
|
|
452
490
|
// 否则就是带缓动求值器
|
|
453
|
-
|
|
491
|
+
prev.evaluator = evaluator.deriveWithEasing(
|
|
454
492
|
new SegmentedEasing((rightDividedNodeSrc.evaluator as NumericEasedEvaluator).easing, 0.0, newRight)
|
|
455
493
|
) as unknown as Evaluator<VT>;
|
|
456
494
|
// TypeScript Compiler我*你娘啊
|
|
@@ -460,7 +498,7 @@ export class RPEChartCompiler {
|
|
|
460
498
|
if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
461
499
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
462
500
|
} else {
|
|
463
|
-
|
|
501
|
+
prev.evaluator = evaluator.deriveWithEasing(
|
|
464
502
|
(rightDividedNodeSrc.evaluator as NumericEasedEvaluator).easing
|
|
465
503
|
) as unknown as Evaluator<VT>;
|
|
466
504
|
}
|
package/util.ts
CHANGED
|
@@ -20,6 +20,56 @@ export const checkType = (value: unknown, type: string | (string | typeof Functi
|
|
|
20
20
|
&& value.length === type.length
|
|
21
21
|
&& type.every((t, i) => checkType(value[i], t))
|
|
22
22
|
} else if (typeof type === "string") {
|
|
23
|
+
if (type.startsWith("int")) {
|
|
24
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const match = type.match(/^int(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
|
|
28
|
+
if (!match) { return true; }
|
|
29
|
+
const [,leftBrac, left, right, rightBrac] = match
|
|
30
|
+
if (!leftBrac) { return true; }
|
|
31
|
+
const leftN = left === "-" ? -Infinity : Number(left);
|
|
32
|
+
const rightN = right === "+" ? +Infinity : Number(right);
|
|
33
|
+
if (value < leftN) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (leftBrac === "(" && value === leftN) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (value > rightN) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (rightBrac === ")" && value === rightN) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
} else if (type.startsWith("number")) {
|
|
48
|
+
|
|
49
|
+
if (typeof value !== "number") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const match = type.match(/^number(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/)
|
|
53
|
+
if (!match) { return true; }
|
|
54
|
+
const [, leftBrac, left, right, rightBrac] = match
|
|
55
|
+
if (!leftBrac) { return true; }
|
|
56
|
+
const leftN = left === "-" ? -Infinity : Number(left);
|
|
57
|
+
const rightN = right === "+" ? +Infinity : Number(right);
|
|
58
|
+
if (value < leftN) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (leftBrac === "(" && value === leftN) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (value > rightN) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (rightBrac === ")" && value === rightN) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
23
73
|
return typeof value === type
|
|
24
74
|
} else {
|
|
25
75
|
return value instanceof type
|