kipphi 2.0.0 → 2.1.0
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/README.md +1 -3
- package/basic.ts +285 -0
- package/bpm.ts +332 -0
- package/chart.ts +418 -106
- package/chartType.schema.json +584 -0
- package/chartType2.schema.json +1107 -0
- package/chartTypes.ts +131 -30
- package/easing.ts +125 -90
- package/env.ts +208 -0
- package/evaluator.ts +106 -20
- package/event.ts +357 -255
- package/index.d.ts +3055 -0
- package/index.js +5530 -0
- package/index.ts +17 -11
- package/judgeline.ts +395 -94
- package/jumparray.ts +10 -11
- package/line.ts +246 -0
- package/macro.ts +215 -0
- package/note.ts +32 -55
- package/operation/basic.ts +285 -0
- package/operation/chart.ts +21 -0
- package/operation/event.ts +511 -0
- package/operation/index.ts +6 -0
- package/operation/line.ts +304 -0
- package/operation/macro.ts +60 -0
- package/operation/note.ts +457 -0
- package/package.json +7 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +2 -3
- package/util.ts +21 -1
- package/version.ts +2 -1
package/chart.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import { VERSION } from "./version";
|
|
1
|
+
import { SCHEMA, VERSION } from "./version";
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
|
-
TimeCalculator
|
|
6
|
-
|
|
7
|
-
} from "./time";
|
|
4
|
+
TimeCalculator
|
|
5
|
+
} from "./bpm";
|
|
8
6
|
|
|
9
7
|
import {
|
|
10
8
|
BezierEasing,
|
|
11
9
|
Easing,
|
|
10
|
+
NormalEasing,
|
|
12
11
|
rpeEasingArray,
|
|
13
12
|
SegmentedEasing,
|
|
14
13
|
TemplateEasingLib,
|
|
@@ -16,13 +15,10 @@ import {
|
|
|
16
15
|
|
|
17
16
|
import {
|
|
18
17
|
EventNodeSequence,
|
|
19
|
-
type AnyEN,
|
|
20
|
-
type ENOrHead,
|
|
21
|
-
type ENOrTail,
|
|
22
|
-
EventNodeLike,
|
|
23
18
|
EventStartNode,
|
|
24
19
|
EventEndNode,
|
|
25
20
|
EventNode,
|
|
21
|
+
SpeedENS,
|
|
26
22
|
} from "./event";
|
|
27
23
|
|
|
28
24
|
import {
|
|
@@ -34,19 +30,13 @@ import {
|
|
|
34
30
|
} from "./judgeline";
|
|
35
31
|
|
|
36
32
|
import {
|
|
37
|
-
Note,
|
|
38
|
-
NNList,
|
|
39
33
|
NNNList,
|
|
40
|
-
type NNNOrHead,
|
|
41
34
|
type NNNOrTail,
|
|
42
35
|
NNNode
|
|
43
36
|
} from "./note";
|
|
44
37
|
|
|
45
38
|
import {
|
|
46
|
-
type RGB,
|
|
47
39
|
type TimeT,
|
|
48
|
-
type EventType,
|
|
49
|
-
type EventNodeSequenceDataKPA,
|
|
50
40
|
type ValueTypeOfEventType,
|
|
51
41
|
type BPMSegmentData,
|
|
52
42
|
type ChartDataRPE,
|
|
@@ -57,6 +47,7 @@ import {
|
|
|
57
47
|
type EvaluatorDataKPA2,
|
|
58
48
|
EvaluatorType,
|
|
59
49
|
type EasedEvaluatorDataOfType,
|
|
50
|
+
EventType,
|
|
60
51
|
EventValueType,
|
|
61
52
|
type EventValueTypeOfType,
|
|
62
53
|
type EventValueESType,
|
|
@@ -64,11 +55,17 @@ import {
|
|
|
64
55
|
type TextEasedEvaluatorKPA2,
|
|
65
56
|
type EventNodeSequenceDataKPA2,
|
|
66
57
|
type ChartDataKPA2,
|
|
67
|
-
type EventDataKPA2
|
|
58
|
+
type EventDataKPA2,
|
|
59
|
+
InterpreteAs,
|
|
60
|
+
MacroEvaluatorBodyData,
|
|
61
|
+
MacroEvaluatorDataKPA2,
|
|
62
|
+
FinalEventStartNodeDataKPA2,
|
|
63
|
+
MacroData,
|
|
64
|
+
MacroLink
|
|
68
65
|
} from "./chartTypes";
|
|
69
|
-
import { ProgramUpdateLevel } from "typescript";
|
|
70
66
|
import { ColorEasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type EasedEvaluatorOfType } from "./evaluator";
|
|
71
|
-
|
|
67
|
+
import { err, ERROR_IDS, KPAError } from "./env";
|
|
68
|
+
import { MacroLib, EventMacroTime, EventMacroValue, EventMacro } from "./macro";
|
|
72
69
|
|
|
73
70
|
/// #declaration:global
|
|
74
71
|
|
|
@@ -76,80 +73,103 @@ import { ColorEasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvalua
|
|
|
76
73
|
export type BasicEventName = "moveX" | "moveY" | "rotate" | "alpha" | "speed";
|
|
77
74
|
|
|
78
75
|
|
|
79
|
-
type Plain<T> = Record<string, T>;
|
|
80
76
|
|
|
81
77
|
|
|
82
|
-
/**
|
|
83
|
-
* 相当于 Python 推导式
|
|
84
|
-
* @param obj
|
|
85
|
-
* @param expr
|
|
86
|
-
* @param guard
|
|
87
|
-
* @returns
|
|
88
|
-
*/
|
|
89
|
-
function dictForIn<T, RT>(obj: Plain<T>, expr: (v: T) => RT, guard?: (v: T) => boolean): Plain<RT> {
|
|
90
|
-
let ret: Plain<RT> = {}
|
|
91
|
-
for (let key in obj) {
|
|
92
|
-
const each = obj[key]
|
|
93
|
-
if (!guard || guard && guard(each)) {
|
|
94
|
-
ret[key] = expr(each)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return ret;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
78
|
|
|
101
79
|
export type UIName = "combo" | "combonumber" | "score" | "pause" | "bar" | "name" | "level"
|
|
102
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 表示一张谱面的核心数据结构
|
|
83
|
+
*
|
|
84
|
+
* 包含了谱面的所有元素:判定线、音符、事件序列等信息
|
|
85
|
+
*/
|
|
103
86
|
export class Chart {
|
|
87
|
+
/** 谱面中所有的判定线列表 */
|
|
104
88
|
judgeLines: JudgeLine[] = [];
|
|
105
|
-
|
|
106
|
-
timeCalculator = new TimeCalculator();
|
|
89
|
+
/** 时间计算器,用于处理BPM变化和时间转换 */
|
|
90
|
+
readonly timeCalculator = new TimeCalculator();
|
|
91
|
+
/** 无父级的根判定线列表 */
|
|
107
92
|
orphanLines: JudgeLine[] = [];
|
|
108
|
-
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/** 谱面名称 */
|
|
109
97
|
name: string = "unknown";
|
|
98
|
+
/** 谱面难度等级 */
|
|
110
99
|
level: string = "unknown";
|
|
100
|
+
/** 曲师信息 */
|
|
111
101
|
composer: string = "unknown";
|
|
102
|
+
/** 谱师信息 */
|
|
112
103
|
charter: string = "unknown";
|
|
104
|
+
/** 插画师信息 */
|
|
113
105
|
illustrator: string = "unknown";
|
|
106
|
+
|
|
107
|
+
/** 谱面偏移时间(秒) */
|
|
114
108
|
offset: number = 0;
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
/** 模板缓动库,用于管理和复用缓动函数 */
|
|
111
|
+
readonly templateEasingLib = new TemplateEasingLib(EventNodeSequence.newSeq<EventType.easing>, ExpressionEvaluator);
|
|
112
|
+
readonly macroLib = new MacroLib();
|
|
113
|
+
|
|
114
|
+
/** 事件序列映射表,通过ID索引事件序列 */
|
|
115
|
+
readonly sequenceMap = new Map<string, EventNodeSequence<EventValueESType>>();
|
|
116
|
+
/** 有效节拍数(基于谱面持续时间计算得出) */
|
|
119
117
|
effectiveBeats: number;
|
|
118
|
+
/** 音符节点列表,用于管理谱面上的所有音符 */
|
|
120
119
|
nnnList: NNNList;
|
|
121
|
-
/**
|
|
120
|
+
/** 判定线组列表,用于组织和分类判定线 */
|
|
122
121
|
judgeLineGroups: JudgeLineGroup[] = [];
|
|
122
|
+
/** 谱面持续时间(秒) */
|
|
123
123
|
duration: number;
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
/** 谱面制作所用时间(以秒计) */
|
|
126
|
+
chartingSeconds: number;
|
|
127
|
+
/** RPE格式的谱面制作时间(以秒计) */
|
|
128
|
+
rpeChartingSeconds: number;
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
/** 标记谱面是否已被修改 */
|
|
130
132
|
modified: boolean = false;
|
|
133
|
+
/** 谱面最大连击数 */
|
|
131
134
|
maxCombo: number = 0;
|
|
132
135
|
|
|
133
136
|
|
|
137
|
+
/** 暂停按钮绑定的判定线 */
|
|
134
138
|
pauseAttach: JudgeLine | null = null;
|
|
139
|
+
/** 连击数字绑定的判定线 */
|
|
135
140
|
combonumberAttach: JudgeLine | null = null;
|
|
141
|
+
/** 连击标识绑定的判定线 */
|
|
136
142
|
comboAttach: JudgeLine | null = null;
|
|
143
|
+
/** 进度条绑定的判定线 */
|
|
137
144
|
barAttach: JudgeLine | null = null;
|
|
145
|
+
/** 分数显示绑定的判定线 */
|
|
138
146
|
scoreAttach: JudgeLine | null = null;
|
|
147
|
+
/** 歌曲名称显示绑定的判定线 */
|
|
139
148
|
nameAttach: JudgeLine | null = null;
|
|
149
|
+
/** 难度等级显示绑定的判定线 */
|
|
140
150
|
levelAttach: JudgeLine | null = null;
|
|
141
151
|
|
|
142
152
|
constructor() {}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 获取有效节拍数
|
|
156
|
+
* @returns 基于谱面持续时间计算的有效节拍数
|
|
157
|
+
*/
|
|
143
158
|
getEffectiveBeats() {
|
|
144
159
|
const effectiveBeats = this.timeCalculator.secondsToBeats(this.duration)
|
|
145
|
-
console.log(effectiveBeats)
|
|
146
160
|
this.effectiveBeats = effectiveBeats
|
|
147
161
|
return this.effectiveBeats
|
|
148
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 从RPE格式的JSON数据创建谱面对象
|
|
166
|
+
* @param data RPE格式的谱面数据
|
|
167
|
+
* @param duration 谱面持续时间(秒)
|
|
168
|
+
* @returns 创建的Chart对象
|
|
169
|
+
*/
|
|
149
170
|
static fromRPEJSON(data: ChartDataRPE, duration: number) {
|
|
150
171
|
const chart = new Chart();
|
|
151
172
|
chart.judgeLineGroups = data.judgeLineGroup.map(group => new JudgeLineGroup(group));
|
|
152
|
-
chart.bpmList = data.BPMList;
|
|
153
173
|
chart.name = data.META.name;
|
|
154
174
|
chart.level = data.META.level;
|
|
155
175
|
chart.offset = data.META.offset;
|
|
@@ -157,21 +177,14 @@ export class Chart {
|
|
|
157
177
|
chart.charter = data.META.charter ?? "unknown";
|
|
158
178
|
chart.illustrator = data.META.illustration ?? "unknown";
|
|
159
179
|
chart.duration = duration;
|
|
160
|
-
chart.
|
|
161
|
-
chart.
|
|
162
|
-
chart.
|
|
163
|
-
chart.
|
|
164
|
-
console.log(chart, chart.getEffectiveBeats())
|
|
180
|
+
chart.chartingSeconds = data.kpaChartTime ?? 0;
|
|
181
|
+
chart.rpeChartingSeconds = data.chartTime ?? 0;
|
|
182
|
+
chart.chartingSeconds = 0;
|
|
183
|
+
chart.initCalculator(data.BPMList)
|
|
165
184
|
chart.nnnList = new NNNList(chart.getEffectiveBeats())
|
|
166
185
|
|
|
167
|
-
/*
|
|
168
|
-
if (data.envEasings) {
|
|
169
|
-
chart.templateEasingLib.add(...data.envEasings)
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
*/
|
|
173
186
|
|
|
174
|
-
|
|
187
|
+
|
|
175
188
|
const judgeLineDataList: JudgeLineDataRPE[] = <JudgeLineDataRPE[]>data.judgeLineList;
|
|
176
189
|
const judgeLineList: JudgeLine[] = judgeLineDataList.map(
|
|
177
190
|
(lineData, id) =>
|
|
@@ -185,6 +198,7 @@ export class Chart {
|
|
|
185
198
|
const father = data.father === -1 ? null : judgeLineList[data.father];
|
|
186
199
|
if (father) {
|
|
187
200
|
father.children.add(line);
|
|
201
|
+
line.father = father;
|
|
188
202
|
} else {
|
|
189
203
|
chart.orphanLines.push(line);
|
|
190
204
|
}
|
|
@@ -193,10 +207,14 @@ export class Chart {
|
|
|
193
207
|
return chart
|
|
194
208
|
}
|
|
195
209
|
|
|
210
|
+
/**
|
|
211
|
+
* 从KPA格式的JSON数据创建谱面对象
|
|
212
|
+
* @param data KPA格式的谱面数据
|
|
213
|
+
* @returns 创建的Chart对象
|
|
214
|
+
*/
|
|
196
215
|
static fromKPAJSON(data: ChartDataKPA | ChartDataKPA2) {
|
|
197
216
|
const chart = new Chart();
|
|
198
217
|
|
|
199
|
-
chart.bpmList = data.bpmList;
|
|
200
218
|
chart.duration = data.duration;
|
|
201
219
|
chart.name = data.info.name;
|
|
202
220
|
chart.level = data.info.level;
|
|
@@ -205,46 +223,80 @@ export class Chart {
|
|
|
205
223
|
chart.charter = data.info.charter ?? "unknown";
|
|
206
224
|
chart.offset = data.offset;
|
|
207
225
|
chart.judgeLineGroups = data.judgeLineGroups.map(group => new JudgeLineGroup(group));
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
|
|
227
|
+
const interpreteAsSecs = data.version && data.version >= 203;
|
|
228
|
+
if (interpreteAsSecs) {
|
|
229
|
+
chart.chartingSeconds = data.chartTime ?? 0;
|
|
230
|
+
chart.rpeChartingSeconds = data.rpeChartTime ?? 0;
|
|
231
|
+
} else {
|
|
232
|
+
chart.chartingSeconds = data.chartTime ? data.chartTime * 60 : 0;
|
|
233
|
+
chart.rpeChartingSeconds = data.rpeChartTime ? data.rpeChartTime * 60 : 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
chart.initCalculator(data.bpmList);
|
|
237
|
+
chart.nnnList = new NNNList(chart.getEffectiveBeats());
|
|
238
|
+
/**
|
|
239
|
+
* 影响事件的格式、是否存在“求值器”这一中间层以及判定线的属性名
|
|
240
|
+
*/
|
|
241
|
+
const isKPA2 = data.version >= 200;
|
|
242
|
+
const templateEasings = isKPA2 ? (data as ChartDataKPA2).templateEasings : (data as ChartDataKPA).envEasings;
|
|
243
|
+
const len = templateEasings.length
|
|
214
244
|
for (let i = 0; i < len; i++) {
|
|
215
|
-
const easingData =
|
|
245
|
+
const easingData = templateEasings[i];
|
|
216
246
|
chart.templateEasingLib.require(easingData.name);
|
|
217
247
|
}
|
|
218
248
|
|
|
219
|
-
if (
|
|
249
|
+
if (isKPA2) {
|
|
250
|
+
chart.templateEasingLib.readWrapperEasings((data as ChartDataKPA2).wrapperEasings);
|
|
220
251
|
const sequences = (data as ChartDataKPA2).eventNodeSequences;
|
|
221
252
|
const length = sequences.length;
|
|
222
253
|
for (let i = 0; i < length; i++) {
|
|
223
254
|
const seqData = sequences[i];
|
|
224
|
-
|
|
255
|
+
type VT = ValueTypeOfEventType<typeof seqData.type>
|
|
256
|
+
const sequence = EventNodeSequence.fromKPA2JSON<typeof seqData.type, VT>(
|
|
257
|
+
seqData.type,
|
|
258
|
+
seqData.events as EventDataKPA2<VT>[],
|
|
259
|
+
chart,
|
|
260
|
+
seqData.id,
|
|
261
|
+
seqData.final as FinalEventStartNodeDataKPA2<VT>
|
|
262
|
+
);
|
|
225
263
|
sequence.id = seqData.id;
|
|
226
264
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
227
265
|
}
|
|
266
|
+
|
|
267
|
+
chart.macroLib.readTimeMacros((data as ChartDataKPA2).timeMacros ?? []);
|
|
268
|
+
chart.macroLib.readValueMacros((data as ChartDataKPA2).valueMacros ?? []);
|
|
228
269
|
} else {
|
|
229
270
|
|
|
230
271
|
const sequences = (data as ChartDataKPA).eventNodeSequences
|
|
231
272
|
const length = sequences.length
|
|
232
273
|
for (let i = 0; i < length; i++) {
|
|
233
274
|
const seqData = sequences[i];
|
|
234
|
-
const sequence = EventNodeSequence.fromRPEJSON<typeof seqData.type, ValueTypeOfEventType<typeof seqData.type>>(
|
|
275
|
+
const sequence = EventNodeSequence.fromRPEJSON<typeof seqData.type, ValueTypeOfEventType<typeof seqData.type>>(
|
|
276
|
+
seqData.type,
|
|
277
|
+
seqData.events,
|
|
278
|
+
chart,
|
|
279
|
+
seqData.id,
|
|
280
|
+
seqData.endValue);
|
|
235
281
|
sequence.id = seqData.id;
|
|
236
282
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
237
283
|
}
|
|
238
284
|
}
|
|
239
285
|
|
|
240
286
|
for (let i = 0; i < len; i++) {
|
|
241
|
-
const easingData =
|
|
242
|
-
|
|
287
|
+
const easingData = templateEasings[i];
|
|
288
|
+
const sequence = chart.sequenceMap.get(easingData.content);
|
|
289
|
+
if (sequence.type !== EventType.easing) {
|
|
290
|
+
throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_EASING_ENS(easingData.name);
|
|
291
|
+
}
|
|
292
|
+
if (typeof sequence.head.next.value !== "number") {
|
|
293
|
+
throw err.CANNOT_IMPLEMENT_TEMEAS_WITH_NON_NUMERIC_ENS(easingData.name);
|
|
294
|
+
}
|
|
295
|
+
chart.templateEasingLib.implement(easingData.name, sequence as EventNodeSequence<number>);
|
|
243
296
|
}
|
|
244
297
|
chart.templateEasingLib.check()
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const line: JudgeLine = JudgeLine.fromKPAJSON(isOld, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
|
|
298
|
+
for (const lineData of data.orphanLines) {
|
|
299
|
+
const line: JudgeLine = JudgeLine.fromKPAJSON(data.version, chart, lineData.id, lineData, chart.templateEasingLib, chart.timeCalculator)
|
|
248
300
|
chart.orphanLines.push(line)
|
|
249
301
|
}
|
|
250
302
|
chart.judgeLines.sort((a, b) => a.id - b.id);
|
|
@@ -262,11 +314,21 @@ export class Chart {
|
|
|
262
314
|
}
|
|
263
315
|
return chart;
|
|
264
316
|
}
|
|
265
|
-
|
|
266
|
-
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 初始化时间计算器
|
|
320
|
+
* @param bpmList BPM变化列表
|
|
321
|
+
*/
|
|
322
|
+
initCalculator(bpmList: BPMSegmentData[]) {
|
|
323
|
+
this.timeCalculator.bpmList = bpmList;
|
|
267
324
|
this.timeCalculator.duration = this.duration;
|
|
268
|
-
this.timeCalculator.
|
|
325
|
+
this.timeCalculator.initSequence()
|
|
269
326
|
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 更新有效节拍数
|
|
330
|
+
* @param duration 新的持续时间
|
|
331
|
+
*/
|
|
270
332
|
updateEffectiveBeats(duration: number) {
|
|
271
333
|
const EB = this.timeCalculator.secondsToBeats(duration);
|
|
272
334
|
for (let i = 0; i < this.judgeLines.length; i++) {
|
|
@@ -274,22 +336,32 @@ export class Chart {
|
|
|
274
336
|
judgeLine.updateEffectiveBeats(EB);
|
|
275
337
|
}
|
|
276
338
|
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 导出为KPA格式数据
|
|
342
|
+
* @returns KPA格式的谱面数据对象
|
|
343
|
+
*/
|
|
277
344
|
dumpKPA(): Required<ChartDataKPA2> {
|
|
278
345
|
const eventNodeSequenceCollector = new Set<EventNodeSequence>();
|
|
279
346
|
const orphanLines = [];
|
|
280
|
-
for (
|
|
347
|
+
for (const line of this.orphanLines) {
|
|
281
348
|
orphanLines.push(line.dumpKPA(eventNodeSequenceCollector, this.judgeLineGroups));
|
|
282
349
|
}
|
|
283
350
|
const envEasings = this.templateEasingLib.dump(eventNodeSequenceCollector);
|
|
284
|
-
const eventNodeSequenceData: EventNodeSequenceDataKPA2<
|
|
285
|
-
for (
|
|
351
|
+
const eventNodeSequenceData: EventNodeSequenceDataKPA2<EventValueESType>[] = [];
|
|
352
|
+
for (const sequence of eventNodeSequenceCollector) {
|
|
286
353
|
eventNodeSequenceData.push(sequence.dump());
|
|
287
354
|
}
|
|
288
355
|
return {
|
|
289
356
|
version: VERSION,
|
|
357
|
+
$schema: SCHEMA,
|
|
290
358
|
duration: this.duration,
|
|
291
359
|
bpmList: this.timeCalculator.dump(),
|
|
292
|
-
|
|
360
|
+
templateEasings: envEasings,
|
|
361
|
+
wrapperEasings: this.templateEasingLib.dumpWrapperEasings(),
|
|
362
|
+
macroEvaluators: this.macroLib.dumpMacroEvaluators(),
|
|
363
|
+
timeMacros: this.macroLib.dumpTimeMacros(),
|
|
364
|
+
valueMacros: this.macroLib.dumpValueMacros(),
|
|
293
365
|
eventNodeSequences: eventNodeSequenceData,
|
|
294
366
|
info: {
|
|
295
367
|
level: this.level,
|
|
@@ -310,23 +382,51 @@ export class Chart {
|
|
|
310
382
|
offset: this.offset,
|
|
311
383
|
orphanLines: orphanLines,
|
|
312
384
|
judgeLineGroups: this.judgeLineGroups.map(g => g.name),
|
|
313
|
-
chartTime: this.
|
|
314
|
-
rpeChartTime: this.
|
|
385
|
+
chartTime: this.chartingSeconds,
|
|
386
|
+
rpeChartTime: this.rpeChartingSeconds
|
|
315
387
|
};
|
|
316
388
|
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 创建一个新的二级音符节点
|
|
392
|
+
* @param time 节点时间
|
|
393
|
+
* @returns 新创建的NNNode对象
|
|
394
|
+
*/
|
|
317
395
|
createNNNode(time: TimeT) {
|
|
318
|
-
|
|
396
|
+
return new NNNode(time)
|
|
319
397
|
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 创建一个新的事件节点序列
|
|
401
|
+
* @param type 事件类型
|
|
402
|
+
* @param name 序列名称(ID)
|
|
403
|
+
* @returns 新创建的事件节点序列
|
|
404
|
+
* @throws {KPAError<ERROR_IDS.SEQUENCE_NAME_OCCUPIED>}
|
|
405
|
+
*/
|
|
320
406
|
createEventNodeSequence<T extends EventType>(type: T, name: string) {
|
|
321
407
|
if (this.sequenceMap.has(name)) {
|
|
322
|
-
throw
|
|
408
|
+
throw err.SEQUENCE_NAME_OCCUPIED(name);
|
|
323
409
|
}
|
|
324
410
|
const seq = EventNodeSequence.newSeq(type, this.getEffectiveBeats());
|
|
325
411
|
seq.id = name;
|
|
326
412
|
this.sequenceMap.set(name, seq);
|
|
327
413
|
return seq;
|
|
328
414
|
}
|
|
329
|
-
|
|
415
|
+
|
|
416
|
+
registerEventNodeSequence<T extends EventType>(type: T, name: string, seq: EventNodeSequence<T>) {
|
|
417
|
+
if (this.sequenceMap.has(name)) {
|
|
418
|
+
throw err.SEQUENCE_NAME_OCCUPIED(name);
|
|
419
|
+
}
|
|
420
|
+
seq.id = name;
|
|
421
|
+
this.sequenceMap.set(name, seq);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* 对谱面物量进行重新计数。
|
|
426
|
+
*
|
|
427
|
+
* 不会返回值,谱面物量存储在 `this.maxCombo` 中。
|
|
428
|
+
*/
|
|
429
|
+
countMaxCombo(): void {
|
|
330
430
|
let combo = 0;
|
|
331
431
|
const nnnlist = this.nnnList;
|
|
332
432
|
for (let node: NNNOrTail = nnnlist.head.next; node.type !== NodeType.TAIL; node = node.next) {
|
|
@@ -345,14 +445,27 @@ export class Chart {
|
|
|
345
445
|
}
|
|
346
446
|
this.maxCombo = combo;
|
|
347
447
|
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* 将UI绑定到某判定线
|
|
451
|
+
* @param ui UI名称,与RPEJSON中的代号相同
|
|
452
|
+
* @param judgeLine 所要绑定的目标判定线
|
|
453
|
+
* @throws {KPAError<ERROR_IDS.UI_OCCUPIED>}
|
|
454
|
+
*/
|
|
348
455
|
attachUIToLine(ui: UIName, judgeLine: JudgeLine) {
|
|
349
456
|
const key = `${ui}Attach` satisfies keyof Chart;
|
|
350
457
|
if (this[key]) {
|
|
351
|
-
throw
|
|
458
|
+
throw err.UI_OCCUPIED(ui);
|
|
352
459
|
}
|
|
353
460
|
this[key] = judgeLine;
|
|
354
461
|
judgeLine.hasAttachUI = true;
|
|
355
462
|
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 移除谱面中某个UI的绑定,使UI进入未绑定状态
|
|
466
|
+
* @param ui UI名称,与RPEJSON中的相同
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
356
469
|
detachUI(ui: UIName) {
|
|
357
470
|
const key = `${ui}Attach` satisfies keyof Chart;
|
|
358
471
|
const judgeLine = this[key];
|
|
@@ -372,6 +485,12 @@ export class Chart {
|
|
|
372
485
|
judgeLine.hasAttachUI = false;
|
|
373
486
|
}
|
|
374
487
|
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 查询指定判定线上绑定的UI组件
|
|
491
|
+
* @param judgeLine 目标判定线
|
|
492
|
+
* @returns 绑定到该判定线上的UI组件名称数组
|
|
493
|
+
*/
|
|
375
494
|
queryJudgeLineUI(judgeLine: JudgeLine): UIName[] {
|
|
376
495
|
const arr: UIName[] = [];
|
|
377
496
|
for (const ui of ["combo", "combonumber", "score", "pause", "bar", "name", "level"] satisfies UIName[]) {
|
|
@@ -381,6 +500,13 @@ export class Chart {
|
|
|
381
500
|
}
|
|
382
501
|
return arr;
|
|
383
502
|
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 扫描所有用到的判定线贴图(纹理)并返回
|
|
506
|
+
*
|
|
507
|
+
* 给谱面播放器的接口,谱面播放器需要在初加载时提供贴图
|
|
508
|
+
* @returns 所有使用的纹理名称集合
|
|
509
|
+
*/
|
|
384
510
|
scanAllTextures() {
|
|
385
511
|
const textures: Set<string> = new Set;
|
|
386
512
|
for (const line of this.judgeLines) {
|
|
@@ -388,6 +514,14 @@ export class Chart {
|
|
|
388
514
|
}
|
|
389
515
|
return textures
|
|
390
516
|
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 使用KPA2数据创建一个缓动对象。
|
|
520
|
+
*
|
|
521
|
+
* 只有对贝塞尔缓动和截段缓动才会创建新对象,其他几种缓动从缓动库等中的对象池获取
|
|
522
|
+
* @param data 缓动数据
|
|
523
|
+
* @returns 创建的缓动对象
|
|
524
|
+
*/
|
|
391
525
|
createEasingFromData(data: EasingDataKPA2) {
|
|
392
526
|
switch (data.type) {
|
|
393
527
|
case EasingType.bezier:
|
|
@@ -395,19 +529,44 @@ export class Chart {
|
|
|
395
529
|
case EasingType.normal:
|
|
396
530
|
return rpeEasingArray[data.identifier];
|
|
397
531
|
case EasingType.segmented:
|
|
398
|
-
return new SegmentedEasing(this.createEasingFromData(data), data.left, data.right);
|
|
532
|
+
return new SegmentedEasing(this.createEasingFromData(data.inner), data.left, data.right);
|
|
399
533
|
case EasingType.template:
|
|
400
534
|
return this.templateEasingLib.get(data.identifier);
|
|
535
|
+
case EasingType.wrapper:
|
|
536
|
+
return this.templateEasingLib.getWrapper(data.identifier);
|
|
401
537
|
}
|
|
402
538
|
}
|
|
403
|
-
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* 使用KPA2数据创建一个求值器对象。
|
|
542
|
+
*
|
|
543
|
+
* 求值器只有缓动型和表达式型两类。
|
|
544
|
+
* @param data 求值器数据
|
|
545
|
+
* @param type 事件值类型
|
|
546
|
+
* @returns 创建的求值器对象
|
|
547
|
+
*/
|
|
548
|
+
bindEvaluator<T extends EventValueESType>(node: EventStartNode<T>, data: EvaluatorDataKPA2<T>, type: EventValueTypeOfType<T>, pos: string) {
|
|
404
549
|
switch (data.type) {
|
|
405
550
|
case EvaluatorType.eased:
|
|
406
|
-
|
|
551
|
+
// 我管你这的那的 —— 小奶椰
|
|
552
|
+
node.evaluator = this.createEasedEvaluator(data, type) as unknown as Evaluator<T>;
|
|
553
|
+
break;
|
|
407
554
|
case EvaluatorType.expressionbased:
|
|
408
|
-
|
|
555
|
+
node.evaluator = this.createExpressionEvaluator(data) as ExpressionEvaluator<T>;
|
|
556
|
+
break;
|
|
557
|
+
case EvaluatorType.macro:
|
|
558
|
+
this.bindMacroEvaluator(node, data, pos);
|
|
409
559
|
}
|
|
410
560
|
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 使用KPA2数据创建一个缓动求值器。
|
|
564
|
+
*
|
|
565
|
+
* 对于普通缓动,这些求值器是从对应类构造器的静态对象池属性中获取的。
|
|
566
|
+
* @param data 缓动求值器数据
|
|
567
|
+
* @param type 事件值类型
|
|
568
|
+
* @returns 创建的缓动求值器对象
|
|
569
|
+
*/
|
|
411
570
|
createEasedEvaluator<T extends EventValueESType>(data: EasedEvaluatorDataOfType<T>, type: EventValueTypeOfType<T>): EasedEvaluatorOfType<T> {
|
|
412
571
|
switch (type) {
|
|
413
572
|
case EventValueType.numeric:
|
|
@@ -424,54 +583,207 @@ export class Chart {
|
|
|
424
583
|
: new TextEasedEvaluator(this.createEasingFromData(data.easing), (data as TextEasedEvaluatorKPA2).interpretedAs) as EasedEvaluatorOfType<T>
|
|
425
584
|
}
|
|
426
585
|
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* 用一个缓动和事件类型获取一个缓动求值器
|
|
589
|
+
* @param easing 缓动对象
|
|
590
|
+
* @param type 事件值类型
|
|
591
|
+
* @param interpreteAs 文本解释方式
|
|
592
|
+
* @returns 对应类型的缓动求值器
|
|
593
|
+
*/
|
|
594
|
+
getEasedEvaluator<T extends EventValueESType>(easing: Easing, type: EventValueTypeOfType<T>, interpreteAs?: InterpreteAs): EasedEvaluatorOfType<T> {
|
|
595
|
+
const easingIsNormal = easing instanceof NormalEasing;
|
|
596
|
+
switch (type) {
|
|
597
|
+
case EventValueType.numeric:
|
|
598
|
+
return easingIsNormal
|
|
599
|
+
? NumericEasedEvaluator.evaluatorsOfNormalEasing[easing.rpeId] as EasedEvaluatorOfType<T>
|
|
600
|
+
: new NumericEasedEvaluator(easing) as EasedEvaluatorOfType<T>;
|
|
601
|
+
case EventValueType.color:
|
|
602
|
+
return easingIsNormal
|
|
603
|
+
? ColorEasedEvaluator.evaluatorsOfNormalEasing[easing.rpeId] as EasedEvaluatorOfType<T>
|
|
604
|
+
: new ColorEasedEvaluator(easing) as EasedEvaluatorOfType<T>;
|
|
605
|
+
case EventValueType.text:
|
|
606
|
+
return easingIsNormal
|
|
607
|
+
? TextEasedEvaluator.evaluatorsOfNoEzAndItpAs[easing.rpeId][interpreteAs] as EasedEvaluatorOfType<T>
|
|
608
|
+
: new TextEasedEvaluator(easing, interpreteAs) as EasedEvaluatorOfType<T>
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* 根据JavaScript表达式创建表达式求值器
|
|
614
|
+
* @param data 表达式求值器数据
|
|
615
|
+
* @returns 创建的表达式求值器对象
|
|
616
|
+
*/
|
|
427
617
|
createExpressionEvaluator<T extends EventValueESType>(data: ExpressionEvaluatorDataKPA2) {
|
|
428
618
|
return new ExpressionEvaluator<T>(data.jsExpr);
|
|
429
619
|
}
|
|
620
|
+
|
|
621
|
+
bindMacroEvaluator(node: EventStartNode<EventValueESType>, data: MacroEvaluatorDataKPA2, pos: string) {
|
|
622
|
+
const key = data.name;
|
|
623
|
+
if (!key) {
|
|
624
|
+
throw err.MISSING_MACRO_EVALUATOR_KEY(pos);
|
|
625
|
+
}
|
|
626
|
+
const evaluator = this.macroLib.macroEvaluators.get(key);
|
|
627
|
+
if (!evaluator) {
|
|
628
|
+
throw err.MACRO_EVALUATOR_NOT_FOUND(key, pos);
|
|
629
|
+
}
|
|
630
|
+
node.evaluator = evaluator;
|
|
631
|
+
evaluator.consumers.set(node, new ExpressionEvaluator(data.compiled || "0"));
|
|
632
|
+
}
|
|
633
|
+
|
|
430
634
|
/**
|
|
431
635
|
* 使用KPA2JSON创建一对面对面节点
|
|
432
|
-
* @param data
|
|
433
|
-
* @param type
|
|
434
|
-
* @returns
|
|
636
|
+
* @param data 事件数据
|
|
637
|
+
* @param type 事件值类型
|
|
638
|
+
* @returns 包含起始节点和结束节点的元组
|
|
435
639
|
*/
|
|
436
|
-
createEventFromData<VT extends EventValueESType>(data: EventDataKPA2<VT>, type: EventValueTypeOfType<VT
|
|
640
|
+
createEventFromData<VT extends EventValueESType>(data: EventDataKPA2<VT>, type: EventValueTypeOfType<VT>, pos: string): [EventStartNode<VT>, EventEndNode<VT>] {
|
|
437
641
|
const start = new EventStartNode(data.startTime, data.start);
|
|
438
642
|
const end = new EventEndNode(data.endTime, data.end);
|
|
439
|
-
|
|
643
|
+
this.bindEvaluator(start, data.evaluator, type, pos);
|
|
644
|
+
if (data.macroStart) {
|
|
645
|
+
this.bindValueMacro(start, data.macroStart, pos)
|
|
646
|
+
}
|
|
647
|
+
if (data.macroEnd) {
|
|
648
|
+
this.bindValueMacro(end, data.macroEnd, pos)
|
|
649
|
+
}
|
|
650
|
+
if (data.macroStartTime) {
|
|
651
|
+
this.bindTimeMacro(start, data.macroStartTime, pos)
|
|
652
|
+
}
|
|
653
|
+
if (data.startLinkedMacro) {
|
|
654
|
+
for (const macroLink of data.startLinkedMacro) {
|
|
655
|
+
this.linkMacro(start, macroLink, pos)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (data.endLinkedMacro) {
|
|
659
|
+
for (const macroLink of data.endLinkedMacro) {
|
|
660
|
+
this.linkMacro(end, macroLink, pos)
|
|
661
|
+
}
|
|
662
|
+
}
|
|
440
663
|
EventNode.connect(start, end);
|
|
441
664
|
return [start, end];
|
|
442
665
|
}
|
|
666
|
+
createFinalEventStartNodeFromData<VT extends EventValueESType>(data: FinalEventStartNodeDataKPA2<VT>, type: EventValueTypeOfType<VT>, pos: string): EventStartNode<VT> {
|
|
667
|
+
const node = new EventStartNode(data.startTime, data.start);
|
|
668
|
+
this.bindEvaluator(node, data.evaluator, type, pos);
|
|
669
|
+
if (typeof data.macro === "string") {
|
|
670
|
+
this.bindValueMacro(node, data.macro, pos);
|
|
671
|
+
}
|
|
672
|
+
if (typeof data.macroTime === "string") {
|
|
673
|
+
this.bindTimeMacro(node, data.macroTime, pos);
|
|
674
|
+
}
|
|
675
|
+
if (data.linkedMacro) {
|
|
676
|
+
for (const macroLink of data.linkedMacro) {
|
|
677
|
+
this.linkMacro(node, macroLink, pos)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return node;
|
|
681
|
+
}
|
|
682
|
+
bindTimeMacro(node: EventStartNode<any>, macroData: MacroData, pos: string) {
|
|
683
|
+
const id = macroData[0];
|
|
684
|
+
const macro = this.macroLib.timeMacros.get(id);
|
|
685
|
+
if (typeof macro === "object" && macro instanceof EventMacroTime) {
|
|
686
|
+
node.macroTime = macro;
|
|
687
|
+
macro.bindNode(node, macroData, pos);
|
|
688
|
+
} else {
|
|
689
|
+
err.TIME_MACRO_NOT_FOUND(id, pos).warn();
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
bindValueMacro(node: EventNode<any>, macroData: MacroData, pos: string) {
|
|
694
|
+
const id = macroData[0];
|
|
695
|
+
const macro = this.macroLib.valueMacros.get(id);
|
|
696
|
+
if (typeof macro === "object" && macro instanceof EventMacroValue) {
|
|
697
|
+
node.macroValue = macro;
|
|
698
|
+
macro.bindNode(node, macroData, pos);
|
|
699
|
+
} else {
|
|
700
|
+
err.VALUE_MACRO_NOT_FOUND(id, pos).warn();
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
linkMacro(node: EventNode<EventValueESType>, linkData: MacroLink, pos: string) {
|
|
705
|
+
const prefix = linkData[0].split(":")[0] as 'time' | 'value';
|
|
706
|
+
const macroDict = prefix === 'time' ? this.macroLib.timeMacros : this.macroLib.valueMacros;
|
|
707
|
+
const realName = linkData[0].substring(prefix.length + 1);
|
|
708
|
+
const macro = macroDict.get(realName);
|
|
709
|
+
if (typeof macro === "object" && macro instanceof EventMacro) {
|
|
710
|
+
macro.linkProtoNode(node, linkData[1]);
|
|
711
|
+
} else {
|
|
712
|
+
err[`${prefix.toUpperCase() as 'TIME' | 'VALUE'}_MACRO_NOT_FOUND`](realName, pos).warn();
|
|
713
|
+
}
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
/* 暂时不用此方法,因为谱面播放器里面还是用反解法弄的
|
|
717
|
+
updateNNListsFromENS(speedENS: SpeedENS) {
|
|
718
|
+
|
|
719
|
+
}
|
|
720
|
+
*/
|
|
443
721
|
}
|
|
444
722
|
|
|
723
|
+
/**
|
|
724
|
+
* 表示一组判定线的容器
|
|
725
|
+
*
|
|
726
|
+
* 用于组织和管理具有相同属性或用途的判定线集合
|
|
727
|
+
*/
|
|
445
728
|
export class JudgeLineGroup {
|
|
446
|
-
|
|
729
|
+
/**
|
|
730
|
+
* 该只读标记只是为了防止外部修改,内部可以修改
|
|
731
|
+
*
|
|
732
|
+
* 属于该组的判定线列表,按ID升序排列
|
|
733
|
+
*/
|
|
734
|
+
judgeLines: readonly JudgeLine[];
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* 创建一个新的判定线组
|
|
738
|
+
* @param name 组名称
|
|
739
|
+
*/
|
|
447
740
|
constructor(public name: string) {
|
|
448
741
|
this.judgeLines = []
|
|
449
742
|
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* 向判定线组添加一条判定线,并且保证其内部的判定线ID是升序排列的。
|
|
746
|
+
* @param judgeLine 要添加的判定线
|
|
747
|
+
* @returns
|
|
748
|
+
*/
|
|
450
749
|
add(judgeLine: JudgeLine) {
|
|
451
750
|
// 加入之前已经按照ID升序排列
|
|
452
751
|
// 加入时将新判定线插入到正确位置
|
|
752
|
+
const judgeLines = this.judgeLines as JudgeLine[];
|
|
453
753
|
if (judgeLine.group) {
|
|
454
754
|
judgeLine.group.remove(judgeLine);
|
|
455
755
|
}
|
|
456
756
|
judgeLine.group = this;
|
|
457
757
|
|
|
458
758
|
// 找到正确的位置插入,保持按ID升序排列
|
|
459
|
-
for (let i = 0; i <
|
|
460
|
-
if (
|
|
461
|
-
|
|
759
|
+
for (let i = 0; i < judgeLines.length; i++) {
|
|
760
|
+
if (judgeLines[i].id > judgeLine.id) {
|
|
761
|
+
judgeLines.splice(i, 0, judgeLine);
|
|
462
762
|
return;
|
|
463
763
|
}
|
|
464
764
|
}
|
|
465
765
|
// 如果没有找到比它大的ID,则插入到末尾
|
|
466
|
-
|
|
766
|
+
judgeLines.push(judgeLine);
|
|
467
767
|
|
|
468
768
|
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* 从判定线组移除一条判定线
|
|
772
|
+
* @param judgeLine 要移除的判定线
|
|
773
|
+
*/
|
|
469
774
|
remove(judgeLine: JudgeLine) {
|
|
470
|
-
|
|
775
|
+
// 只读仅对外部作限制
|
|
776
|
+
const judgeLines = this.judgeLines as JudgeLine[];
|
|
777
|
+
const index = judgeLines.indexOf(judgeLine);
|
|
471
778
|
if (index !== -1) {
|
|
472
|
-
|
|
779
|
+
judgeLines.splice(index, 1);
|
|
473
780
|
}
|
|
474
781
|
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* 检查该判定线组是否为默认组
|
|
785
|
+
* @returns 该判定线组是否为默认判定线组,默认的判断标准是:名称为 "Default"(大小写不敏感)
|
|
786
|
+
*/
|
|
475
787
|
isDefault() {
|
|
476
788
|
return this.name.toLowerCase() === "default";
|
|
477
789
|
}
|