kipphi 2.1.2 → 2.1.3-beta.2
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 +42 -2
- package/chartTypes.ts +26 -5
- package/easing.ts +31 -2
- package/env.ts +6 -0
- package/evaluator.ts +39 -14
- package/event.ts +50 -9
- package/index.d.ts +77 -11
- package/index.js +240 -79
- package/judgeline.ts +100 -43
- package/note.ts +19 -21
- package/operation/easy.ts +136 -0
- package/operation/event.ts +7 -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/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
|
@@ -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
|
/**
|
|
@@ -169,7 +175,7 @@ export class Chart {
|
|
|
169
175
|
*/
|
|
170
176
|
static fromRPEJSON(data: ChartDataRPE, duration: number) {
|
|
171
177
|
const chart = new Chart();
|
|
172
|
-
chart.judgeLineGroups = data.judgeLineGroup.map(group => new JudgeLineGroup(group));
|
|
178
|
+
chart.judgeLineGroups = (data.judgeLineGroup || ["Default"]).map(group => new JudgeLineGroup(group));
|
|
173
179
|
chart.name = data.META.name;
|
|
174
180
|
chart.level = data.META.level;
|
|
175
181
|
chart.offset = data.META.offset;
|
|
@@ -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
|
}
|
|
@@ -294,7 +303,19 @@ export class Chart {
|
|
|
294
303
|
}
|
|
295
304
|
chart.templateEasingLib.implement(easingData.name, sequence as EventNodeSequence<number>);
|
|
296
305
|
}
|
|
297
|
-
|
|
306
|
+
for (let i = 0; i < len; i++) {
|
|
307
|
+
const easingData = templateEasings[i];
|
|
308
|
+
const sequence = chart.sequenceMap.get(easingData.content) as EventNodeSequence;
|
|
309
|
+
// 遍历该序列检查循环依赖
|
|
310
|
+
if (sequence.hasReferenceTo(sequence)) {
|
|
311
|
+
throw err.TEMPLATE_EASING_CIRCULAR_REFERENCE(easingData.name);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
chart.templateEasingLib.check();
|
|
316
|
+
|
|
317
|
+
chart.checkSegmentedTemplates();
|
|
318
|
+
|
|
298
319
|
for (const lineData of data.orphanLines) {
|
|
299
320
|
const line: JudgeLine = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
|
|
300
321
|
chart.orphanLines.push(line)
|
|
@@ -718,6 +739,25 @@ export class Chart {
|
|
|
718
739
|
|
|
719
740
|
}
|
|
720
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
|
+
}
|
|
721
761
|
}
|
|
722
762
|
|
|
723
763
|
/**
|
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,8 @@ 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,
|
|
77
|
+
EASING_DELTA_CANNOT_BE_ZERO = EASING | INVALID_USAGE | 5,
|
|
76
78
|
|
|
77
79
|
CANNOT_DIVIDE_EXPRESSION_EVALUATOR = EVALUATOR | INVALID_USAGE | 0,
|
|
78
80
|
MISSING_MACRO_EVALUATOR_KEY = EVALUATOR | INVALID_DATA | 0,
|
|
@@ -181,6 +183,10 @@ export const ERRORS = {
|
|
|
181
183
|
`EventNode is not dense. At ${pos}`,
|
|
182
184
|
HOLD_HAS_NO_DURATION: () =>
|
|
183
185
|
`Hold should have a duration.`,
|
|
186
|
+
TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) =>
|
|
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)}`,
|
|
184
190
|
} satisfies Record<keyof typeof ERROR_IDS, (...args: any[]) => string>
|
|
185
191
|
|
|
186
192
|
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) {
|
|
@@ -643,6 +640,12 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
643
640
|
for (let index = 0; index < length; index++) {
|
|
644
641
|
const event = data[index];
|
|
645
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
|
+
}
|
|
646
649
|
// 从前面复制了,复用性减一
|
|
647
650
|
// KPA2没有更改RPE的按事件存储的机制。
|
|
648
651
|
if (TC.lt(event.startTime, lastEndTime)) { // event.startTime < lastEndTime
|
|
@@ -798,6 +801,9 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
798
801
|
getValueAt(beats: number, usePrev: boolean = false): VT {
|
|
799
802
|
return this.getNodeAt(beats, usePrev).getValueAt(beats);
|
|
800
803
|
}
|
|
804
|
+
getValueAtBySecs(beats: number, seconds: number, timeCalculator: TimeCalculator, usePrev: boolean = false) {
|
|
805
|
+
return this.getNodeAt(beats, usePrev).getValueAt(seconds, timeCalculator);
|
|
806
|
+
}
|
|
801
807
|
getFloorPositionAt(this: EventNodeSequence<number>, beats: number, timeCalculator: TimeCalculator) {
|
|
802
808
|
const node: EventStartNode<number> = this.getNodeAt(beats);
|
|
803
809
|
const value = node.getLocalFloorPos(beats, timeCalculator) + node.floorPosition;
|
|
@@ -826,7 +832,7 @@ export class EventNodeSequence<VT extends EventValueESType = number> { // 泛型
|
|
|
826
832
|
const prevStart = node.previous.previous;
|
|
827
833
|
currentFP = prevStart.floorPosition + prevStart.getFullLocalFloorPos(tc);
|
|
828
834
|
} else {
|
|
829
|
-
currentFP = 0;
|
|
835
|
+
node.floorPosition = currentFP = 0;
|
|
830
836
|
}
|
|
831
837
|
while (true) {
|
|
832
838
|
const canBeEnd = node.next;
|
|
@@ -956,15 +962,50 @@ 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;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
hasReferenceTo(this: EventNodeSequence<number>, seq: EventNodeSequence<number>) {
|
|
995
|
+
|
|
996
|
+
let node = this.head.next;
|
|
997
|
+
while (true) {
|
|
998
|
+
const endNode = node.next;
|
|
999
|
+
if (endNode.type === NodeType.TAIL) {
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
const evaluator = node.evaluator;
|
|
1003
|
+
if (evaluator instanceof EasedEvaluator && evaluator.easing instanceof TemplateEasing) {
|
|
1004
|
+
if (TemplateEasing.checkCircularReference(seq, evaluator.easing as TemplateEasing)) {
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
node = endNode.next;
|
|
968
1009
|
}
|
|
969
1010
|
}
|
|
970
1011
|
}
|