kipphi 2.1.2 → 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 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 === 0) {
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: AnyBN) => {
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
- /** Sets the tint for the hit effects of the note. Defaults to null. */
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
- /** Determines the width of the judgment area of the note. Defaults to size. */
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
- /** y值偏移,使音符被打击时的位置偏离判定线 */
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
- /** Sets the tint for the hit effects of the note. Defaults to null. */
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,
@@ -181,6 +182,8 @@ export const ERRORS = {
181
182
  `EventNode is not dense. At ${pos}`,
182
183
  HOLD_HAS_NO_DURATION: () =>
183
184
  `Hold should have a duration.`,
185
+ TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) =>
186
+ `Template Easing '${temEasName}' has circular reference`,
184
187
  } satisfies Record<keyof typeof ERROR_IDS, (...args: any[]) => string>
185
188
 
186
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
- return this.convert(value, nextValue, this.easing.getValue(current / timeDelta));
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.round(progress * delta) + "";
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(event: NonLastStartNode<T>, beats: number): T {
222
- return this.consumers.get(event)!.eval(event, beats);
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
- const next = startNode.next;
247
- const timeDelta = TC.getDelta(next.time, startNode.time)
248
- const current = beats - TC.toBeats(startNode.time)
249
- return this.func(current / timeDelta);
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(beats: number): VT {
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>, beats);
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
- /** Sets the tint for the hit effects of the note. Defaults to null. */
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
- /** Determines the width of the judgment area of the note. Defaults to size. */
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
- /** y值偏移,使音符被打击时的位置偏离判定线 */
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
- /** Sets the tint for the hit effects of the note. Defaults to null. */
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 = 212;
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(event: NonLastStartNode<T>, beats: number): T;
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,6 +2261,7 @@ 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,
@@ -2263,6 +2312,7 @@ declare module "env" {
2263
2312
  MACRO_NOT_PARAMETRIC: (macroId: string, pos: any) => string;
2264
2313
  EVENT_NODE_NOT_DENSE: (pos: string) => string;
2265
2314
  HOLD_HAS_NO_DURATION: () => string;
2315
+ TEMPLATE_EASING_CIRCULAR_REFERENCE: (temEasName: string) => string;
2266
2316
  };
2267
2317
  interface ErrorMap extends Record<keyof typeof ERROR_IDS, Array<any>> {
2268
2318
  EVENT_NODE_NOT_DENSE: [EventNode];
@@ -2328,6 +2378,7 @@ declare module "env" {
2328
2378
  MACRO_NOT_PARAMETRIC: (macroId: string, pos: any) => KPAError<ERROR_IDS.MACRO_NOT_PARAMETRIC>;
2329
2379
  EVENT_NODE_NOT_DENSE: (pos: string) => KPAError<ERROR_IDS.EVENT_NODE_NOT_DENSE>;
2330
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>;
2331
2382
  };
2332
2383
  freeze(): void;
2333
2384
  };
@@ -3027,6 +3078,7 @@ declare module "rpeChartCompiler" {
3027
3078
  chart: Chart;
3028
3079
  sequenceMap: Map<EventNodeSequence<any>, EventNodeSequence<any>>;
3029
3080
  interpolationStep: TimeT;
3081
+ deletesEmptyLines: boolean;
3030
3082
  constructor(chart: Chart);
3031
3083
  compileChart(): ChartDataRPE;
3032
3084
  compileJudgeLine(judgeLine: JudgeLine): JudgeLineDataRPE;