kipphi 2.1.3-beta.1 → 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/chart.ts +32 -1
- package/env.ts +3 -0
- package/event.ts +25 -1
- package/index.d.ts +20 -129
- package/index.js +165 -13
- package/judgeline.ts +0 -127
- package/note.ts +10 -5
- package/operation/easy.ts +21 -16
- package/operation/event.ts +9 -1
- package/package.json +1 -1
- package/rpeChartCompiler.ts +47 -9
- package/util.ts +50 -0
package/chart.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
NormalEasing,
|
|
11
11
|
rpeEasingArray,
|
|
12
12
|
SegmentedEasing,
|
|
13
|
+
TemplateEasing,
|
|
13
14
|
TemplateEasingLib,
|
|
14
15
|
} from "./easing";
|
|
15
16
|
|
|
@@ -149,6 +150,11 @@ export class Chart {
|
|
|
149
150
|
/** 难度等级显示绑定的判定线 */
|
|
150
151
|
levelAttach: JudgeLine | null = null;
|
|
151
152
|
|
|
153
|
+
/** 仅用于构造时检查
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
segmentedTemplates: Map<(SegmentedEasing & { easing: TemplateEasing}), [string, TimeT]> = new Map();
|
|
157
|
+
|
|
152
158
|
constructor() {}
|
|
153
159
|
|
|
154
160
|
/**
|
|
@@ -286,6 +292,9 @@ export class Chart {
|
|
|
286
292
|
for (let i = 0; i < len; i++) {
|
|
287
293
|
const easingData = templateEasings[i];
|
|
288
294
|
const sequence = chart.sequenceMap.get(easingData.content);
|
|
295
|
+
if (!sequence) {
|
|
296
|
+
continue; // 后面check的时候会错误处理
|
|
297
|
+
}
|
|
289
298
|
if (sequence.type !== EventType.easing) {
|
|
290
299
|
throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_EASING_ENS(easingData.name);
|
|
291
300
|
}
|
|
@@ -303,7 +312,10 @@ export class Chart {
|
|
|
303
312
|
}
|
|
304
313
|
}
|
|
305
314
|
|
|
306
|
-
chart.templateEasingLib.check()
|
|
315
|
+
chart.templateEasingLib.check();
|
|
316
|
+
|
|
317
|
+
chart.checkSegmentedTemplates();
|
|
318
|
+
|
|
307
319
|
for (const lineData of data.orphanLines) {
|
|
308
320
|
const line: JudgeLine = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
|
|
309
321
|
chart.orphanLines.push(line)
|
|
@@ -727,6 +739,25 @@ export class Chart {
|
|
|
727
739
|
|
|
728
740
|
}
|
|
729
741
|
*/
|
|
742
|
+
checkErrors() {
|
|
743
|
+
KPAError.flush();
|
|
744
|
+
for (const [_, seq] of this.sequenceMap) {
|
|
745
|
+
seq.checkErrors();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* 用于构造谱面时检查
|
|
750
|
+
*/
|
|
751
|
+
protected checkSegmentedTemplates() {
|
|
752
|
+
for (const [easing, [pos, time]] of this.segmentedTemplates) {
|
|
753
|
+
const inner = easing.easing;
|
|
754
|
+
if (inner.getValue(easing.left) === inner.getValue(easing.right)) {
|
|
755
|
+
err.EASING_DELTA_CANNOT_BE_ZERO(pos, time).warn();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
this.segmentedTemplates.clear();
|
|
759
|
+
// 查完就清除,不再需要
|
|
760
|
+
}
|
|
730
761
|
}
|
|
731
762
|
|
|
732
763
|
/**
|
package/env.ts
CHANGED
|
@@ -74,6 +74,7 @@ export enum ERROR_IDS {
|
|
|
74
74
|
NODES_NOT_BELONG_TO_SAME_SEQUENCE = EASING | INVALID_USAGE | 2,
|
|
75
75
|
NODES_HAS_ZERO_DELTA = EASING | INVALID_USAGE | 3,
|
|
76
76
|
TEMPLATE_EASING_CIRCULAR_REFERENCE = EASING | INVALID_USAGE | 4,
|
|
77
|
+
EASING_DELTA_CANNOT_BE_ZERO = EASING | INVALID_USAGE | 5,
|
|
77
78
|
|
|
78
79
|
CANNOT_DIVIDE_EXPRESSION_EVALUATOR = EVALUATOR | INVALID_USAGE | 0,
|
|
79
80
|
MISSING_MACRO_EVALUATOR_KEY = EVALUATOR | INVALID_DATA | 0,
|
|
@@ -184,6 +185,8 @@ export const ERRORS = {
|
|
|
184
185
|
`Hold should have a duration.`,
|
|
185
186
|
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) =>
|
|
186
187
|
`Template Easing '${temEasName}' has circular reference`,
|
|
188
|
+
EASING_DELTA_CANNOT_BE_ZERO: (seqName: string, time: TimeT) =>
|
|
189
|
+
`Easing delta cannot be zero. (at ${seqName}, ${toTimeString(time)}`,
|
|
187
190
|
} satisfies Record<keyof typeof ERROR_IDS, (...args: any[]) => string>
|
|
188
191
|
|
|
189
192
|
type EnumKeys<E extends Record<string, string | number>> = E[keyof E];
|
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;
|
|
@@ -640,6 +640,12 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
640
640
|
for (let index = 0; index < length; index++) {
|
|
641
641
|
const event = data[index];
|
|
642
642
|
const [start, end] = chart.createEventFromData<VT>(event, valueType, `${pos}.events[${index}]`);
|
|
643
|
+
|
|
644
|
+
// 收集被截的模板缓动
|
|
645
|
+
const evaluator = start.evaluator;
|
|
646
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof SegmentedEasing && evaluator.easing.easing instanceof TemplateEasing) {
|
|
647
|
+
chart.segmentedTemplates.set(evaluator.easing as any, [pos, start.time]);
|
|
648
|
+
}
|
|
643
649
|
// 从前面复制了,复用性减一
|
|
644
650
|
// KPA2没有更改RPE的按事件存储的机制。
|
|
645
651
|
if (TC.lt(event.startTime, lastEndTime)) { // event.startTime < lastEndTime
|
|
@@ -956,15 +962,33 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
956
962
|
}
|
|
957
963
|
|
|
958
964
|
let lastEnd: EventEndNode<VT> = endNode;
|
|
965
|
+
currentNode = endNode.next;
|
|
959
966
|
while (true) {
|
|
967
|
+
const evaluator = currentNode.evaluator;
|
|
968
|
+
if (this.type === EventType.easing && evaluator instanceof EasedEvaluator && evaluator.easing instanceof TemplateEasing) {
|
|
969
|
+
if (TemplateEasing.checkCircularReference(this as EventNodeSequence, evaluator.easing)) {
|
|
970
|
+
err.TEMPLATE_EASING_CIRCULAR_REFERENCE(this.id).warn();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof SegmentedEasing) {
|
|
974
|
+
const easing = evaluator.easing;
|
|
975
|
+
const inner = easing.easing;
|
|
976
|
+
if (inner.getValue(easing.left) === inner.getValue(easing.right)) {
|
|
977
|
+
err.EASING_DELTA_CANNOT_BE_ZERO(this.id, currentNode.time).warn();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
960
980
|
const endNode = currentNode.next;
|
|
961
981
|
if (endNode.type === NodeType.TAIL) {
|
|
962
982
|
break;
|
|
963
983
|
}
|
|
984
|
+
if (!TC.gt(endNode.time, currentNode.time)) {
|
|
985
|
+
err.EVENT_NODE_TIME_NOT_INCREMENTAL(`${this.id}, ${currentNode.time}`).warn();
|
|
986
|
+
}
|
|
964
987
|
if (TC.ne(lastEnd.time, currentNode.time)) {
|
|
965
988
|
err.EVENT_NODE_NOT_DENSE(`${this.id}, ${currentNode.time}`).warn();
|
|
966
989
|
}
|
|
967
990
|
currentNode = currentNode.next.next;
|
|
991
|
+
lastEnd = endNode;
|
|
968
992
|
}
|
|
969
993
|
}
|
|
970
994
|
hasReferenceTo(this: EventNodeSequence<number>, seq: EventNodeSequence<number>) {
|
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
|
*
|
|
@@ -1697,7 +1570,7 @@ declare module "easing" {
|
|
|
1697
1570
|
}
|
|
1698
1571
|
declare module "chart" {
|
|
1699
1572
|
import { TimeCalculator } from "bpm";
|
|
1700
|
-
import { Easing, TemplateEasingLib } from "easing";
|
|
1573
|
+
import { Easing, SegmentedEasing, TemplateEasing, TemplateEasingLib } from "easing";
|
|
1701
1574
|
import { EventNodeSequence, EventStartNode, EventEndNode, EventNode } from "event";
|
|
1702
1575
|
import { JudgeLine } from "judgeline";
|
|
1703
1576
|
import { NNNList, NNNode } from "note";
|
|
@@ -1765,6 +1638,12 @@ declare module "chart" {
|
|
|
1765
1638
|
nameAttach: JudgeLine | null;
|
|
1766
1639
|
/** 难度等级显示绑定的判定线 */
|
|
1767
1640
|
levelAttach: JudgeLine | null;
|
|
1641
|
+
/** 仅用于构造时检查
|
|
1642
|
+
* @internal
|
|
1643
|
+
*/
|
|
1644
|
+
segmentedTemplates: Map<(SegmentedEasing & {
|
|
1645
|
+
easing: TemplateEasing;
|
|
1646
|
+
}), [string, TimeT]>;
|
|
1768
1647
|
constructor();
|
|
1769
1648
|
/**
|
|
1770
1649
|
* 获取有效节拍数
|
|
@@ -1898,6 +1777,11 @@ declare module "chart" {
|
|
|
1898
1777
|
bindTimeMacro(node: EventStartNode<any>, macroData: MacroData, pos: string): any;
|
|
1899
1778
|
bindValueMacro(node: EventNode<any>, macroData: MacroData, pos: string): any;
|
|
1900
1779
|
linkMacro(node: EventNode<EventValueESType>, linkData: MacroLink, pos: string): any;
|
|
1780
|
+
checkErrors(): void;
|
|
1781
|
+
/**
|
|
1782
|
+
* 用于构造谱面时检查
|
|
1783
|
+
*/
|
|
1784
|
+
protected checkSegmentedTemplates(): void;
|
|
1901
1785
|
}
|
|
1902
1786
|
/**
|
|
1903
1787
|
* 表示一组判定线的容器
|
|
@@ -2262,6 +2146,7 @@ declare module "env" {
|
|
|
2262
2146
|
NODES_NOT_BELONG_TO_SAME_SEQUENCE = 2610,
|
|
2263
2147
|
NODES_HAS_ZERO_DELTA = 2611,
|
|
2264
2148
|
TEMPLATE_EASING_CIRCULAR_REFERENCE = 2612,
|
|
2149
|
+
EASING_DELTA_CANNOT_BE_ZERO = 2613,
|
|
2265
2150
|
CANNOT_DIVIDE_EXPRESSION_EVALUATOR = 2352,
|
|
2266
2151
|
MISSING_MACRO_EVALUATOR_KEY = 2336,
|
|
2267
2152
|
MACRO_EVALUATOR_NOT_FOUND = 2337,
|
|
@@ -2313,6 +2198,7 @@ declare module "env" {
|
|
|
2313
2198
|
EVENT_NODE_NOT_DENSE: (pos: string) => string;
|
|
2314
2199
|
HOLD_HAS_NO_DURATION: () => string;
|
|
2315
2200
|
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) => string;
|
|
2201
|
+
EASING_DELTA_CANNOT_BE_ZERO: (seqName: string, time: TimeT) => string;
|
|
2316
2202
|
};
|
|
2317
2203
|
interface ErrorMap extends Record<keyof typeof ERROR_IDS, Array<any>> {
|
|
2318
2204
|
EVENT_NODE_NOT_DENSE: [EventNode];
|
|
@@ -2379,6 +2265,7 @@ declare module "env" {
|
|
|
2379
2265
|
EVENT_NODE_NOT_DENSE: (pos: string) => KPAError<ERROR_IDS.EVENT_NODE_NOT_DENSE>;
|
|
2380
2266
|
HOLD_HAS_NO_DURATION: () => KPAError<ERROR_IDS.HOLD_HAS_NO_DURATION>;
|
|
2381
2267
|
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) => KPAError<ERROR_IDS.TEMPLATE_EASING_CIRCULAR_REFERENCE>;
|
|
2268
|
+
EASING_DELTA_CANNOT_BE_ZERO: (seqName: string, time: TimeT) => KPAError<ERROR_IDS.EASING_DELTA_CANNOT_BE_ZERO>;
|
|
2382
2269
|
};
|
|
2383
2270
|
freeze(): void;
|
|
2384
2271
|
};
|
|
@@ -2594,6 +2481,9 @@ declare module "operation/event" {
|
|
|
2594
2481
|
overlapping: boolean;
|
|
2595
2482
|
constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP?: boolean);
|
|
2596
2483
|
}
|
|
2484
|
+
export class EventNodePairAutoInsertOperation<VT extends EventValueESType> extends EventNodePairInsertOperation<VT> {
|
|
2485
|
+
constructor(node: EventStartNode<VT>, parentSeq: EventNodeSequence<VT>, updatesFP?: boolean);
|
|
2486
|
+
}
|
|
2597
2487
|
export class EventNodeValueChangeOperation<VT extends EventValueESType> extends Operation {
|
|
2598
2488
|
updatesEditor: boolean;
|
|
2599
2489
|
node: EventNode<VT>;
|
|
@@ -3068,7 +2958,7 @@ declare module "rpeChartCompiler" {
|
|
|
3068
2958
|
import type { Chart } from "chart";
|
|
3069
2959
|
import { type TimeT, type ChartDataRPE, type JudgeLineDataRPE, type EventDataRPELike, type NoteDataRPE, type EventValueESType } from "chartTypes";
|
|
3070
2960
|
import { type EasedEvaluatorOfType } from "evaluator";
|
|
3071
|
-
import { EventEndNode, EventNodeSequence, EventStartNode } from "event";
|
|
2961
|
+
import { EventEndNode, EventNodeSequence, EventStartNode, SpeedENS } from "event";
|
|
3072
2962
|
import type { JudgeLine } from "judgeline";
|
|
3073
2963
|
import type { NNList, HNList } from "note";
|
|
3074
2964
|
/**
|
|
@@ -3086,6 +2976,7 @@ declare module "rpeChartCompiler" {
|
|
|
3086
2976
|
evaluator: EasedEvaluatorOfType<VT>;
|
|
3087
2977
|
}, getValue: (node: EventStartNode<VT> | EventEndNode<VT>) => VT): EventDataRPELike<VT>;
|
|
3088
2978
|
dumpEventNodeSequence<VT extends EventValueESType>(sequence: EventNodeSequence<VT>): EventDataRPELike<VT>[];
|
|
2979
|
+
dumpSpeedENS(seq: SpeedENS): EventDataRPELike<number>[];
|
|
3089
2980
|
compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[];
|
|
3090
2981
|
/**
|
|
3091
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;
|
|
@@ -79,6 +134,7 @@ var ERROR_IDS;
|
|
|
79
134
|
ERROR_IDS2[ERROR_IDS2["NODES_NOT_BELONG_TO_SAME_SEQUENCE"] = EASING | INVALID_USAGE | 2] = "NODES_NOT_BELONG_TO_SAME_SEQUENCE";
|
|
80
135
|
ERROR_IDS2[ERROR_IDS2["NODES_HAS_ZERO_DELTA"] = EASING | INVALID_USAGE | 3] = "NODES_HAS_ZERO_DELTA";
|
|
81
136
|
ERROR_IDS2[ERROR_IDS2["TEMPLATE_EASING_CIRCULAR_REFERENCE"] = EASING | INVALID_USAGE | 4] = "TEMPLATE_EASING_CIRCULAR_REFERENCE";
|
|
137
|
+
ERROR_IDS2[ERROR_IDS2["EASING_DELTA_CANNOT_BE_ZERO"] = EASING | INVALID_USAGE | 5] = "EASING_DELTA_CANNOT_BE_ZERO";
|
|
82
138
|
ERROR_IDS2[ERROR_IDS2["CANNOT_DIVIDE_EXPRESSION_EVALUATOR"] = EVALUATOR | INVALID_USAGE | 0] = "CANNOT_DIVIDE_EXPRESSION_EVALUATOR";
|
|
83
139
|
ERROR_IDS2[ERROR_IDS2["MISSING_MACRO_EVALUATOR_KEY"] = EVALUATOR | INVALID_DATA | 0] = "MISSING_MACRO_EVALUATOR_KEY";
|
|
84
140
|
ERROR_IDS2[ERROR_IDS2["MACRO_EVALUATOR_NOT_FOUND"] = EVALUATOR | INVALID_DATA | 1] = "MACRO_EVALUATOR_NOT_FOUND";
|
|
@@ -129,7 +185,8 @@ var ERRORS = {
|
|
|
129
185
|
MACRO_NOT_PARAMETRIC: (macroId, pos) => `Macro '${macroId}' is not parametric. At ${pos}`,
|
|
130
186
|
EVENT_NODE_NOT_DENSE: (pos) => `EventNode is not dense. At ${pos}`,
|
|
131
187
|
HOLD_HAS_NO_DURATION: () => `Hold should have a duration.`,
|
|
132
|
-
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName) => `Template Easing '${temEasName}' has circular reference
|
|
188
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName) => `Template Easing '${temEasName}' has circular reference`,
|
|
189
|
+
EASING_DELTA_CANNOT_BE_ZERO: (seqName, time) => `Easing delta cannot be zero. (at ${seqName}, ${toTimeString(time)}`
|
|
133
190
|
};
|
|
134
191
|
|
|
135
192
|
class KPAError extends Error {
|
|
@@ -1643,7 +1700,7 @@ class EventStartNode extends EventNode {
|
|
|
1643
1700
|
return super.clone(offset);
|
|
1644
1701
|
}
|
|
1645
1702
|
clonePair(offset) {
|
|
1646
|
-
const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) :
|
|
1703
|
+
const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) : null;
|
|
1647
1704
|
const startNode = this.clone(offset);
|
|
1648
1705
|
EventNode.connect(endNode, startNode);
|
|
1649
1706
|
return startNode;
|
|
@@ -1771,6 +1828,10 @@ class EventNodeSequence {
|
|
|
1771
1828
|
for (let index = 0;index < length; index++) {
|
|
1772
1829
|
const event = data[index];
|
|
1773
1830
|
const [start, end] = chart.createEventFromData(event, valueType, `${pos}.events[${index}]`);
|
|
1831
|
+
const evaluator = start.evaluator;
|
|
1832
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof SegmentedEasing && evaluator.easing.easing instanceof TemplateEasing) {
|
|
1833
|
+
chart.segmentedTemplates.set(evaluator.easing, [pos, start.time]);
|
|
1834
|
+
}
|
|
1774
1835
|
if (TC2.lt(event.startTime, lastEndTime)) {
|
|
1775
1836
|
err.EVENT_NODE_TIME_NOT_INCREMENTAL(`${pos}.events[${index}] and the previous`).warn();
|
|
1776
1837
|
}
|
|
@@ -2011,15 +2072,33 @@ class EventNodeSequence {
|
|
|
2011
2072
|
return;
|
|
2012
2073
|
}
|
|
2013
2074
|
let lastEnd = endNode;
|
|
2075
|
+
currentNode = endNode.next;
|
|
2014
2076
|
while (true) {
|
|
2077
|
+
const evaluator = currentNode.evaluator;
|
|
2078
|
+
if (this.type === 5 /* easing */ && evaluator instanceof EasedEvaluator && evaluator.easing instanceof TemplateEasing) {
|
|
2079
|
+
if (TemplateEasing.checkCircularReference(this, evaluator.easing)) {
|
|
2080
|
+
err.TEMPLATE_EASING_CIRCULAR_REFERENCE(this.id).warn();
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof SegmentedEasing) {
|
|
2084
|
+
const easing = evaluator.easing;
|
|
2085
|
+
const inner = easing.easing;
|
|
2086
|
+
if (inner.getValue(easing.left) === inner.getValue(easing.right)) {
|
|
2087
|
+
err.EASING_DELTA_CANNOT_BE_ZERO(this.id, currentNode.time).warn();
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2015
2090
|
const endNode2 = currentNode.next;
|
|
2016
2091
|
if (endNode2.type === 1 /* TAIL */) {
|
|
2017
2092
|
break;
|
|
2018
2093
|
}
|
|
2094
|
+
if (!TC2.gt(endNode2.time, currentNode.time)) {
|
|
2095
|
+
err.EVENT_NODE_TIME_NOT_INCREMENTAL(`${this.id}, ${currentNode.time}`).warn();
|
|
2096
|
+
}
|
|
2019
2097
|
if (TC2.ne(lastEnd.time, currentNode.time)) {
|
|
2020
2098
|
err.EVENT_NODE_NOT_DENSE(`${this.id}, ${currentNode.time}`).warn();
|
|
2021
2099
|
}
|
|
2022
2100
|
currentNode = currentNode.next.next;
|
|
2101
|
+
lastEnd = endNode2;
|
|
2023
2102
|
}
|
|
2024
2103
|
}
|
|
2025
2104
|
hasReferenceTo(seq) {
|
|
@@ -2137,8 +2216,8 @@ class Note {
|
|
|
2137
2216
|
visibleTime,
|
|
2138
2217
|
yOffset: this.yOffset / this.speed,
|
|
2139
2218
|
speed: this.speed,
|
|
2140
|
-
tint: this.tint !== undefined ? hex2rgb(this.tint) : undefined,
|
|
2141
|
-
tintHitEffects: this.
|
|
2219
|
+
tint: this.tint !== undefined && this.tint !== 16777215 ? hex2rgb(this.tint) : undefined,
|
|
2220
|
+
tintHitEffects: this.tintHitEffects !== undefined && this.tintHitEffects !== 16777215 ? hex2rgb(this.tintHitEffects) : undefined,
|
|
2142
2221
|
judgeArea: this.judgeSize
|
|
2143
2222
|
};
|
|
2144
2223
|
}
|
|
@@ -2156,8 +2235,8 @@ class Note {
|
|
|
2156
2235
|
yOffset: this.yOffset / this.speed,
|
|
2157
2236
|
absoluteYOffset: this.yOffset,
|
|
2158
2237
|
speed: this.speed,
|
|
2159
|
-
tint: this.tint !== undefined ? hex2rgb(this.tint) : undefined,
|
|
2160
|
-
tintHitEffects: this.
|
|
2238
|
+
tint: this.tint !== undefined && this.tint !== 16777215 ? hex2rgb(this.tint) : undefined,
|
|
2239
|
+
tintHitEffects: this.tintHitEffects !== undefined && this.tintHitEffects !== 16777215 ? hex2rgb(this.tintHitEffects) : undefined,
|
|
2161
2240
|
judgeSize: this.judgeSize && this.judgeSize !== 1 ? this.judgeSize : undefined
|
|
2162
2241
|
};
|
|
2163
2242
|
}
|
|
@@ -2256,7 +2335,12 @@ class NoteNode extends NoteNodeLike {
|
|
|
2256
2335
|
}
|
|
2257
2336
|
}
|
|
2258
2337
|
remove(note) {
|
|
2259
|
-
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);
|
|
2260
2344
|
note.parentNode = null;
|
|
2261
2345
|
}
|
|
2262
2346
|
static disconnect(note1, note2) {
|
|
@@ -3327,6 +3411,7 @@ class Chart {
|
|
|
3327
3411
|
scoreAttach = null;
|
|
3328
3412
|
nameAttach = null;
|
|
3329
3413
|
levelAttach = null;
|
|
3414
|
+
segmentedTemplates = new Map;
|
|
3330
3415
|
constructor() {}
|
|
3331
3416
|
getEffectiveBeats() {
|
|
3332
3417
|
const effectiveBeats = this.timeCalculator.secondsToBeats(this.duration);
|
|
@@ -3418,6 +3503,9 @@ class Chart {
|
|
|
3418
3503
|
for (let i = 0;i < len; i++) {
|
|
3419
3504
|
const easingData = templateEasings[i];
|
|
3420
3505
|
const sequence = chart.sequenceMap.get(easingData.content);
|
|
3506
|
+
if (!sequence) {
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3421
3509
|
if (sequence.type !== 5 /* easing */) {
|
|
3422
3510
|
throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_EASING_ENS(easingData.name);
|
|
3423
3511
|
}
|
|
@@ -3434,6 +3522,7 @@ class Chart {
|
|
|
3434
3522
|
}
|
|
3435
3523
|
}
|
|
3436
3524
|
chart.templateEasingLib.check();
|
|
3525
|
+
chart.checkSegmentedTemplates();
|
|
3437
3526
|
for (const lineData of data.orphanLines) {
|
|
3438
3527
|
const line = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator);
|
|
3439
3528
|
chart.orphanLines.push(line);
|
|
@@ -3729,6 +3818,21 @@ class Chart {
|
|
|
3729
3818
|
}
|
|
3730
3819
|
return null;
|
|
3731
3820
|
}
|
|
3821
|
+
checkErrors() {
|
|
3822
|
+
KPAError.flush();
|
|
3823
|
+
for (const [_, seq] of this.sequenceMap) {
|
|
3824
|
+
seq.checkErrors();
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
checkSegmentedTemplates() {
|
|
3828
|
+
for (const [easing, [pos, time]] of this.segmentedTemplates) {
|
|
3829
|
+
const inner = easing.easing;
|
|
3830
|
+
if (inner.getValue(easing.left) === inner.getValue(easing.right)) {
|
|
3831
|
+
err.EASING_DELTA_CANNOT_BE_ZERO(pos, time).warn();
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
this.segmentedTemplates.clear();
|
|
3835
|
+
}
|
|
3732
3836
|
}
|
|
3733
3837
|
|
|
3734
3838
|
class JudgeLineGroup {
|
|
@@ -3813,6 +3917,7 @@ __export(exports_operation, {
|
|
|
3813
3917
|
EventNodePairRemoveOperation: () => EventNodePairRemoveOperation,
|
|
3814
3918
|
EventNodePairInsertOrOverwriteOperation: () => EventNodePairInsertOrOverwriteOperation,
|
|
3815
3919
|
EventNodePairInsertOperation: () => EventNodePairInsertOperation,
|
|
3920
|
+
EventNodePairAutoInsertOperation: () => EventNodePairAutoInsertOperation,
|
|
3816
3921
|
EventNodeMacroTimeReevaluateOperation: () => EventNodeMacroTimeReevaluateOperation,
|
|
3817
3922
|
EventNodeEvaluatorChangeOperation: () => EventNodeEvaluatorChangeOperation,
|
|
3818
3923
|
EventInterpolationOperation: () => EventInterpolationOperation,
|
|
@@ -4148,6 +4253,12 @@ class EventNodePairInsertOrOverwriteOperation extends UnionOperation {
|
|
|
4148
4253
|
}
|
|
4149
4254
|
}
|
|
4150
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
|
+
|
|
4151
4262
|
class EventNodeValueChangeOperation extends Operation {
|
|
4152
4263
|
updatesEditor = true;
|
|
4153
4264
|
node;
|
|
@@ -4360,6 +4471,9 @@ class EncapsuleOperation extends ComplexOperation {
|
|
|
4360
4471
|
const sequence = easing.eventNodeSequence;
|
|
4361
4472
|
sequence.effectiveBeats = TC2.toBeats(nodeArray[nodeArray.length - 1].time);
|
|
4362
4473
|
new MultiNodeAddOperation(nodeArray, sequence).do();
|
|
4474
|
+
const first = sequence.head.next;
|
|
4475
|
+
first.evaluator = nodeArray[0].evaluator;
|
|
4476
|
+
first.value = nodeArray[0].value;
|
|
4363
4477
|
return new EncapsuleOperation(oldArray, easing);
|
|
4364
4478
|
}
|
|
4365
4479
|
}
|
|
@@ -5299,8 +5413,20 @@ class RPEChartCompiler {
|
|
|
5299
5413
|
compileChart() {
|
|
5300
5414
|
const chart2 = this.chart;
|
|
5301
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
|
+
};
|
|
5302
5428
|
const filter = this.deletesEmptyLines ? (line2) => {
|
|
5303
|
-
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) => {
|
|
5304
5430
|
const seq = line2.eventLayers[0][evType];
|
|
5305
5431
|
let node = seq.head.next;
|
|
5306
5432
|
for (let i = 0;i < 2; i++) {
|
|
@@ -5383,7 +5509,7 @@ class RPEChartCompiler {
|
|
|
5383
5509
|
moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : undefined,
|
|
5384
5510
|
rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : undefined,
|
|
5385
5511
|
alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : undefined,
|
|
5386
|
-
speedEvents: index === 0 ? this.
|
|
5512
|
+
speedEvents: index === 0 ? this.dumpSpeedENS(judgeLine.speedSequence) : undefined
|
|
5387
5513
|
})),
|
|
5388
5514
|
extended: {
|
|
5389
5515
|
scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : undefined,
|
|
@@ -5481,6 +5607,32 @@ class RPEChartCompiler {
|
|
|
5481
5607
|
nodes.push(this.compileEasedEvent(newStart, getValue));
|
|
5482
5608
|
return nodes;
|
|
5483
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
|
+
}
|
|
5484
5636
|
compileNNLists(nnLists, hnLists) {
|
|
5485
5637
|
const noteLists = nnLists.map((list) => this.nnListToArray(list));
|
|
5486
5638
|
const holdLists = hnLists.map((list) => this.nnListToArray(list));
|
|
@@ -5564,8 +5716,8 @@ class RPEChartCompiler {
|
|
|
5564
5716
|
srcStart = srcSeq.head.next.value;
|
|
5565
5717
|
srcEnd = srcSeq.tail.previous.value;
|
|
5566
5718
|
leftDividedNodeSrc = srcSeq.head.next;
|
|
5567
|
-
rightDividedNodeSrc = srcSeq.tail.previous;
|
|
5568
|
-
toStopAt =
|
|
5719
|
+
rightDividedNodeSrc = srcSeq.tail.previous.previous.previous;
|
|
5720
|
+
toStopAt = srcSeq.tail.previous;
|
|
5569
5721
|
srcStartTime = srcSeq.head.next.time;
|
|
5570
5722
|
srcTimeDelta = TC2.sub(srcSeq.tail.previous.time, srcStartTime);
|
|
5571
5723
|
}
|
|
@@ -5612,13 +5764,13 @@ class RPEChartCompiler {
|
|
|
5612
5764
|
if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
5613
5765
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
5614
5766
|
} else {
|
|
5615
|
-
|
|
5767
|
+
prev.evaluator = evaluator.deriveWithEasing(new SegmentedEasing(rightDividedNodeSrc.evaluator.easing, 0, newRight));
|
|
5616
5768
|
}
|
|
5617
5769
|
} else {
|
|
5618
5770
|
if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
5619
5771
|
throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
|
|
5620
5772
|
} else {
|
|
5621
|
-
|
|
5773
|
+
prev.evaluator = evaluator.deriveWithEasing(rightDividedNodeSrc.evaluator.easing);
|
|
5622
5774
|
}
|
|
5623
5775
|
}
|
|
5624
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
|
@@ -168,8 +168,8 @@ export class Note {
|
|
|
168
168
|
visibleTime: visibleTime,
|
|
169
169
|
yOffset: this.yOffset / this.speed,
|
|
170
170
|
speed: this.speed,
|
|
171
|
-
tint: this.tint !== undefined ? hex2rgb(this.tint) : undefined,
|
|
172
|
-
tintHitEffects: this.
|
|
171
|
+
tint: this.tint !== undefined && this.tint !== 0xffffff ? hex2rgb(this.tint) : undefined,
|
|
172
|
+
tintHitEffects: this.tintHitEffects !== undefined && this.tintHitEffects !== 0xffffff ? hex2rgb(this.tintHitEffects) : undefined,
|
|
173
173
|
judgeArea: this.judgeSize
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -189,8 +189,8 @@ export class Note {
|
|
|
189
189
|
/** 但是有历史包袱,所以加字段 */
|
|
190
190
|
absoluteYOffset: this.yOffset,
|
|
191
191
|
speed: this.speed,
|
|
192
|
-
tint: this.tint !== undefined ? hex2rgb(this.tint) : undefined,
|
|
193
|
-
tintHitEffects: this.
|
|
192
|
+
tint: this.tint !== undefined && this.tint !== 0xffffff ? hex2rgb(this.tint) : undefined,
|
|
193
|
+
tintHitEffects: this.tintHitEffects !== undefined && this.tintHitEffects !== 0xffffff ? hex2rgb(this.tintHitEffects) : undefined,
|
|
194
194
|
judgeSize: this.judgeSize && this.judgeSize !== 1.0 ? this.judgeSize : undefined,
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -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/easy.ts
CHANGED
|
@@ -35,20 +35,21 @@ class Operable {
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class OperableNote extends Operable {
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
38
|
+
// @ts-expect-error 后面会赋值
|
|
39
|
+
private _fields: {
|
|
40
|
+
[x in NotePropName]: Note[x]
|
|
41
|
+
} = {};
|
|
41
42
|
constructor(public target: Note, buffer: Operation[]) {
|
|
42
43
|
super(buffer);
|
|
43
44
|
if (target.parentNode === null) {
|
|
44
45
|
throw new Error("Note has no parent node")
|
|
45
46
|
}
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
47
|
+
this._fields.startTime = target.startTime;
|
|
48
|
+
this._fields.endTime = target.endTime;
|
|
49
|
+
this._fields.type = target.type;
|
|
49
50
|
}
|
|
50
51
|
get startTime() {
|
|
51
|
-
return this.
|
|
52
|
+
return this._fields.startTime;
|
|
52
53
|
}
|
|
53
54
|
set startTime(userTime: Time) {
|
|
54
55
|
const timeT = userTimeToTuple(userTime);
|
|
@@ -60,28 +61,28 @@ class OperableNote extends Operable {
|
|
|
60
61
|
if (beats > nnList.effectiveBeats) {
|
|
61
62
|
throw new Error("")
|
|
62
63
|
}
|
|
63
|
-
this.
|
|
64
|
+
this._fields.startTime = timeT;
|
|
64
65
|
const node = nnList.getNodeOf(timeT);
|
|
65
66
|
this.buffer.push(NoteTimeChangeOperation.lazy(this.target, node));
|
|
66
67
|
}
|
|
67
|
-
get endTime() { return this.
|
|
68
|
+
get endTime() { return this._fields.endTime; }
|
|
68
69
|
set endTime(userTime: Time) {
|
|
69
|
-
if (this.
|
|
70
|
+
if (this._fields.type !== NoteType.hold) {
|
|
70
71
|
throw new Error("Note is not a hold note");
|
|
71
72
|
}
|
|
72
73
|
const timeT = userTimeToTuple(userTime);
|
|
73
|
-
if (!TC.gt(timeT, this.
|
|
74
|
+
if (!TC.gt(timeT, this._fields.startTime)) {
|
|
74
75
|
throw new Error("");
|
|
75
76
|
}
|
|
76
|
-
this.
|
|
77
|
+
this._fields.endTime = timeT;
|
|
77
78
|
this.buffer.push(HoldEndTimeChangeOperation.lazy(this.target, timeT));
|
|
78
79
|
}
|
|
79
|
-
get type() { return this.
|
|
80
|
+
get type() { return this._fields.type; }
|
|
80
81
|
set type(type: NoteType) {
|
|
81
|
-
if (this.
|
|
82
|
+
if (this._fields.type === type) {
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
|
-
this.
|
|
85
|
+
this._fields.type = type;
|
|
85
86
|
this.buffer.push(NoteTypeChangeOperation.lazy(this.target, type));
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -109,8 +110,12 @@ interface OperableNote {
|
|
|
109
110
|
|
|
110
111
|
for (const propName of ["above", "alpha", "positionX", "judgeSize", "isFake", "size", "tint", "tintHitEffects", "visibleBeats"] satisfies NotePropName[]) {
|
|
111
112
|
Object.defineProperty(OperableNote.prototype, propName, {
|
|
112
|
-
get() { return this.target[propName]},
|
|
113
|
+
get() { return this._fields[propName] ?? this.target[propName]},
|
|
113
114
|
set(value) {
|
|
115
|
+
if (this._fields[propName] === value) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this._fields[propName] = value;
|
|
114
119
|
this.buffer.push(NotePropChangeOperation.lazy(this.target, propName, value));
|
|
115
120
|
}
|
|
116
121
|
});
|
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
|