sonolus-next-rush-engine 0.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/LICENSE.txt +21 -0
- package/README.md +38 -0
- package/dist/EngineConfiguration +0 -0
- package/dist/EnginePlayData +0 -0
- package/dist/EnginePreviewData +0 -0
- package/dist/EngineRom +0 -0
- package/dist/EngineTutorialData +0 -0
- package/dist/EngineWatchData +0 -0
- package/dist/LevelData/analyze.d.ts +3 -0
- package/dist/LevelData/analyze.js +7 -0
- package/dist/extended/analyze.d.ts +3 -0
- package/dist/extended/analyze.js +8 -0
- package/dist/extended/convert.d.ts +12 -0
- package/dist/extended/convert.js +453 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +84 -0
- package/dist/lib/binaryseeker/mod.d.ts +14 -0
- package/dist/lib/binaryseeker/mod.js +9 -0
- package/dist/lib/binaryseeker/reader.d.ts +157 -0
- package/dist/lib/binaryseeker/reader.js +305 -0
- package/dist/lib/binaryseeker/writer.d.ts +171 -0
- package/dist/lib/binaryseeker/writer.js +360 -0
- package/dist/mmw/analyze.d.ts +109 -0
- package/dist/mmw/analyze.js +256 -0
- package/dist/mmw/convert.d.ts +14 -0
- package/dist/mmw/convert.js +681 -0
- package/dist/sus/analyze.d.ts +32 -0
- package/dist/sus/analyze.js +287 -0
- package/dist/sus/convert.d.ts +5 -0
- package/dist/sus/convert.js +320 -0
- package/dist/thumbnail.png +0 -0
- package/dist/usc/analyze.d.ts +3 -0
- package/dist/usc/analyze.js +7 -0
- package/dist/usc/convert.d.ts +4 -0
- package/dist/usc/convert.js +496 -0
- package/dist/usc/index.d.ts +112 -0
- package/dist/usc/index.js +21 -0
- package/package.json +47 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
const SONOLUS_DIRECTIONS = {
|
|
2
|
+
left: 1,
|
|
3
|
+
up: 0,
|
|
4
|
+
right: 2,
|
|
5
|
+
};
|
|
6
|
+
const SONOLUS_CONNECTOR_EASES = {
|
|
7
|
+
outin: 5,
|
|
8
|
+
out: 3,
|
|
9
|
+
linear: 1,
|
|
10
|
+
in: 2,
|
|
11
|
+
inout: 4,
|
|
12
|
+
};
|
|
13
|
+
const mapUscEaseToSonolusEase = (ease) => {
|
|
14
|
+
const uscEase = ease ?? 'linear';
|
|
15
|
+
return uscEase;
|
|
16
|
+
};
|
|
17
|
+
const SONOLUS_GUIDE_COLORS = {
|
|
18
|
+
neutral: 101,
|
|
19
|
+
red: 102,
|
|
20
|
+
green: 103,
|
|
21
|
+
blue: 104,
|
|
22
|
+
yellow: 105,
|
|
23
|
+
purple: 106,
|
|
24
|
+
cyan: 107,
|
|
25
|
+
black: 108,
|
|
26
|
+
};
|
|
27
|
+
const EPSILON = 1e-6;
|
|
28
|
+
/** Convert a USC to a Level Data */
|
|
29
|
+
export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuideLayer = false) => {
|
|
30
|
+
const allUscObjects = usc.objects ?? [];
|
|
31
|
+
const allIntermediateEntities = [];
|
|
32
|
+
const simLineEligibleNotes = [];
|
|
33
|
+
const timeScaleGroupIntermediates = [];
|
|
34
|
+
const createIntermediate = (archetype, data, isSimEligible = false) => {
|
|
35
|
+
const intermediateEntity = { archetype, data };
|
|
36
|
+
allIntermediateEntities.push(intermediateEntity);
|
|
37
|
+
if (isSimEligible) {
|
|
38
|
+
simLineEligibleNotes.push(intermediateEntity);
|
|
39
|
+
}
|
|
40
|
+
return intermediateEntity;
|
|
41
|
+
};
|
|
42
|
+
createIntermediate('Initialization', {});
|
|
43
|
+
const bpmChanges = allUscObjects.filter((o) => o.type === 'bpm');
|
|
44
|
+
const timeScaleGroups = allUscObjects.filter((o) => o.type === 'timeScaleGroup');
|
|
45
|
+
const singleNotes = allUscObjects.filter((o) => o.type === 'single');
|
|
46
|
+
const damageNotes = allUscObjects.filter((o) => o.type === 'damage');
|
|
47
|
+
const slideNotes = allUscObjects.filter((o) => o.type === 'slide' || (o.type === 'guide' && !('midpoints' in o)));
|
|
48
|
+
const guideNotes = allUscObjects.filter((o) => o.type === 'guide' && 'midpoints' in o);
|
|
49
|
+
const skills = allUscObjects.filter((o) => o.type === 'skill');
|
|
50
|
+
const feverChance = allUscObjects
|
|
51
|
+
.filter((o) => o.type === 'feverChance')
|
|
52
|
+
.sort((a, b) => a.beat - b.beat)
|
|
53
|
+
.slice(0, 1);
|
|
54
|
+
const feverStart = allUscObjects
|
|
55
|
+
.filter((o) => o.type === 'feverStart')
|
|
56
|
+
.sort((a, b) => a.beat - b.beat)
|
|
57
|
+
.slice(0, 1);
|
|
58
|
+
if (bpmChanges.length === 0) {
|
|
59
|
+
bpmChanges.push({ type: 'bpm', beat: 0, bpm: 160 });
|
|
60
|
+
}
|
|
61
|
+
for (const bpmChange of bpmChanges) {
|
|
62
|
+
createIntermediate('#BPM_CHANGE', {
|
|
63
|
+
'#BEAT': bpmChange.beat,
|
|
64
|
+
'#BPM': bpmChange.bpm,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (timeScaleGroups.length === 0) {
|
|
68
|
+
timeScaleGroups.push({
|
|
69
|
+
type: 'timeScaleGroup',
|
|
70
|
+
changes: [{ beat: 0, timeScale: 1 }],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const skill of skills) {
|
|
74
|
+
createIntermediate('Skill', {
|
|
75
|
+
'#BEAT': skill.beat,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const fc of feverChance) {
|
|
79
|
+
createIntermediate('FeverChance', {
|
|
80
|
+
'#BEAT': fc.beat,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
for (const fs of feverStart) {
|
|
84
|
+
createIntermediate('FeverStart', {
|
|
85
|
+
'#BEAT': fs.beat,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const timeScaleGroup of timeScaleGroups) {
|
|
89
|
+
const groupIntermediateEntity = createIntermediate('#TIMESCALE_GROUP', {});
|
|
90
|
+
timeScaleGroupIntermediates.push(groupIntermediateEntity);
|
|
91
|
+
let lastChangeIntermediate = null;
|
|
92
|
+
const changes = [...timeScaleGroup.changes].sort((a, b) => a.beat - b.beat);
|
|
93
|
+
for (const timeScaleChange of changes) {
|
|
94
|
+
const newChangeIntermediate = createIntermediate('#TIMESCALE_CHANGE', {
|
|
95
|
+
'#BEAT': timeScaleChange.beat,
|
|
96
|
+
'#TIMESCALE': timeScaleChange.timeScale === 0 ? 0.000001 : timeScaleChange.timeScale,
|
|
97
|
+
'#TIMESCALE_SKIP': 0,
|
|
98
|
+
'#TIMESCALE_GROUP': groupIntermediateEntity,
|
|
99
|
+
'#TIMESCALE_EASE': 0,
|
|
100
|
+
});
|
|
101
|
+
if (lastChangeIntermediate === null) {
|
|
102
|
+
groupIntermediateEntity.data.first = newChangeIntermediate;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
lastChangeIntermediate.data.next = newChangeIntermediate;
|
|
106
|
+
}
|
|
107
|
+
lastChangeIntermediate = newChangeIntermediate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const singleNote of singleNotes) {
|
|
111
|
+
const name_parts = [];
|
|
112
|
+
name_parts.push(singleNote.critical ? 'Critical' : 'Normal');
|
|
113
|
+
if (singleNote.direction === undefined || singleNote.direction === null) {
|
|
114
|
+
name_parts.push(singleNote.trace ? 'Trace' : 'Tap');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
name_parts.push(singleNote.trace ? 'TraceFlick' : 'Flick');
|
|
118
|
+
}
|
|
119
|
+
name_parts.push('Note');
|
|
120
|
+
const archetype = name_parts.join('');
|
|
121
|
+
const timeScaleGroupRef = timeScaleGroupIntermediates[singleNote.timeScaleGroup ?? 0];
|
|
122
|
+
let sonolusDirName;
|
|
123
|
+
if (singleNote.direction === undefined || singleNote.direction === null) {
|
|
124
|
+
sonolusDirName = 'up';
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
sonolusDirName = singleNote.direction;
|
|
128
|
+
}
|
|
129
|
+
const data = {
|
|
130
|
+
'#BEAT': singleNote.beat,
|
|
131
|
+
lane: singleNote.lane,
|
|
132
|
+
size: singleNote.size,
|
|
133
|
+
isAttached: 0,
|
|
134
|
+
connectorEase: SONOLUS_CONNECTOR_EASES.linear,
|
|
135
|
+
isSeparator: 0,
|
|
136
|
+
segmentKind: singleNote.critical ? 2 : 1,
|
|
137
|
+
segmentAlpha: 1,
|
|
138
|
+
'#TIMESCALE_GROUP': timeScaleGroupRef,
|
|
139
|
+
};
|
|
140
|
+
const directionValue = SONOLUS_DIRECTIONS[sonolusDirName];
|
|
141
|
+
if (directionValue !== undefined) {
|
|
142
|
+
data.direction = directionValue;
|
|
143
|
+
}
|
|
144
|
+
createIntermediate(archetype, data, true);
|
|
145
|
+
}
|
|
146
|
+
for (const damageNote of damageNotes) {
|
|
147
|
+
const archetype = 'DamageNote';
|
|
148
|
+
const timeScaleGroupRef = timeScaleGroupIntermediates[damageNote.timeScaleGroup ?? 0];
|
|
149
|
+
createIntermediate(archetype, {
|
|
150
|
+
'#BEAT': damageNote.beat,
|
|
151
|
+
lane: damageNote.lane,
|
|
152
|
+
size: damageNote.size,
|
|
153
|
+
direction: SONOLUS_DIRECTIONS.up,
|
|
154
|
+
isAttached: 0,
|
|
155
|
+
connectorEase: SONOLUS_CONNECTOR_EASES.linear,
|
|
156
|
+
isSeparator: 0,
|
|
157
|
+
segmentKind: 1,
|
|
158
|
+
segmentAlpha: 1,
|
|
159
|
+
'#TIMESCALE_GROUP': timeScaleGroupRef,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
for (const slideNote of slideNotes) {
|
|
163
|
+
let prevJointIntermediate = null;
|
|
164
|
+
let prevNoteIntermediate = null;
|
|
165
|
+
let headNoteIntermediate = null;
|
|
166
|
+
let currentSegmentHead = null;
|
|
167
|
+
const queuedAttachIntermediates = [];
|
|
168
|
+
const connectors = [];
|
|
169
|
+
const pendingSegmentConnectors = [];
|
|
170
|
+
const connections = [...slideNote.connections].sort((a, b) => a.beat - b.beat);
|
|
171
|
+
if (connections.length === 0)
|
|
172
|
+
continue;
|
|
173
|
+
const stepSize = Math.max(1, connections.length - 1);
|
|
174
|
+
let nextHiddenTickBeat = Math.floor(connections[0].beat * 2 + 1) / 2;
|
|
175
|
+
let stepIdx = 0;
|
|
176
|
+
for (const connectionNote of connections) {
|
|
177
|
+
let isSimLineEligible = false;
|
|
178
|
+
let isAttached = false;
|
|
179
|
+
const name_parts = [];
|
|
180
|
+
switch (connectionNote.type) {
|
|
181
|
+
case 'start':
|
|
182
|
+
if (connectionNote.judgeType === 'none') {
|
|
183
|
+
name_parts.push('Anchor');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
name_parts.push(connectionNote.critical ? 'Critical' : 'Normal');
|
|
187
|
+
name_parts.push('Head');
|
|
188
|
+
if (connectionNote.judgeType === 'trace')
|
|
189
|
+
name_parts.push('Trace');
|
|
190
|
+
else if (connectionNote.judgeType === 'normal')
|
|
191
|
+
name_parts.push('Tap');
|
|
192
|
+
isSimLineEligible = true;
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case 'end':
|
|
196
|
+
if (connectionNote.judgeType === 'none') {
|
|
197
|
+
name_parts.push('Anchor');
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
name_parts.push(connectionNote.critical ? 'Critical' : 'Normal');
|
|
201
|
+
name_parts.push('Tail');
|
|
202
|
+
if (connectionNote.direction === undefined ||
|
|
203
|
+
connectionNote.direction === null) {
|
|
204
|
+
if (connectionNote.judgeType === 'trace')
|
|
205
|
+
name_parts.push('Trace');
|
|
206
|
+
else if (connectionNote.judgeType === 'normal')
|
|
207
|
+
name_parts.push('Release');
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
if (connectionNote.judgeType === 'trace')
|
|
211
|
+
name_parts.push('TraceFlick');
|
|
212
|
+
else if (connectionNote.judgeType === 'normal')
|
|
213
|
+
name_parts.push('Flick');
|
|
214
|
+
}
|
|
215
|
+
isSimLineEligible = true;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'tick':
|
|
219
|
+
if (connectionNote.critical !== undefined) {
|
|
220
|
+
name_parts.push(connectionNote.critical ? 'Critical' : 'Normal');
|
|
221
|
+
if (connectionNote.judgeType === 'trace')
|
|
222
|
+
name_parts.push('Trace');
|
|
223
|
+
else
|
|
224
|
+
name_parts.push('Tick');
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
name_parts.push('Anchor');
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case 'attach':
|
|
231
|
+
isAttached = true;
|
|
232
|
+
if (connectionNote.critical !== undefined) {
|
|
233
|
+
name_parts.push(connectionNote.critical ? 'Critical' : 'Normal');
|
|
234
|
+
name_parts.push('Tick');
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
name_parts.push('TransientHiddenTick');
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
name_parts.push('Note');
|
|
242
|
+
const archetype = name_parts.join('');
|
|
243
|
+
const timeScaleGroupRef = timeScaleGroupIntermediates[connectionNote.timeScaleGroup ?? 0];
|
|
244
|
+
let sonolusDirName;
|
|
245
|
+
const direction = connectionNote.direction;
|
|
246
|
+
if (direction === undefined || direction === null) {
|
|
247
|
+
sonolusDirName = 'up';
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
sonolusDirName = direction;
|
|
251
|
+
}
|
|
252
|
+
const sonolusEaseValue = SONOLUS_CONNECTOR_EASES[mapUscEaseToSonolusEase('ease' in connectionNote
|
|
253
|
+
? connectionNote.ease
|
|
254
|
+
: undefined)];
|
|
255
|
+
let segmentAlpha = 1;
|
|
256
|
+
if (slideNote.type === 'guide') {
|
|
257
|
+
// 공식: 1 - (0.8 * (현재단계 / 전체구간)) -> 1에서 0.2까지 감소
|
|
258
|
+
segmentAlpha = 1 - 0.8 * (stepIdx / stepSize);
|
|
259
|
+
}
|
|
260
|
+
const isSeparatorValue = slideNote.type == 'slide' ? 0 : 1;
|
|
261
|
+
const data = {
|
|
262
|
+
'#BEAT': connectionNote.beat,
|
|
263
|
+
lane: 'lane' in connectionNote ? connectionNote.lane : 0,
|
|
264
|
+
size: 'size' in connectionNote ? connectionNote.size : 0,
|
|
265
|
+
isAttached: isAttached ? 1 : 0,
|
|
266
|
+
connectorEase: sonolusEaseValue,
|
|
267
|
+
isSeparator: isSeparatorValue,
|
|
268
|
+
segmentKind: slideNote.type == 'slide'
|
|
269
|
+
? slideNote.critical
|
|
270
|
+
? 2
|
|
271
|
+
: 1
|
|
272
|
+
: slideNote.critical
|
|
273
|
+
? 105
|
|
274
|
+
: 103,
|
|
275
|
+
segmentAlpha: segmentAlpha,
|
|
276
|
+
segmentLayer: slideNote.type == 'slide' ? 0 : 1,
|
|
277
|
+
'#TIMESCALE_GROUP': timeScaleGroupRef,
|
|
278
|
+
};
|
|
279
|
+
const directionValue = SONOLUS_DIRECTIONS[sonolusDirName];
|
|
280
|
+
if (directionValue !== undefined) {
|
|
281
|
+
data.direction = directionValue;
|
|
282
|
+
}
|
|
283
|
+
const connectionIntermediate = createIntermediate(archetype, data, isSimLineEligible);
|
|
284
|
+
if (headNoteIntermediate === null) {
|
|
285
|
+
headNoteIntermediate = connectionIntermediate;
|
|
286
|
+
}
|
|
287
|
+
if (currentSegmentHead === null) {
|
|
288
|
+
currentSegmentHead = connectionIntermediate;
|
|
289
|
+
}
|
|
290
|
+
connectionIntermediate.data.activeHead = headNoteIntermediate;
|
|
291
|
+
if (isAttached) {
|
|
292
|
+
queuedAttachIntermediates.push(connectionIntermediate);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (prevJointIntermediate !== null) {
|
|
296
|
+
for (const attachIntermediate of queuedAttachIntermediates) {
|
|
297
|
+
attachIntermediate.data.attachHead = prevJointIntermediate;
|
|
298
|
+
attachIntermediate.data.attachTail = connectionIntermediate;
|
|
299
|
+
}
|
|
300
|
+
queuedAttachIntermediates.length = 0;
|
|
301
|
+
while (slideNote.type == 'slide' &&
|
|
302
|
+
nextHiddenTickBeat + EPSILON <
|
|
303
|
+
connectionIntermediate.data['#BEAT']) {
|
|
304
|
+
createIntermediate('TransientHiddenTickNote', {
|
|
305
|
+
'#BEAT': nextHiddenTickBeat,
|
|
306
|
+
'#TIMESCALE_GROUP': timeScaleGroupIntermediates[0],
|
|
307
|
+
lane: connectionIntermediate.data.lane,
|
|
308
|
+
size: connectionIntermediate.data.size,
|
|
309
|
+
direction: SONOLUS_DIRECTIONS.up,
|
|
310
|
+
isAttached: 1,
|
|
311
|
+
connectorEase: SONOLUS_CONNECTOR_EASES.linear,
|
|
312
|
+
isSeparator: 0,
|
|
313
|
+
segmentKind: 1,
|
|
314
|
+
segmentAlpha: 0,
|
|
315
|
+
activeHead: headNoteIntermediate,
|
|
316
|
+
attachHead: prevJointIntermediate,
|
|
317
|
+
attachTail: connectionIntermediate,
|
|
318
|
+
});
|
|
319
|
+
nextHiddenTickBeat += 0.5;
|
|
320
|
+
}
|
|
321
|
+
const connectorIntermediate = createIntermediate('Connector', {
|
|
322
|
+
head: prevJointIntermediate,
|
|
323
|
+
tail: connectionIntermediate,
|
|
324
|
+
});
|
|
325
|
+
connectors.push(connectorIntermediate);
|
|
326
|
+
pendingSegmentConnectors.push(connectorIntermediate);
|
|
327
|
+
}
|
|
328
|
+
prevJointIntermediate = connectionIntermediate;
|
|
329
|
+
}
|
|
330
|
+
if (isSeparatorValue === 1) {
|
|
331
|
+
for (const conn of pendingSegmentConnectors) {
|
|
332
|
+
conn.data.segmentHead = currentSegmentHead;
|
|
333
|
+
conn.data.segmentTail = connectionIntermediate;
|
|
334
|
+
}
|
|
335
|
+
pendingSegmentConnectors.length = 0;
|
|
336
|
+
currentSegmentHead = connectionIntermediate;
|
|
337
|
+
}
|
|
338
|
+
if (prevNoteIntermediate !== null) {
|
|
339
|
+
prevNoteIntermediate.data.next = connectionIntermediate;
|
|
340
|
+
}
|
|
341
|
+
prevNoteIntermediate = connectionIntermediate;
|
|
342
|
+
stepIdx++;
|
|
343
|
+
}
|
|
344
|
+
if (!headNoteIntermediate || !prevJointIntermediate) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (currentSegmentHead) {
|
|
348
|
+
for (const conn of pendingSegmentConnectors) {
|
|
349
|
+
conn.data.segmentHead = currentSegmentHead;
|
|
350
|
+
conn.data.segmentTail = prevJointIntermediate;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (slideNote.type === 'slide') {
|
|
354
|
+
for (const connectorIntermediate of connectors) {
|
|
355
|
+
connectorIntermediate.data.activeHead = headNoteIntermediate;
|
|
356
|
+
connectorIntermediate.data.activeTail = prevJointIntermediate;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const guideNote of guideNotes) {
|
|
361
|
+
const connections = [...guideNote.midpoints].sort((a, b) => a.beat - b.beat);
|
|
362
|
+
let prevMidpointIntermediate = null;
|
|
363
|
+
let headMidpointIntermediate = null;
|
|
364
|
+
const guideConnectors = [];
|
|
365
|
+
const stepSize = Math.max(1, connections.length - 1);
|
|
366
|
+
let stepIdx = 0;
|
|
367
|
+
for (const midpointNote of connections) {
|
|
368
|
+
const timeScaleGroupRef = timeScaleGroupIntermediates[midpointNote.timeScaleGroup ?? 0];
|
|
369
|
+
let segmentAlpha = 1;
|
|
370
|
+
let isSeparator = 0;
|
|
371
|
+
if (smoothGuideFade) {
|
|
372
|
+
isSeparator = 1;
|
|
373
|
+
if (guideNote.fade === 'out') {
|
|
374
|
+
segmentAlpha = 1 - 0.8 * (stepIdx / stepSize);
|
|
375
|
+
}
|
|
376
|
+
else if (guideNote.fade === 'in') {
|
|
377
|
+
segmentAlpha = 1 - 0.8 * ((stepSize - stepIdx) / stepSize);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const midpointIntermediate = createIntermediate('AnchorNote', {
|
|
381
|
+
'#BEAT': midpointNote.beat,
|
|
382
|
+
lane: midpointNote.lane,
|
|
383
|
+
size: midpointNote.size,
|
|
384
|
+
direction: SONOLUS_DIRECTIONS.up,
|
|
385
|
+
isAttached: 0,
|
|
386
|
+
connectorEase: SONOLUS_CONNECTOR_EASES[mapUscEaseToSonolusEase(midpointNote.ease)],
|
|
387
|
+
isSeparator: isSeparator,
|
|
388
|
+
segmentKind: SONOLUS_GUIDE_COLORS[guideNote.color],
|
|
389
|
+
segmentAlpha: segmentAlpha,
|
|
390
|
+
segmentLayer: useGuideLayer ? 1 : 0,
|
|
391
|
+
'#TIMESCALE_GROUP': timeScaleGroupRef,
|
|
392
|
+
});
|
|
393
|
+
if (headMidpointIntermediate === null) {
|
|
394
|
+
headMidpointIntermediate = midpointIntermediate;
|
|
395
|
+
}
|
|
396
|
+
if (prevMidpointIntermediate !== null) {
|
|
397
|
+
const connectorIntermediate = createIntermediate('Connector', {
|
|
398
|
+
head: prevMidpointIntermediate,
|
|
399
|
+
tail: midpointIntermediate,
|
|
400
|
+
});
|
|
401
|
+
guideConnectors.push(connectorIntermediate);
|
|
402
|
+
prevMidpointIntermediate.data.next = midpointIntermediate;
|
|
403
|
+
}
|
|
404
|
+
prevMidpointIntermediate = midpointIntermediate;
|
|
405
|
+
stepIdx++;
|
|
406
|
+
}
|
|
407
|
+
if (!headMidpointIntermediate || !prevMidpointIntermediate) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
for (const connectorIntermediate of guideConnectors) {
|
|
411
|
+
connectorIntermediate.data.segmentHead = headMidpointIntermediate;
|
|
412
|
+
connectorIntermediate.data.segmentTail = prevMidpointIntermediate;
|
|
413
|
+
}
|
|
414
|
+
if (!smoothGuideFade) {
|
|
415
|
+
switch (guideNote.fade) {
|
|
416
|
+
case 'in':
|
|
417
|
+
if (headMidpointIntermediate) {
|
|
418
|
+
headMidpointIntermediate.data.segmentAlpha = 0;
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
case 'out':
|
|
422
|
+
if (prevMidpointIntermediate) {
|
|
423
|
+
prevMidpointIntermediate.data.segmentAlpha = 0;
|
|
424
|
+
}
|
|
425
|
+
break;
|
|
426
|
+
case 'none':
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
simLineEligibleNotes.sort((noteA, noteB) => {
|
|
432
|
+
const beatA = noteA.data['#BEAT'];
|
|
433
|
+
const beatB = noteB.data['#BEAT'];
|
|
434
|
+
if (beatA !== beatB)
|
|
435
|
+
return beatA - beatB;
|
|
436
|
+
const laneA = noteA.data.lane;
|
|
437
|
+
const laneB = noteB.data.lane;
|
|
438
|
+
return laneA - laneB;
|
|
439
|
+
});
|
|
440
|
+
const simGroups = [];
|
|
441
|
+
let currentSimGroup = [];
|
|
442
|
+
for (const simNote of simLineEligibleNotes) {
|
|
443
|
+
if (currentSimGroup.length === 0 ||
|
|
444
|
+
Math.abs(simNote.data['#BEAT'] - currentSimGroup[0].data['#BEAT']) < 1e-2) {
|
|
445
|
+
currentSimGroup.push(simNote);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
simGroups.push(currentSimGroup);
|
|
449
|
+
currentSimGroup = [simNote];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (currentSimGroup.length > 0) {
|
|
453
|
+
simGroups.push(currentSimGroup);
|
|
454
|
+
}
|
|
455
|
+
for (const simGroup of simGroups) {
|
|
456
|
+
for (let i = 0; i < simGroup.length - 1; i++) {
|
|
457
|
+
const leftNote = simGroup[i];
|
|
458
|
+
const rightNote = simGroup[i + 1];
|
|
459
|
+
createIntermediate('SimLine', {
|
|
460
|
+
left: leftNote,
|
|
461
|
+
right: rightNote,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const entities = [];
|
|
466
|
+
const intermediateToRef = new Map();
|
|
467
|
+
let entityRefCounter = 0;
|
|
468
|
+
const getRef = (intermediateEntity) => {
|
|
469
|
+
let ref = intermediateToRef.get(intermediateEntity);
|
|
470
|
+
if (ref)
|
|
471
|
+
return ref;
|
|
472
|
+
ref = (entityRefCounter++).toString(16);
|
|
473
|
+
intermediateToRef.set(intermediateEntity, ref);
|
|
474
|
+
return ref;
|
|
475
|
+
};
|
|
476
|
+
for (const intermediateEntity of allIntermediateEntities) {
|
|
477
|
+
const entity = {
|
|
478
|
+
archetype: intermediateEntity.archetype,
|
|
479
|
+
name: getRef(intermediateEntity),
|
|
480
|
+
data: [],
|
|
481
|
+
};
|
|
482
|
+
for (const [dataName, dataValue] of Object.entries(intermediateEntity.data)) {
|
|
483
|
+
if (typeof dataValue === 'number') {
|
|
484
|
+
entity.data.push({ name: dataName, value: dataValue });
|
|
485
|
+
}
|
|
486
|
+
else if (dataValue !== undefined) {
|
|
487
|
+
entity.data.push({ name: dataName, ref: getRef(dataValue) });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
entities.push(entity);
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
bgmOffset: usc.offset + offset,
|
|
494
|
+
entities,
|
|
495
|
+
};
|
|
496
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export interface USC {
|
|
2
|
+
offset: number;
|
|
3
|
+
objects: USCObject[];
|
|
4
|
+
}
|
|
5
|
+
export type USCObject = USCBpmChange | USCTimeScaleChange | USCSingleNote | USCSlideNote | USCGuideNote | USCDamageNote | USCSkill | USCFever;
|
|
6
|
+
interface BaseUSCObject {
|
|
7
|
+
beat: number;
|
|
8
|
+
timeScaleGroup: number;
|
|
9
|
+
}
|
|
10
|
+
export type USCBpmChange = Omit<BaseUSCObject, 'timeScaleGroup'> & {
|
|
11
|
+
type: 'bpm';
|
|
12
|
+
bpm: number;
|
|
13
|
+
};
|
|
14
|
+
export interface USCTimeScaleChange {
|
|
15
|
+
type: 'timeScaleGroup';
|
|
16
|
+
changes: {
|
|
17
|
+
beat: number;
|
|
18
|
+
timeScale: number;
|
|
19
|
+
}[];
|
|
20
|
+
}
|
|
21
|
+
type BaseUSCNote = BaseUSCObject & {
|
|
22
|
+
lane: number;
|
|
23
|
+
size: number;
|
|
24
|
+
};
|
|
25
|
+
export type USCSingleNote = BaseUSCNote & {
|
|
26
|
+
type: 'single';
|
|
27
|
+
critical: boolean;
|
|
28
|
+
trace: boolean;
|
|
29
|
+
direction?: 'left' | 'up' | 'right' | 'none';
|
|
30
|
+
};
|
|
31
|
+
export type USCDamageNote = BaseUSCNote & {
|
|
32
|
+
type: 'damage';
|
|
33
|
+
};
|
|
34
|
+
export type USCConnectionStartNote = BaseUSCNote & {
|
|
35
|
+
type: 'start';
|
|
36
|
+
critical: boolean;
|
|
37
|
+
ease: 'out' | 'linear' | 'in' | 'inout' | 'outin';
|
|
38
|
+
judgeType: 'normal' | 'trace' | 'none';
|
|
39
|
+
};
|
|
40
|
+
export type USCConnectionTickNote = BaseUSCNote & {
|
|
41
|
+
type: 'tick';
|
|
42
|
+
critical?: boolean;
|
|
43
|
+
ease: 'out' | 'linear' | 'in' | 'inout' | 'outin';
|
|
44
|
+
judgeType?: 'normal' | 'trace';
|
|
45
|
+
};
|
|
46
|
+
export type USCConnectionAttachNote = Omit<BaseUSCObject, 'timeScaleGroup'> & {
|
|
47
|
+
type: 'attach';
|
|
48
|
+
critical?: boolean;
|
|
49
|
+
timeScaleGroup?: number;
|
|
50
|
+
};
|
|
51
|
+
export type USCConnectionEndNote = BaseUSCNote & {
|
|
52
|
+
type: 'end';
|
|
53
|
+
critical: boolean;
|
|
54
|
+
direction?: 'left' | 'up' | 'right';
|
|
55
|
+
judgeType: 'normal' | 'trace' | 'none';
|
|
56
|
+
};
|
|
57
|
+
export interface USCSlideNote {
|
|
58
|
+
type: 'slide' | 'guide';
|
|
59
|
+
critical: boolean;
|
|
60
|
+
connections: [
|
|
61
|
+
USCConnectionStartNote,
|
|
62
|
+
...(USCConnectionTickNote | USCConnectionAttachNote)[],
|
|
63
|
+
USCConnectionEndNote
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
export declare const USCColor: {
|
|
67
|
+
neutral: number;
|
|
68
|
+
red: number;
|
|
69
|
+
green: number;
|
|
70
|
+
blue: number;
|
|
71
|
+
yellow: number;
|
|
72
|
+
purple: number;
|
|
73
|
+
cyan: number;
|
|
74
|
+
black: number;
|
|
75
|
+
};
|
|
76
|
+
export type USCColor = keyof typeof USCColor;
|
|
77
|
+
export type USCGuideMidpointNote = BaseUSCNote & {
|
|
78
|
+
ease: 'out' | 'linear' | 'in' | 'inout' | 'outin';
|
|
79
|
+
};
|
|
80
|
+
export declare const USCFade: {
|
|
81
|
+
in: number;
|
|
82
|
+
out: number;
|
|
83
|
+
none: number;
|
|
84
|
+
};
|
|
85
|
+
export type USCFade = keyof typeof USCFade;
|
|
86
|
+
export interface USCGuideNote {
|
|
87
|
+
type: 'guide';
|
|
88
|
+
color: USCColor;
|
|
89
|
+
fade: USCFade;
|
|
90
|
+
midpoints: USCGuideMidpointNote[];
|
|
91
|
+
}
|
|
92
|
+
export declare const SkillEffects: {
|
|
93
|
+
readonly none: 0;
|
|
94
|
+
readonly heal: 1;
|
|
95
|
+
readonly score: 2;
|
|
96
|
+
readonly judgment: 3;
|
|
97
|
+
};
|
|
98
|
+
export type SkillEffects = (typeof SkillEffects)[keyof typeof SkillEffects];
|
|
99
|
+
type Level = 1 | 2 | 3 | 4;
|
|
100
|
+
interface USCEvent {
|
|
101
|
+
beat: number;
|
|
102
|
+
}
|
|
103
|
+
export type USCSkill = USCEvent & {
|
|
104
|
+
type: 'skill';
|
|
105
|
+
effect?: SkillEffects;
|
|
106
|
+
level?: Level;
|
|
107
|
+
};
|
|
108
|
+
export type USCFever = USCEvent & {
|
|
109
|
+
type: 'feverChance' | 'feverStart';
|
|
110
|
+
force?: boolean;
|
|
111
|
+
};
|
|
112
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const USCColor = {
|
|
2
|
+
neutral: 0,
|
|
3
|
+
red: 1,
|
|
4
|
+
green: 2,
|
|
5
|
+
blue: 3,
|
|
6
|
+
yellow: 4,
|
|
7
|
+
purple: 5,
|
|
8
|
+
cyan: 6,
|
|
9
|
+
black: 7,
|
|
10
|
+
};
|
|
11
|
+
export const USCFade = {
|
|
12
|
+
in: 2,
|
|
13
|
+
out: 0,
|
|
14
|
+
none: 1,
|
|
15
|
+
};
|
|
16
|
+
export const SkillEffects = {
|
|
17
|
+
none: 0,
|
|
18
|
+
heal: 1,
|
|
19
|
+
score: 2,
|
|
20
|
+
judgment: 3,
|
|
21
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sonolus-next-rush-engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Perspective-lane rhythm game for Sonolus",
|
|
5
|
+
"author": "Hyeon2",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/UntitledCharts/sonolus-next-rush-engine.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"Sonolus"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./dist/index.js",
|
|
20
|
+
"./EngineConfiguration": "./dist/EngineConfiguration",
|
|
21
|
+
"./EnginePlayData": "./dist/EnginePlayData",
|
|
22
|
+
"./EngineWatchData": "./dist/EngineWatchData",
|
|
23
|
+
"./EnginePreviewData": "./dist/EnginePreviewData",
|
|
24
|
+
"./EngineTutorialData": "./dist/EngineTutorialData",
|
|
25
|
+
"./EngineRom": "./dist/EngineRom",
|
|
26
|
+
"./EngineThumbnail": "./dist/thumbnail.png"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"check-type": "tsc -p . --noEmit",
|
|
30
|
+
"check-lint": "biome lint",
|
|
31
|
+
"check-format": "biome format",
|
|
32
|
+
"biome": "biome check",
|
|
33
|
+
"format": "biome format --write",
|
|
34
|
+
"fix": "biome check --write",
|
|
35
|
+
"version": "node ./version.js",
|
|
36
|
+
"build": "tsc -p . && node ./build.js"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@sonolus/core": "~7.15.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@biomejs/biome": "~2.4.14",
|
|
43
|
+
"@sonolus/sonolus.js": "~9.7.1",
|
|
44
|
+
"@types/node": "^25.6.2",
|
|
45
|
+
"typescript": "~6.0.3"
|
|
46
|
+
}
|
|
47
|
+
}
|