kipphi 2.1.2-rc.1 → 2.1.3-beta.1
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/bpm.ts +26 -6
- package/chart.ts +10 -1
- package/chartTypes.ts +26 -5
- package/easing.ts +31 -2
- package/env.ts +6 -0
- package/evaluator.ts +39 -14
- package/event.ts +26 -9
- package/index.d.ts +68 -12
- package/index.js +218 -78
- package/judgeline.ts +100 -43
- package/note.ts +16 -18
- package/operation/basic.ts +10 -0
- package/operation/easy.ts +131 -0
- package/operation/event.ts +17 -2
- package/operation/note.ts +5 -0
- package/package.json +1 -1
- package/rpeChartCompiler.ts +39 -10
- package/tsconfig.json +2 -2
- package/util.ts +4 -2
- package/version.ts +1 -1
- package/line.ts +0 -246
package/bpm.ts
CHANGED
|
@@ -110,7 +110,12 @@ type BNOrHead = BPMNode | BPMNodeLike<NodeType.HEAD>;
|
|
|
110
110
|
/**
|
|
111
111
|
* BPM序列类,管理BPM变化序列
|
|
112
112
|
* 拥有与事件类似的逻辑,每对节点之间代表一个BPM相同的片段
|
|
113
|
-
* 片段之间BPM
|
|
113
|
+
* 片段之间BPM可以发生改变。
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const bpmList = [{ startTime: [0, 0, 0], bpm: 120 }];
|
|
117
|
+
* const bpmSequence = new BPMSequence(bpmList, 180);
|
|
118
|
+
* // JumpArray 在构造器中自动初始化。
|
|
114
119
|
*/
|
|
115
120
|
export class BPMSequence extends EventNodeSequence {
|
|
116
121
|
/** 头部节点 */
|
|
@@ -141,7 +146,7 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
141
146
|
BPMStartNode.connect(curPos, startNode);
|
|
142
147
|
curPos = endNode;
|
|
143
148
|
}
|
|
144
|
-
const last = new BPMStartNode(next.startTime, next.bpm)
|
|
149
|
+
const last = new BPMStartNode(next.startTime, next.bpm);
|
|
145
150
|
BPMStartNode.connect(curPos, last);
|
|
146
151
|
BPMStartNode.connect(last, this.tail);
|
|
147
152
|
this.initJump();
|
|
@@ -160,7 +165,9 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
/**
|
|
163
|
-
*
|
|
168
|
+
* 更新秒跳转数组。
|
|
169
|
+
*
|
|
170
|
+
* 缓存每个节点的秒数发生在这里。
|
|
164
171
|
*/
|
|
165
172
|
updateSecondJump(): void {
|
|
166
173
|
let integral = 0;
|
|
@@ -179,7 +186,7 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
179
186
|
node = endNode.next;
|
|
180
187
|
}
|
|
181
188
|
node.cachedStartIntegral = integral;
|
|
182
|
-
if (this.effectiveBeats
|
|
189
|
+
if (this.effectiveBeats === 0) {
|
|
183
190
|
return;
|
|
184
191
|
}
|
|
185
192
|
const originalListLength = this.listLength;
|
|
@@ -188,7 +195,7 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
188
195
|
this.tail,
|
|
189
196
|
originalListLength,
|
|
190
197
|
this.duration,
|
|
191
|
-
(node:
|
|
198
|
+
(node: BPMStartNode | BPMNodeLike<NodeType.TAIL> | BPMNodeLike<NodeType.HEAD>) => {
|
|
192
199
|
if (node.type === NodeType.TAIL) {
|
|
193
200
|
return [null, null];
|
|
194
201
|
}
|
|
@@ -259,8 +266,9 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
259
266
|
|
|
260
267
|
/**
|
|
261
268
|
* 根据拍数获取节点
|
|
269
|
+
*
|
|
262
270
|
* @param beats 拍数
|
|
263
|
-
* @param usePrev
|
|
271
|
+
* @param usePrev 是否使用前一个节点。假设有两个BPM片段,0-2拍,2-无穷,`getNodeAt(2, true)` 会返回第一个片段的开始节点
|
|
264
272
|
* @returns 对应的BPM起始节点
|
|
265
273
|
*/
|
|
266
274
|
getNodeAt(beats: number, usePrev?: boolean): BPMStartNode {
|
|
@@ -270,6 +278,15 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
270
278
|
|
|
271
279
|
/**
|
|
272
280
|
* 时间计算器类,用于处理拍数与秒数之间的转换
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* const bpmList = = [
|
|
284
|
+
* { bpm: 120, startTime: [0, 0, 1] }
|
|
285
|
+
* ];
|
|
286
|
+
* const tc = new TimeCalculator();
|
|
287
|
+
* tc.bpmList = bpmList;
|
|
288
|
+
* tc.duration = 131; // 这两者都是必需的。
|
|
289
|
+
* tc.initSequence();
|
|
273
290
|
*/
|
|
274
291
|
export class TimeCalculator {
|
|
275
292
|
/** BPM片段数据列表 */
|
|
@@ -289,6 +306,9 @@ export class TimeCalculator {
|
|
|
289
306
|
* 初始化BPM序列
|
|
290
307
|
*/
|
|
291
308
|
initSequence() {
|
|
309
|
+
if (!this.bpmList || !this.duration) {
|
|
310
|
+
throw new Error("TimeCalculator: bpmList and duration must be set before initSequence");
|
|
311
|
+
}
|
|
292
312
|
const bpmList = this.bpmList;
|
|
293
313
|
// @ts-expect-error 不在构造器中初始化的只读属性
|
|
294
314
|
this.bpmSequence = new BPMSequence(bpmList, this.duration);
|
package/chart.ts
CHANGED
|
@@ -169,7 +169,7 @@ export class Chart {
|
|
|
169
169
|
*/
|
|
170
170
|
static fromRPEJSON(data: ChartDataRPE, duration: number) {
|
|
171
171
|
const chart = new Chart();
|
|
172
|
-
chart.judgeLineGroups = data.judgeLineGroup.map(group => new JudgeLineGroup(group));
|
|
172
|
+
chart.judgeLineGroups = (data.judgeLineGroup || ["Default"]).map(group => new JudgeLineGroup(group));
|
|
173
173
|
chart.name = data.META.name;
|
|
174
174
|
chart.level = data.META.level;
|
|
175
175
|
chart.offset = data.META.offset;
|
|
@@ -294,6 +294,15 @@ export class Chart {
|
|
|
294
294
|
}
|
|
295
295
|
chart.templateEasingLib.implement(easingData.name, sequence as EventNodeSequence<number>);
|
|
296
296
|
}
|
|
297
|
+
for (let i = 0; i < len; i++) {
|
|
298
|
+
const easingData = templateEasings[i];
|
|
299
|
+
const sequence = chart.sequenceMap.get(easingData.content) as EventNodeSequence;
|
|
300
|
+
// 遍历该序列检查循环依赖
|
|
301
|
+
if (sequence.hasReferenceTo(sequence)) {
|
|
302
|
+
throw err.TEMPLATE_EASING_CIRCULAR_REFERENCE(easingData.name);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
297
306
|
chart.templateEasingLib.check()
|
|
298
307
|
for (const lineData of data.orphanLines) {
|
|
299
308
|
const line: JudgeLine = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
|
package/chartTypes.ts
CHANGED
|
@@ -84,11 +84,27 @@ export interface NoteDataRPE {
|
|
|
84
84
|
* Sets the Z index for the hit effects of the note. Defaults to 7.
|
|
85
85
|
*/
|
|
86
86
|
zIndexHitEffects?: number;
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Sets the tint for the hit effects of the note. Defaults to null.
|
|
89
|
+
*
|
|
90
|
+
* @alias color
|
|
91
|
+
*/
|
|
88
92
|
tint?: RGB;
|
|
93
|
+
/**
|
|
94
|
+
* @see {@linkcode tint}
|
|
95
|
+
*/
|
|
96
|
+
color?: RGB;
|
|
89
97
|
tintHitEffects?: RGB;
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Determines the width of the judgment area of the note. Defaults to size.
|
|
102
|
+
* @alias judgeSize
|
|
103
|
+
*/
|
|
104
|
+
judgeArea?: number;
|
|
105
|
+
/**
|
|
106
|
+
* @see {@linkcode judgeArea}
|
|
107
|
+
*/
|
|
92
108
|
judgeSize?: number;
|
|
93
109
|
}
|
|
94
110
|
|
|
@@ -113,7 +129,11 @@ export interface NoteDataKPA {
|
|
|
113
129
|
type: NoteType;
|
|
114
130
|
/** 音符可视时间(打击前多少秒开始显现,默认99999.0) */
|
|
115
131
|
visibleTime?: number;
|
|
116
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* y值偏移,使音符被打击时的位置偏离判定线
|
|
134
|
+
*
|
|
135
|
+
* @deprecated 使用{@linkcode absoluteYOffset}代替。
|
|
136
|
+
*/
|
|
117
137
|
yOffset: number;
|
|
118
138
|
|
|
119
139
|
// 下面是PhiZone Player扩展的内容
|
|
@@ -123,11 +143,12 @@ export interface NoteDataKPA {
|
|
|
123
143
|
* Sets the Z index for the hit effects of the note. Defaults to 7.
|
|
124
144
|
*/
|
|
125
145
|
zIndexHitEffects?: number;
|
|
126
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Sets the tint for the hit effects of the note. Defaults to null.
|
|
148
|
+
*/
|
|
127
149
|
tint?: RGB;
|
|
128
150
|
tintHitEffects?: RGB;
|
|
129
151
|
|
|
130
|
-
/** Determines the width of the judgment area of the note. Defaults to size. */
|
|
131
152
|
judgeSize?: number;
|
|
132
153
|
visibleBeats?: number;
|
|
133
154
|
absoluteYOffset: number;
|
package/easing.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type TemplateEasingBodyData, type EasingDataKPA2, EasingType, EventType, type SegmentedEasingData, type NormalEasingData, type BezierEasingData, type TemplateEasingData, WrapperEasingData, WrapperEasingBodyData } from "./chartTypes";
|
|
2
2
|
import { type EventNodeSequence } from "./event";
|
|
3
|
-
import { type TupleCoord } from "./util";
|
|
3
|
+
import { NodeType, type TupleCoord } from "./util";
|
|
4
4
|
import Environment, { err } from "./env";
|
|
5
|
-
import { type ExpressionEvaluator } from "./evaluator";
|
|
5
|
+
import { type EasedEvaluator, type ExpressionEvaluator } from "./evaluator";
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/// #declaration:global
|
|
@@ -318,6 +318,9 @@ export class TemplateEasing extends Easing {
|
|
|
318
318
|
this.name = name;
|
|
319
319
|
}
|
|
320
320
|
getValue(t: number) {
|
|
321
|
+
if (t === 1) {
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
321
324
|
const seq = this.eventNodeSequence;
|
|
322
325
|
const delta = this.valueDelta;
|
|
323
326
|
if (delta === 0) {
|
|
@@ -339,6 +342,32 @@ export class TemplateEasing extends Easing {
|
|
|
339
342
|
get headValue(): number {
|
|
340
343
|
return this.eventNodeSequence.head.next.value;
|
|
341
344
|
}
|
|
345
|
+
|
|
346
|
+
segmentedValueGetter(easingLeft: number, easingRight: number) {
|
|
347
|
+
// 由于模板缓动是可变的,所以不能在分段缓动构造时预先计算几个变化量
|
|
348
|
+
return (t: number) => {
|
|
349
|
+
|
|
350
|
+
const leftValue = this.getValue(easingLeft);
|
|
351
|
+
const rightValue = this.getValue(easingRight);
|
|
352
|
+
const timeDelta = easingRight - easingLeft;
|
|
353
|
+
const delta = rightValue - leftValue;
|
|
354
|
+
if (delta === 0) {
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
return (this.getValue(easingLeft + timeDelta * t) - leftValue) / delta
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
static checkCircularReference(seq: EventNodeSequence, template: TemplateEasing) {
|
|
362
|
+
const seq2 = template.eventNodeSequence;
|
|
363
|
+
if (seq === seq2) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
if (seq2.hasReferenceTo(seq)) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
342
371
|
}
|
|
343
372
|
|
|
344
373
|
export class WrapperEasing extends Easing {
|
package/env.ts
CHANGED
|
@@ -73,6 +73,7 @@ export enum ERROR_IDS {
|
|
|
73
73
|
NODES_NOT_CONTINUOUS = EASING | INVALID_USAGE | 1,
|
|
74
74
|
NODES_NOT_BELONG_TO_SAME_SEQUENCE = EASING | INVALID_USAGE | 2,
|
|
75
75
|
NODES_HAS_ZERO_DELTA = EASING | INVALID_USAGE | 3,
|
|
76
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE = EASING | INVALID_USAGE | 4,
|
|
76
77
|
|
|
77
78
|
CANNOT_DIVIDE_EXPRESSION_EVALUATOR = EVALUATOR | INVALID_USAGE | 0,
|
|
78
79
|
MISSING_MACRO_EVALUATOR_KEY = EVALUATOR | INVALID_DATA | 0,
|
|
@@ -81,6 +82,7 @@ export enum ERROR_IDS {
|
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
INVALID_NOTE_PROP_TYPE = NOTE | INVALID_TYPE | 0,
|
|
85
|
+
HOLD_HAS_NO_DURATION = NOTE | INVALID_DATA | 0,
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
INVALID_TIME_TUPLE = TC | INVALID_DATA | 0,
|
|
@@ -178,6 +180,10 @@ export const ERRORS = {
|
|
|
178
180
|
`Macro '${macroId}' is not parametric. At ${pos}`,
|
|
179
181
|
EVENT_NODE_NOT_DENSE: (pos: string) =>
|
|
180
182
|
`EventNode is not dense. At ${pos}`,
|
|
183
|
+
HOLD_HAS_NO_DURATION: () =>
|
|
184
|
+
`Hold should have a duration.`,
|
|
185
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) =>
|
|
186
|
+
`Template Easing '${temEasName}' has circular reference`,
|
|
181
187
|
} satisfies Record<keyof typeof ERROR_IDS, (...args: any[]) => string>
|
|
182
188
|
|
|
183
189
|
type EnumKeys<E extends Record<string, string | number>> = E[keyof E];
|
package/evaluator.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { EasingType, EvaluatorType, EventValueType, EventValueTypeOfType, Interp
|
|
|
13
13
|
import type { JudgeLine } from "./judgeline";
|
|
14
14
|
import { Chart } from "./chart";
|
|
15
15
|
import { EVENT_MACROS } from "./macro";
|
|
16
|
+
import { type TimeCalculator } from "./bpm";
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
/// #declaration:global
|
|
@@ -28,6 +29,7 @@ import { EVENT_MACROS } from "./macro";
|
|
|
28
29
|
*/
|
|
29
30
|
export abstract class Evaluator<T extends EventValueESType> {
|
|
30
31
|
abstract eval(event: NonLastStartNode<T>, beats: number): T;
|
|
32
|
+
abstract eval(event: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
31
33
|
abstract dumpFor(node: EventStartNode<T>): EvaluatorDataKPA2<T>;
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -38,17 +40,28 @@ export abstract class EasedEvaluator<T extends EventValueESType> extends Evaluat
|
|
|
38
40
|
super();
|
|
39
41
|
this.easing = easing;
|
|
40
42
|
}
|
|
41
|
-
override eval(startNode: NonLastStartNode<T>, beats: number): T
|
|
43
|
+
override eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
44
|
+
override eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
45
|
+
override eval(startNode: NonLastStartNode<T>, beatsOrSeconds: number, timeCalculator?: TimeCalculator): T {
|
|
42
46
|
const next = startNode.next;
|
|
43
|
-
const timeDelta = TC.getDelta(next.time, startNode.time)
|
|
44
|
-
const current = beats - TC.toBeats(startNode.time)
|
|
45
47
|
const nextValue = startNode.next.value;
|
|
46
48
|
const value = startNode.value;
|
|
47
49
|
if (nextValue === value) {
|
|
48
50
|
return value;
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
if (timeCalculator) {
|
|
53
|
+
// 注意这个和下面那个API设计得不一样
|
|
54
|
+
const startSecs = timeCalculator.toSeconds(TC.toBeats(startNode.time));
|
|
55
|
+
const endSecs = timeCalculator.toSeconds(TC.toBeats(next.time));
|
|
56
|
+
const current = beatsOrSeconds - startSecs;
|
|
57
|
+
const timeDelta = endSecs - startSecs;
|
|
58
|
+
return this.convert(value, nextValue, this.easing.getValue(current / timeDelta));
|
|
59
|
+
} else {
|
|
60
|
+
const timeDelta = TC.getDelta(next.time, startNode.time)
|
|
61
|
+
const current = beatsOrSeconds - TC.toBeats(startNode.time)
|
|
62
|
+
// 其他类型,包括普通缓动和非钩定模板缓动
|
|
63
|
+
return this.convert(value, nextValue, this.easing.getValue(current / timeDelta));
|
|
64
|
+
}
|
|
52
65
|
}
|
|
53
66
|
abstract convert(start: T, end: T, progress: number): T;
|
|
54
67
|
/**
|
|
@@ -159,11 +172,11 @@ export class TextEasedEvaluator extends EasedEvaluator<string> {
|
|
|
159
172
|
if (interpretedAs === InterpreteAs.float) {
|
|
160
173
|
const start = parseFloat(value);
|
|
161
174
|
const delta = parseFloat(nextValue as string) - start;
|
|
162
|
-
return start + progress * delta + "";
|
|
175
|
+
return (start + progress * delta).toFixed(3) + "";
|
|
163
176
|
} else if (interpretedAs === InterpreteAs.int) {
|
|
164
177
|
const start = parseInt(value);
|
|
165
178
|
const delta = parseInt(nextValue as string) - start;
|
|
166
|
-
return start + Math.
|
|
179
|
+
return start + Math.floor(progress * delta) + "";
|
|
167
180
|
} else
|
|
168
181
|
if (value.startsWith(nextValue as string)) {
|
|
169
182
|
const startLen = (nextValue as string).length;
|
|
@@ -218,8 +231,10 @@ export class MacroEvaluator<T extends EventValueESType> extends Evaluator<T> {
|
|
|
218
231
|
node.evaluator = this;
|
|
219
232
|
this.consumers.set(node, this.compile(node, chart));
|
|
220
233
|
}
|
|
221
|
-
eval(
|
|
222
|
-
|
|
234
|
+
override eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
235
|
+
override eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
236
|
+
eval(event: NonLastStartNode<T>, beats: number, timeCalculator?: TimeCalculator): T {
|
|
237
|
+
return this.consumers.get(event)!.eval(event, beats, timeCalculator);
|
|
223
238
|
}
|
|
224
239
|
dumpFor(node: EventStartNode<T>): MacroEvaluatorDataKPA2 {
|
|
225
240
|
return {
|
|
@@ -242,11 +257,21 @@ export class ExpressionEvaluator<T extends EventValueESType> extends Evaluator<T
|
|
|
242
257
|
super();
|
|
243
258
|
this.func = new Function("t", "return " + jsExpr) as (t: number) => T;
|
|
244
259
|
}
|
|
245
|
-
override eval(startNode: NonLastStartNode<T>, beats: number): T
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
override eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
261
|
+
override eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
262
|
+
override eval(startNode: NonLastStartNode<T>, beatsOrSecs: number, timeCalculator?: TimeCalculator): T {
|
|
263
|
+
if (timeCalculator) {
|
|
264
|
+
const startSecs = timeCalculator.toSeconds(TC.toBeats(startNode.time));
|
|
265
|
+
const endSecs = timeCalculator.toSeconds(TC.toBeats(startNode.next.time));
|
|
266
|
+
const current = beatsOrSecs - startSecs;
|
|
267
|
+
const timeDelta = endSecs - startSecs;
|
|
268
|
+
return this.func(current / timeDelta);
|
|
269
|
+
} else {
|
|
270
|
+
const next = startNode.next;
|
|
271
|
+
const timeDelta = TC.getDelta(next.time, startNode.time)
|
|
272
|
+
const current = beatsOrSecs - TC.toBeats(startNode.time)
|
|
273
|
+
return this.func(current / timeDelta);
|
|
274
|
+
}
|
|
250
275
|
}
|
|
251
276
|
override dumpFor(): ExpressionEvaluatorDataKPA2 {
|
|
252
277
|
return {
|
package/event.ts
CHANGED
|
@@ -78,11 +78,6 @@ export abstract class EventNode<VT extends EventValueESType = number> extends Ev
|
|
|
78
78
|
* @deprecated
|
|
79
79
|
*/
|
|
80
80
|
static getEasing(data: EventDataKPA<EventValueESType>, templates: TemplateEasingLib, notSegmented = false): Easing {
|
|
81
|
-
const left = data.easingLeft;
|
|
82
|
-
const right = data.easingRight;
|
|
83
|
-
if (!notSegmented && (left && right) && (left !== 0.0 || right !== 1.0)) {
|
|
84
|
-
return new SegmentedEasing(EventNode.getEasing(data, templates, true), left, right)
|
|
85
|
-
}
|
|
86
81
|
if (data.bezier) {
|
|
87
82
|
const bp = data.bezierPoints
|
|
88
83
|
const easing = new BezierEasing([bp[0], bp[1]], [bp[2], bp[3]]);
|
|
@@ -346,14 +341,16 @@ export class EventStartNode<VT extends EventValueESType = number> extends EventN
|
|
|
346
341
|
macroTime: this.macroTime?.dumpForNode(this),
|
|
347
342
|
linkedMacro: [...this.linkedMacros].map(macro => macro.dumpLinkForNode(this)),
|
|
348
343
|
}
|
|
349
|
-
}
|
|
350
|
-
getValueAt(
|
|
344
|
+
}
|
|
345
|
+
getValueAt(seconds: number, timeCalculator: TimeCalculator): VT;
|
|
346
|
+
getValueAt(beats: number): VT;
|
|
347
|
+
getValueAt(beatsOrSecs: number, timeCalculator?: TimeCalculator): VT {
|
|
351
348
|
// 除了尾部的开始节点,其他都有下个节点
|
|
352
349
|
// 钩定型缓动也有
|
|
353
350
|
if (this.next.type === NodeType.TAIL) {
|
|
354
351
|
return this.value;
|
|
355
352
|
}
|
|
356
|
-
return this.evaluator.eval(this as NonLastStartNode<VT>,
|
|
353
|
+
return this.evaluator.eval(this as NonLastStartNode<VT>, beatsOrSecs, timeCalculator);
|
|
357
354
|
}
|
|
358
355
|
getSpeedValueAt(this: EventStartNode<number>, beats: number) {
|
|
359
356
|
if (this.next.type === NodeType.TAIL) {
|
|
@@ -798,6 +795,9 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
798
795
|
getValueAt(beats: number, usePrev: boolean = false): VT {
|
|
799
796
|
return this.getNodeAt(beats, usePrev).getValueAt(beats);
|
|
800
797
|
}
|
|
798
|
+
getValueAtBySecs(beats: number, seconds: number, timeCalculator: TimeCalculator, usePrev: boolean = false) {
|
|
799
|
+
return this.getNodeAt(beats, usePrev).getValueAt(seconds, timeCalculator);
|
|
800
|
+
}
|
|
801
801
|
getFloorPositionAt(this: EventNodeSequence<number>, beats: number, timeCalculator: TimeCalculator) {
|
|
802
802
|
const node: EventStartNode<number> = this.getNodeAt(beats);
|
|
803
803
|
const value = node.getLocalFloorPos(beats, timeCalculator) + node.floorPosition;
|
|
@@ -826,7 +826,7 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
826
826
|
const prevStart = node.previous.previous;
|
|
827
827
|
currentFP = prevStart.floorPosition + prevStart.getFullLocalFloorPos(tc);
|
|
828
828
|
} else {
|
|
829
|
-
currentFP = 0;
|
|
829
|
+
node.floorPosition = currentFP = 0;
|
|
830
830
|
}
|
|
831
831
|
while (true) {
|
|
832
832
|
const canBeEnd = node.next;
|
|
@@ -967,6 +967,23 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
967
967
|
currentNode = currentNode.next.next;
|
|
968
968
|
}
|
|
969
969
|
}
|
|
970
|
+
hasReferenceTo(this: EventNodeSequence<number>, seq: EventNodeSequence<number>) {
|
|
971
|
+
|
|
972
|
+
let node = this.head.next;
|
|
973
|
+
while (true) {
|
|
974
|
+
const endNode = node.next;
|
|
975
|
+
if (endNode.type === NodeType.TAIL) {
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
const evaluator = node.evaluator;
|
|
979
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof TemplateEasing) {
|
|
980
|
+
if (TemplateEasing.checkCircularReference(seq, evaluator.easing as TemplateEasing)) {
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
node = endNode.next;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
970
987
|
}
|
|
971
988
|
|
|
972
989
|
export type SpeedENS = EventNodeSequence<number> & { type: EventType.speed };
|
package/index.d.ts
CHANGED
|
@@ -75,10 +75,25 @@ declare module "chartTypes" {
|
|
|
75
75
|
* Sets the Z index for the hit effects of the note. Defaults to 7.
|
|
76
76
|
*/
|
|
77
77
|
zIndexHitEffects?: number;
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Sets the tint for the hit effects of the note. Defaults to null.
|
|
80
|
+
*
|
|
81
|
+
* @alias color
|
|
82
|
+
*/
|
|
79
83
|
tint?: RGB;
|
|
84
|
+
/**
|
|
85
|
+
* @see {@linkcode tint}
|
|
86
|
+
*/
|
|
87
|
+
color?: RGB;
|
|
80
88
|
tintHitEffects?: RGB;
|
|
81
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Determines the width of the judgment area of the note. Defaults to size.
|
|
91
|
+
* @alias judgeSize
|
|
92
|
+
*/
|
|
93
|
+
judgeArea?: number;
|
|
94
|
+
/**
|
|
95
|
+
* @see {@linkcode judgeArea}
|
|
96
|
+
*/
|
|
82
97
|
judgeSize?: number;
|
|
83
98
|
}
|
|
84
99
|
export interface NoteDataKPA {
|
|
@@ -102,7 +117,11 @@ declare module "chartTypes" {
|
|
|
102
117
|
type: NoteType;
|
|
103
118
|
/** 音符可视时间(打击前多少秒开始显现,默认99999.0) */
|
|
104
119
|
visibleTime?: number;
|
|
105
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* y值偏移,使音符被打击时的位置偏离判定线
|
|
122
|
+
*
|
|
123
|
+
* @deprecated 使用{@linkcode absoluteYOffset}代替。
|
|
124
|
+
*/
|
|
106
125
|
yOffset: number;
|
|
107
126
|
/** Sets the Z index for the object. */
|
|
108
127
|
zIndex?: number;
|
|
@@ -110,10 +129,11 @@ declare module "chartTypes" {
|
|
|
110
129
|
* Sets the Z index for the hit effects of the note. Defaults to 7.
|
|
111
130
|
*/
|
|
112
131
|
zIndexHitEffects?: number;
|
|
113
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Sets the tint for the hit effects of the note. Defaults to null.
|
|
134
|
+
*/
|
|
114
135
|
tint?: RGB;
|
|
115
136
|
tintHitEffects?: RGB;
|
|
116
|
-
/** Determines the width of the judgment area of the note. Defaults to size. */
|
|
117
137
|
judgeSize?: number;
|
|
118
138
|
visibleBeats?: number;
|
|
119
139
|
absoluteYOffset: number;
|
|
@@ -533,7 +553,7 @@ declare module "chartTypes" {
|
|
|
533
553
|
export type ExtendedEventTypeName = "scaleX" | "scaleY" | "text" | "color";
|
|
534
554
|
}
|
|
535
555
|
declare module "version" {
|
|
536
|
-
export const VERSION =
|
|
556
|
+
export const VERSION = 213;
|
|
537
557
|
export const SCHEMA = "https://cdn.jsdelivr.net/npm/kipphi@2.1.0/chartType2.schema.json";
|
|
538
558
|
}
|
|
539
559
|
declare module "util" {
|
|
@@ -684,7 +704,12 @@ declare module "bpm" {
|
|
|
684
704
|
/**
|
|
685
705
|
* BPM序列类,管理BPM变化序列
|
|
686
706
|
* 拥有与事件类似的逻辑,每对节点之间代表一个BPM相同的片段
|
|
687
|
-
* 片段之间BPM
|
|
707
|
+
* 片段之间BPM可以发生改变。
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* const bpmList = [{ startTime: [0, 0, 0], bpm: 120 }];
|
|
711
|
+
* const bpmSequence = new BPMSequence(bpmList, 180);
|
|
712
|
+
* // JumpArray 在构造器中自动初始化。
|
|
688
713
|
*/
|
|
689
714
|
export class BPMSequence extends EventNodeSequence {
|
|
690
715
|
duration: number;
|
|
@@ -707,7 +732,9 @@ declare module "bpm" {
|
|
|
707
732
|
*/
|
|
708
733
|
initJump(): void;
|
|
709
734
|
/**
|
|
710
|
-
*
|
|
735
|
+
* 更新秒跳转数组。
|
|
736
|
+
*
|
|
737
|
+
* 缓存每个节点的秒数发生在这里。
|
|
711
738
|
*/
|
|
712
739
|
updateSecondJump(): void;
|
|
713
740
|
/**
|
|
@@ -729,14 +756,24 @@ declare module "bpm" {
|
|
|
729
756
|
dumpBPM(): BPMSegmentData[];
|
|
730
757
|
/**
|
|
731
758
|
* 根据拍数获取节点
|
|
759
|
+
*
|
|
732
760
|
* @param beats 拍数
|
|
733
|
-
* @param usePrev
|
|
761
|
+
* @param usePrev 是否使用前一个节点。假设有两个BPM片段,0-2拍,2-无穷,`getNodeAt(2, true)` 会返回第一个片段的开始节点
|
|
734
762
|
* @returns 对应的BPM起始节点
|
|
735
763
|
*/
|
|
736
764
|
getNodeAt(beats: number, usePrev?: boolean): BPMStartNode;
|
|
737
765
|
}
|
|
738
766
|
/**
|
|
739
767
|
* 时间计算器类,用于处理拍数与秒数之间的转换
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
* const bpmList = = [
|
|
771
|
+
* { bpm: 120, startTime: [0, 0, 1] }
|
|
772
|
+
* ];
|
|
773
|
+
* const tc = new TimeCalculator();
|
|
774
|
+
* tc.bpmList = bpmList;
|
|
775
|
+
* tc.duration = 131; // 这两者都是必需的。
|
|
776
|
+
* tc.initSequence();
|
|
740
777
|
*/
|
|
741
778
|
export class TimeCalculator {
|
|
742
779
|
/** BPM片段数据列表 */
|
|
@@ -1197,6 +1234,7 @@ declare module "judgeline" {
|
|
|
1197
1234
|
*/
|
|
1198
1235
|
getValues(beats: number, usePrev?: boolean): [x: number, y: number, theta: number, alpha: number];
|
|
1199
1236
|
getStackedValue(type: keyof EventLayer, beats: number, usePrev?: boolean): number;
|
|
1237
|
+
getStackedValueBySeconds(type: keyof EventLayer, beats: number, seconds: number, timeCalculator: TimeCalculator, usePrev?: boolean): number;
|
|
1200
1238
|
/**
|
|
1201
1239
|
* 获取指定时间点的FloorPosition。
|
|
1202
1240
|
*
|
|
@@ -1329,6 +1367,7 @@ declare module "evaluator" {
|
|
|
1329
1367
|
import type { EventStartNode, NonLastStartNode } from "event";
|
|
1330
1368
|
import { EventValueTypeOfType, InterpreteAs, MacroEvaluatorBodyData, MacroEvaluatorDataKPA2, type ColorEasedEvaluatorKPA2, type EvaluatorDataKPA2, type EventValueESType, type ExpressionEvaluatorDataKPA2, type NumericEasedEvaluatorKPA2, type RGB, type TextEasedEvaluatorKPA2 } from "chartTypes";
|
|
1331
1369
|
import { Chart } from "chart";
|
|
1370
|
+
import { type TimeCalculator } from "bpm";
|
|
1332
1371
|
/**
|
|
1333
1372
|
* **求值器**
|
|
1334
1373
|
*
|
|
@@ -1340,12 +1379,14 @@ declare module "evaluator" {
|
|
|
1340
1379
|
*/
|
|
1341
1380
|
export abstract class Evaluator<T extends EventValueESType> {
|
|
1342
1381
|
abstract eval(event: NonLastStartNode<T>, beats: number): T;
|
|
1382
|
+
abstract eval(event: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
1343
1383
|
abstract dumpFor(node: EventStartNode<T>): EvaluatorDataKPA2<T>;
|
|
1344
1384
|
}
|
|
1345
1385
|
export abstract class EasedEvaluator<T extends EventValueESType> extends Evaluator<T> {
|
|
1346
1386
|
readonly easing: Easing;
|
|
1347
1387
|
constructor(easing: Easing);
|
|
1348
1388
|
eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
1389
|
+
eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
1349
1390
|
abstract convert(start: T, end: T, progress: number): T;
|
|
1350
1391
|
/**
|
|
1351
1392
|
* 派生一个类型相同但使用不同缓动的求值器
|
|
@@ -1397,7 +1438,8 @@ declare module "evaluator" {
|
|
|
1397
1438
|
constructor(expression: string, id: string);
|
|
1398
1439
|
compile(node: EventStartNode<T>, chart: Chart): ExpressionEvaluator<T>;
|
|
1399
1440
|
assignTo(node: EventStartNode<T>, chart: Chart): void;
|
|
1400
|
-
eval(
|
|
1441
|
+
eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
1442
|
+
eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
1401
1443
|
dumpFor(node: EventStartNode<T>): MacroEvaluatorDataKPA2;
|
|
1402
1444
|
dumpContent(): MacroEvaluatorBodyData;
|
|
1403
1445
|
}
|
|
@@ -1406,6 +1448,7 @@ declare module "evaluator" {
|
|
|
1406
1448
|
readonly func: (t: number) => T;
|
|
1407
1449
|
constructor(jsExpr: string);
|
|
1408
1450
|
eval(startNode: NonLastStartNode<T>, beats: number): T;
|
|
1451
|
+
eval(startNode: NonLastStartNode<T>, seconds: number, timeCalculator: TimeCalculator): T;
|
|
1409
1452
|
dumpFor(): ExpressionEvaluatorDataKPA2;
|
|
1410
1453
|
}
|
|
1411
1454
|
}
|
|
@@ -1530,6 +1573,8 @@ declare module "easing" {
|
|
|
1530
1573
|
dump(): TemplateEasingData;
|
|
1531
1574
|
get valueDelta(): number;
|
|
1532
1575
|
get headValue(): number;
|
|
1576
|
+
segmentedValueGetter(easingLeft: number, easingRight: number): (t: number) => number;
|
|
1577
|
+
static checkCircularReference(seq: EventNodeSequence, template: TemplateEasing): boolean;
|
|
1533
1578
|
}
|
|
1534
1579
|
export class WrapperEasing extends Easing {
|
|
1535
1580
|
evaluator: ExpressionEvaluator<number>;
|
|
@@ -2023,6 +2068,7 @@ declare module "event" {
|
|
|
2023
2068
|
*/
|
|
2024
2069
|
dump(): EventDataKPA2<VT>;
|
|
2025
2070
|
dumpAsFinal(): FinalEventStartNodeDataKPA2<VT>;
|
|
2071
|
+
getValueAt(seconds: number, timeCalculator: TimeCalculator): VT;
|
|
2026
2072
|
getValueAt(beats: number): VT;
|
|
2027
2073
|
getSpeedValueAt(this: EventStartNode<number>, beats: number): number;
|
|
2028
2074
|
/**
|
|
@@ -2154,6 +2200,7 @@ declare module "event" {
|
|
|
2154
2200
|
updateJump(from: ENOrHead<VT>, to: ENOrTail<VT>): void;
|
|
2155
2201
|
getNodeAt(beats: number, usePrev?: boolean): EventStartNode<VT>;
|
|
2156
2202
|
getValueAt(beats: number, usePrev?: boolean): VT;
|
|
2203
|
+
getValueAtBySecs(beats: number, seconds: number, timeCalculator: TimeCalculator, usePrev?: boolean): VT;
|
|
2157
2204
|
getFloorPositionAt(this: EventNodeSequence<number>, beats: number, timeCalculator: TimeCalculator): number;
|
|
2158
2205
|
/**
|
|
2159
2206
|
* 更新某个速度节点之后的FP
|
|
@@ -2182,6 +2229,7 @@ declare module "event" {
|
|
|
2182
2229
|
*/
|
|
2183
2230
|
static mergeSequences(sequences: EventNodeSequence[]): EventNodeSequence<number>;
|
|
2184
2231
|
checkErrors(): void;
|
|
2232
|
+
hasReferenceTo(this: EventNodeSequence<number>, seq: EventNodeSequence<number>): boolean;
|
|
2185
2233
|
}
|
|
2186
2234
|
export type SpeedENS = EventNodeSequence<number> & {
|
|
2187
2235
|
type: EventType.speed;
|
|
@@ -2213,10 +2261,12 @@ declare module "env" {
|
|
|
2213
2261
|
NODES_NOT_CONTINUOUS = 2609,
|
|
2214
2262
|
NODES_NOT_BELONG_TO_SAME_SEQUENCE = 2610,
|
|
2215
2263
|
NODES_HAS_ZERO_DELTA = 2611,
|
|
2264
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE = 2612,
|
|
2216
2265
|
CANNOT_DIVIDE_EXPRESSION_EVALUATOR = 2352,
|
|
2217
2266
|
MISSING_MACRO_EVALUATOR_KEY = 2336,
|
|
2218
2267
|
MACRO_EVALUATOR_NOT_FOUND = 2337,
|
|
2219
2268
|
INVALID_NOTE_PROP_TYPE = 2880,
|
|
2269
|
+
HOLD_HAS_NO_DURATION = 2848,
|
|
2220
2270
|
INVALID_TIME_TUPLE = 3104,
|
|
2221
2271
|
TIME_MACRO_NOT_FOUND = 3360,
|
|
2222
2272
|
VALUE_MACRO_NOT_FOUND = 3361,
|
|
@@ -2261,6 +2311,8 @@ declare module "env" {
|
|
|
2261
2311
|
PARAMETRIC_MACRO_REQUIRES_PROTO_KEY: (pos: any) => string;
|
|
2262
2312
|
MACRO_NOT_PARAMETRIC: (macroId: string, pos: any) => string;
|
|
2263
2313
|
EVENT_NODE_NOT_DENSE: (pos: string) => string;
|
|
2314
|
+
HOLD_HAS_NO_DURATION: () => string;
|
|
2315
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) => string;
|
|
2264
2316
|
};
|
|
2265
2317
|
interface ErrorMap extends Record<keyof typeof ERROR_IDS, Array<any>> {
|
|
2266
2318
|
EVENT_NODE_NOT_DENSE: [EventNode];
|
|
@@ -2325,6 +2377,8 @@ declare module "env" {
|
|
|
2325
2377
|
PARAMETRIC_MACRO_REQUIRES_PROTO_KEY: (pos: any) => KPAError<ERROR_IDS.PARAMETRIC_MACRO_REQUIRES_PROTO_KEY>;
|
|
2326
2378
|
MACRO_NOT_PARAMETRIC: (macroId: string, pos: any) => KPAError<ERROR_IDS.MACRO_NOT_PARAMETRIC>;
|
|
2327
2379
|
EVENT_NODE_NOT_DENSE: (pos: string) => KPAError<ERROR_IDS.EVENT_NODE_NOT_DENSE>;
|
|
2380
|
+
HOLD_HAS_NO_DURATION: () => KPAError<ERROR_IDS.HOLD_HAS_NO_DURATION>;
|
|
2381
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) => KPAError<ERROR_IDS.TEMPLATE_EASING_CIRCULAR_REFERENCE>;
|
|
2328
2382
|
};
|
|
2329
2383
|
freeze(): void;
|
|
2330
2384
|
};
|
|
@@ -2437,6 +2491,7 @@ declare module "operation/basic" {
|
|
|
2437
2491
|
undo(): void;
|
|
2438
2492
|
redo(): void;
|
|
2439
2493
|
do(operation: Operation): void;
|
|
2494
|
+
tryDo(returnOperation: () => Operation): void;
|
|
2440
2495
|
processFlags(operation: Operation): void;
|
|
2441
2496
|
clear(): void;
|
|
2442
2497
|
addEventListener<T extends OpEventType>(type: T, listener: (event: OpEventMap[T]) => void, options?: boolean | AddEventListenerOptions): void;
|
|
@@ -2532,8 +2587,8 @@ declare module "operation/event" {
|
|
|
2532
2587
|
* @param targetPrevious 要插在谁后面 The node to insert after, accessed through `EventNodeSequence.getNodeAt(TC.toBeats(node))`
|
|
2533
2588
|
*/
|
|
2534
2589
|
constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP?: boolean);
|
|
2535
|
-
do(): void;
|
|
2536
|
-
undo(): void;
|
|
2590
|
+
do(chart: Chart): void;
|
|
2591
|
+
undo(chart: Chart): void;
|
|
2537
2592
|
}
|
|
2538
2593
|
export class EventNodePairInsertOrOverwriteOperation<VT extends EventValueESType> extends UnionOperation<LazyOperation<typeof EventNodePairInsertOperation<VT>> | EventNodeValueChangeOperation<VT>> {
|
|
2539
2594
|
overlapping: boolean;
|
|
@@ -3023,6 +3078,7 @@ declare module "rpeChartCompiler" {
|
|
|
3023
3078
|
chart: Chart;
|
|
3024
3079
|
sequenceMap: Map<EventNodeSequence<any>, EventNodeSequence<any>>;
|
|
3025
3080
|
interpolationStep: TimeT;
|
|
3081
|
+
deletesEmptyLines: boolean;
|
|
3026
3082
|
constructor(chart: Chart);
|
|
3027
3083
|
compileChart(): ChartDataRPE;
|
|
3028
3084
|
compileJudgeLine(judgeLine: JudgeLine): JudgeLineDataRPE;
|