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 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) : new EventEndNode(this.time, this.value);
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) : new EventEndNode(this.time, this.value);
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.tint !== undefined ? hex2rgb(this.tintHitEffects) : undefined,
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.tint !== undefined ? hex2rgb(this.tintHitEffects) : undefined,
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.splice(this.notes.indexOf(note), 1);
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.size > 0 || line2.hnLists.size > 0 || line2.eventLayers.length > 0 || ["moveX", "moveY", "rotate", "alpha"].some((evType) => {
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.dumpEventNodeSequence(judgeLine.speedSequence) : undefined
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 = rightDividedNodeSrc;
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
- first.evaluator = evaluator.deriveWithEasing(new SegmentedEasing(rightDividedNodeSrc.evaluator.easing, 0, newRight));
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
- first.evaluator = evaluator.deriveWithEasing(rightDividedNodeSrc.evaluator.easing);
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.tint !== undefined ? hex2rgb(this.tintHitEffects) : undefined,
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.tint !== undefined ? hex2rgb(this.tintHitEffects) : undefined,
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.splice(this.notes.indexOf(note), 1)
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
- private _startTime: TimeT;
39
- private _endTime: TimeT;
40
- private _type: NoteType;
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._startTime = target.startTime;
47
- this._endTime = target.endTime;
48
- this._type = target.type;
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._startTime;
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._startTime = timeT;
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._endTime; }
68
+ get endTime() { return this._fields.endTime; }
68
69
  set endTime(userTime: Time) {
69
- if (this._type !== NoteType.hold) {
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._startTime)) {
74
+ if (!TC.gt(timeT, this._fields.startTime)) {
74
75
  throw new Error("");
75
76
  }
76
- this._endTime = timeT;
77
+ this._fields.endTime = timeT;
77
78
  this.buffer.push(HoldEndTimeChangeOperation.lazy(this.target, timeT));
78
79
  }
79
- get type() { return this._type; }
80
+ get type() { return this._fields.type; }
80
81
  set type(type: NoteType) {
81
- if (this._type === type) {
82
+ if (this._fields.type === type) {
82
83
  return;
83
84
  }
84
- this._type = type;
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
  });
@@ -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-beta.1",
4
+ "version": "2.1.3",
5
5
  "author": "Team Zincs (https://github.com/TeamZincs)",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -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.size > 0
36
- || line.hnLists.size > 0
47
+ return [...line.nnLists].some(([_, l]) => hasNotes(l))
48
+ || [...line.hnLists].some(([_, l]) => hasNotes(l))
37
49
  || line.eventLayers.length > 0
38
- || (["moveX", "moveY", "rotate", "alpha"] as const).some((evType) => {
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.dumpEventNodeSequence(judgeLine.speedSequence) : undefined
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 = rightDividedNodeSrc;
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
- first.evaluator = evaluator.deriveWithEasing(
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
- first.evaluator = evaluator.deriveWithEasing(
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