sonolus-pjsekai-js 1.1.4 → 1.1.6

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.
@@ -0,0 +1,542 @@
1
+ import { EngineArchetypeDataName, EngineArchetypeName, } from "@sonolus/core";
2
+ import { USCColor, USCFade, } from "./index.js";
3
+ export const uscToLevelData = (usc, offset = 0) => {
4
+ const entities = [];
5
+ const timeToIntermediates = new Map();
6
+ const intermediateToRef = new Map();
7
+ const intermediateToEntity = new Map();
8
+ let i = 0;
9
+ const getRef = (intermediate) => {
10
+ let ref = intermediateToRef.get(intermediate);
11
+ if (ref)
12
+ return ref;
13
+ ref = (i++).toString(36);
14
+ intermediateToRef.set(intermediate, ref);
15
+ const entity = intermediateToEntity.get(intermediate);
16
+ if (entity)
17
+ entity.name = ref;
18
+ return ref;
19
+ };
20
+ const append = (intermediate) => {
21
+ const entity = {
22
+ archetype: intermediate.archetype,
23
+ data: [],
24
+ };
25
+ if (intermediate.sim) {
26
+ const beat = intermediate.data[EngineArchetypeDataName.Beat];
27
+ if (typeof beat !== "number")
28
+ throw new Error("Unexpected beat");
29
+ const intermediates = timeToIntermediates.get(beat);
30
+ if (intermediates) {
31
+ intermediates.push(intermediate);
32
+ }
33
+ else {
34
+ timeToIntermediates.set(beat, [intermediate]);
35
+ }
36
+ }
37
+ const ref = intermediateToRef.get(intermediate);
38
+ if (ref)
39
+ entity.name = ref;
40
+ intermediateToEntity.set(intermediate, entity);
41
+ entities.push(entity);
42
+ for (const [name, value] of Object.entries(intermediate.data)) {
43
+ if (typeof value === "number") {
44
+ entity.data.push({
45
+ name,
46
+ value,
47
+ });
48
+ }
49
+ else if (typeof value === "boolean") {
50
+ entity.data.push({
51
+ name,
52
+ value: value ? 1 : 0,
53
+ });
54
+ }
55
+ else if (typeof value === "string") {
56
+ entity.data.push({
57
+ name,
58
+ ref: value,
59
+ });
60
+ }
61
+ else {
62
+ entity.data.push({
63
+ name,
64
+ ref: getRef(value),
65
+ });
66
+ }
67
+ }
68
+ if ("timeScaleGroup" in intermediate) {
69
+ entity.data.push({
70
+ name: "timeScaleGroup",
71
+ ref: `tsg:${intermediate.timeScaleGroup ?? 0}`,
72
+ });
73
+ }
74
+ };
75
+ append({
76
+ archetype: "Initialization",
77
+ data: {},
78
+ sim: false,
79
+ });
80
+ append({
81
+ archetype: "InputManager",
82
+ data: {},
83
+ sim: false,
84
+ });
85
+ append({
86
+ archetype: "Stage",
87
+ data: {},
88
+ sim: false,
89
+ });
90
+ let tsGroupIndex = -1;
91
+ const tsGroupEntities = [];
92
+ const tsChangeEntities = [];
93
+ for (const tsGroup of usc.objects) {
94
+ if (tsGroup.type !== "timeScaleGroup")
95
+ continue;
96
+ tsGroupIndex++;
97
+ const changes = [...tsGroup.changes];
98
+ changes.sort((a, b) => a.beat - b.beat);
99
+ for (const [index, change] of Object.entries(changes)) {
100
+ tsChangeEntities.push({
101
+ archetype: "TimeScaleChange",
102
+ data: [
103
+ {
104
+ name: EngineArchetypeDataName.Beat,
105
+ value: change.beat,
106
+ },
107
+ {
108
+ name: "timeScale",
109
+ value: change.timeScale === 0 ? 0.000001 : change.timeScale,
110
+ },
111
+ tsGroup.changes[+index + 1] === undefined
112
+ ? {
113
+ name: "next",
114
+ value: -1,
115
+ }
116
+ : {
117
+ name: "next",
118
+ ref: `tsc:${tsGroupIndex}:${+index + 1}`,
119
+ },
120
+ ],
121
+ name: `tsc:${tsGroupIndex}:${index}`,
122
+ });
123
+ }
124
+ tsGroupEntities.push({
125
+ archetype: "TimeScaleGroup",
126
+ data: [
127
+ {
128
+ name: "first",
129
+ ref: `tsc:${tsGroupIndex}:0`,
130
+ },
131
+ {
132
+ name: "length",
133
+ value: tsGroup.changes.length,
134
+ },
135
+ tsGroupIndex === tsGroup.changes.length - 1
136
+ ? {
137
+ name: "next",
138
+ value: -1,
139
+ }
140
+ : {
141
+ name: "next",
142
+ ref: `tsg:${tsGroupIndex + 1}`,
143
+ },
144
+ ],
145
+ name: `tsg:${tsGroupIndex}`,
146
+ });
147
+ }
148
+ if (tsGroupIndex === -1) {
149
+ entities.push({
150
+ archetype: "TimeScaleGroup",
151
+ data: [
152
+ {
153
+ name: "first",
154
+ ref: "tsc:0:0",
155
+ },
156
+ {
157
+ name: "length",
158
+ value: 0,
159
+ },
160
+ ],
161
+ name: "tsg:0",
162
+ });
163
+ entities.push({
164
+ archetype: "TimeScaleChange",
165
+ data: [
166
+ {
167
+ name: EngineArchetypeDataName.Beat,
168
+ value: 0,
169
+ },
170
+ {
171
+ name: "timeScale",
172
+ value: 1,
173
+ },
174
+ {
175
+ name: "timeScaleGroup",
176
+ ref: "trg:0",
177
+ },
178
+ ],
179
+ name: "tsc:0:0",
180
+ });
181
+ }
182
+ else {
183
+ entities.push(...tsGroupEntities);
184
+ entities.push(...tsChangeEntities);
185
+ }
186
+ for (const object of usc.objects) {
187
+ handlers[object.type](object, append);
188
+ }
189
+ for (const intermediates of timeToIntermediates.values()) {
190
+ for (let i = 1; i < intermediates.length; i++) {
191
+ append({
192
+ archetype: "SimLine",
193
+ data: {
194
+ a: intermediates[i - 1],
195
+ b: intermediates[i],
196
+ },
197
+ sim: false,
198
+ });
199
+ }
200
+ }
201
+ return {
202
+ bgmOffset: usc.offset + offset,
203
+ entities,
204
+ };
205
+ };
206
+ const directions = {
207
+ left: -1,
208
+ up: 0,
209
+ right: 1,
210
+ };
211
+ const eases = {
212
+ outIn: -2,
213
+ out: -1,
214
+ linear: 0,
215
+ in: 1,
216
+ inOut: 2,
217
+ };
218
+ const slideStarts = {
219
+ tap: 0,
220
+ trace: 1,
221
+ none: 2,
222
+ };
223
+ const bpm = (object, append) => {
224
+ append({
225
+ archetype: EngineArchetypeName.BpmChange,
226
+ data: {
227
+ [EngineArchetypeDataName.Beat]: object.beat,
228
+ [EngineArchetypeDataName.Bpm]: object.bpm,
229
+ },
230
+ sim: false,
231
+ });
232
+ };
233
+ const timeScaleGroup = () => undefined;
234
+ const single = (object, append) => {
235
+ const intermediate = {
236
+ archetype: object.critical ? "CriticalTapNote" : "NormalTapNote",
237
+ data: {
238
+ [EngineArchetypeDataName.Beat]: object.beat,
239
+ lane: object.lane,
240
+ size: object.size,
241
+ },
242
+ timeScaleGroup: object.timeScaleGroup,
243
+ sim: true,
244
+ };
245
+ if (object.trace) {
246
+ intermediate.archetype = object.critical
247
+ ? "CriticalTraceNote"
248
+ : "NormalTraceNote";
249
+ if (object.direction) {
250
+ if (object.direction === "none") {
251
+ intermediate.archetype = "NonDirectionalTraceFlickNote";
252
+ }
253
+ else {
254
+ intermediate.archetype = object.critical
255
+ ? "CriticalTraceFlickNote"
256
+ : "NormalTraceFlickNote";
257
+ intermediate.data.direction = directions[object.direction];
258
+ }
259
+ }
260
+ }
261
+ else {
262
+ if (object.direction) {
263
+ intermediate.archetype = object.critical
264
+ ? "CriticalFlickNote"
265
+ : "NormalFlickNote";
266
+ if (object.direction === "none") {
267
+ return;
268
+ }
269
+ intermediate.data.direction = directions[object.direction];
270
+ }
271
+ }
272
+ append(intermediate);
273
+ };
274
+ const damage = (object, append) => {
275
+ const intermediate = {
276
+ archetype: "IgnoredSlideTickNote",
277
+ data: {
278
+ [EngineArchetypeDataName.Beat]: object.beat,
279
+ lane: object.lane,
280
+ size: object.size,
281
+ },
282
+ sim: false,
283
+ timeScaleGroup: object.timeScaleGroup,
284
+ };
285
+ append(intermediate);
286
+ };
287
+ const slide = (object, append) => {
288
+ const cis = [];
289
+ const joints = [];
290
+ const attaches = [];
291
+ const ends = [];
292
+ let startType = "tap";
293
+ const connections = getConnections(object);
294
+ for (const [i, connection] of connections.entries()) {
295
+ if (i === 0) {
296
+ if (connection.type !== "start")
297
+ continue;
298
+ let archetype;
299
+ let sim = true;
300
+ if (connection.judgeType === "none") {
301
+ archetype = "HiddenSlideStartNote";
302
+ sim = false;
303
+ startType = "none";
304
+ }
305
+ else if (connection.judgeType === "trace") {
306
+ if (connection.critical) {
307
+ archetype = "CriticalTraceSlideStartNote";
308
+ }
309
+ else {
310
+ archetype = "NormalTraceSlideStartNote";
311
+ }
312
+ startType = "trace";
313
+ }
314
+ else {
315
+ if (connection.critical) {
316
+ archetype = "CriticalSlideStartNote";
317
+ }
318
+ else {
319
+ archetype = "NormalSlideStartNote";
320
+ }
321
+ startType = "tap";
322
+ }
323
+ const ci = {
324
+ archetype,
325
+ data: {
326
+ [EngineArchetypeDataName.Beat]: connection.beat,
327
+ lane: connection.lane,
328
+ size: connection.size,
329
+ },
330
+ sim,
331
+ ease: connection.ease,
332
+ timeScaleGroup: connection.timeScaleGroup,
333
+ };
334
+ cis.push(ci);
335
+ joints.push(ci);
336
+ continue;
337
+ }
338
+ if (i === connections.length - 1) {
339
+ if (connection.type !== "end")
340
+ continue;
341
+ let ci;
342
+ if (connection.judgeType === "none") {
343
+ ci = {
344
+ archetype: "HiddenSlideTickNote",
345
+ data: {
346
+ [EngineArchetypeDataName.Beat]: connection.beat,
347
+ lane: connection.lane,
348
+ size: connection.size,
349
+ },
350
+ sim: false,
351
+ timeScaleGroup: connection.timeScaleGroup,
352
+ };
353
+ }
354
+ else {
355
+ let archetype;
356
+ if (connection.judgeType === "trace") {
357
+ if (connection.critical) {
358
+ archetype = "CriticalTraceSlideEndNote";
359
+ }
360
+ else {
361
+ archetype = "NormalTraceSlideEndNote";
362
+ }
363
+ }
364
+ else {
365
+ if (connection.critical) {
366
+ archetype = "CriticalSlideEndNote";
367
+ }
368
+ else {
369
+ archetype = "NormalSlideEndNote";
370
+ }
371
+ }
372
+ ci = {
373
+ archetype,
374
+ data: {
375
+ [EngineArchetypeDataName.Beat]: connection.beat,
376
+ lane: connection.lane,
377
+ size: connection.size,
378
+ },
379
+ sim: true,
380
+ timeScaleGroup: connection.timeScaleGroup,
381
+ };
382
+ if ("direction" in connection) {
383
+ ci.archetype = connection.critical
384
+ ? "CriticalSlideEndFlickNote"
385
+ : "NormalSlideEndFlickNote";
386
+ ci.data.direction = directions[connection.direction];
387
+ }
388
+ }
389
+ cis.push(ci);
390
+ joints.push(ci);
391
+ ends.push(ci);
392
+ continue;
393
+ }
394
+ switch (connection.type) {
395
+ case "tick": {
396
+ const ci = {
397
+ archetype: "HiddenSlideTickNote",
398
+ data: {
399
+ [EngineArchetypeDataName.Beat]: connection.beat,
400
+ lane: connection.lane,
401
+ size: connection.size,
402
+ },
403
+ sim: false,
404
+ ease: connection.ease,
405
+ timeScaleGroup: connection.timeScaleGroup,
406
+ };
407
+ if ("critical" in connection)
408
+ ci.archetype = connection.critical
409
+ ? "CriticalSlideTickNote"
410
+ : "NormalSlideTickNote";
411
+ cis.push(ci);
412
+ joints.push(ci);
413
+ break;
414
+ }
415
+ case "attach": {
416
+ const ci = {
417
+ archetype: "IgnoredSlideTickNote",
418
+ data: {
419
+ [EngineArchetypeDataName.Beat]: connection.beat,
420
+ },
421
+ sim: false,
422
+ };
423
+ if ("critical" in connection)
424
+ ci.archetype = connection.critical
425
+ ? "CriticalAttachedSlideTickNote"
426
+ : "NormalAttachedSlideTickNote";
427
+ if ("timeScaleGroup" in connection)
428
+ ci.timeScaleGroup = connection.timeScaleGroup;
429
+ cis.push(ci);
430
+ attaches.push(ci);
431
+ break;
432
+ }
433
+ case "start":
434
+ case "end":
435
+ throw new Error("Unexpected slide tick");
436
+ }
437
+ }
438
+ const connectors = [];
439
+ const start = cis[0];
440
+ for (const [i, joint] of joints.entries()) {
441
+ if (i === 0)
442
+ continue;
443
+ const head = joints[i - 1];
444
+ if (!head.ease)
445
+ throw new Error("Unexpected missing ease");
446
+ const archetype = object.critical
447
+ ? "CriticalSlideConnector"
448
+ : "NormalSlideConnector";
449
+ connectors.push({
450
+ archetype,
451
+ data: {
452
+ start,
453
+ end: ends[0],
454
+ head,
455
+ tail: joint,
456
+ ease: eases[head.ease],
457
+ startType: slideStarts[startType],
458
+ },
459
+ sim: false,
460
+ });
461
+ }
462
+ for (const attach of attaches) {
463
+ const index = cis.indexOf(attach);
464
+ const tailIndex = joints.findIndex((c) => cis.indexOf(c) > index);
465
+ attach.data.attach = connectors[tailIndex - 1];
466
+ }
467
+ for (const end of ends) {
468
+ end.data.slide = connectors[connectors.length - 1];
469
+ }
470
+ for (const ci of cis) {
471
+ append(ci);
472
+ }
473
+ for (const connector of connectors) {
474
+ append(connector);
475
+ }
476
+ };
477
+ const guide = (object, append) => {
478
+ const start = object.midpoints[0];
479
+ const end = object.midpoints[object.midpoints.length - 1];
480
+ for (const [i, joint] of object.midpoints.entries()) {
481
+ if (i === 0)
482
+ continue;
483
+ const head = object.midpoints[i - 1];
484
+ if (!head.ease)
485
+ throw new Error("Unexpected missing ease");
486
+ append({
487
+ archetype: object.color == 'yellow' ? "CriticalActiveSlideConnector" : "NormalActiveSlideConnector",
488
+ data: {
489
+ color: USCColor[object.color],
490
+ fade: USCFade[object.fade],
491
+ ease: eases[head.ease],
492
+ startLane: start.lane,
493
+ startSize: start.size,
494
+ startBeat: start.beat,
495
+ startTimeScaleGroup: `tsg:${start.timeScaleGroup ?? 0}`,
496
+ headLane: head.lane,
497
+ headSize: head.size,
498
+ headBeat: head.beat,
499
+ headTimeScaleGroup: `tsg:${head.timeScaleGroup ?? 0}`,
500
+ tailLane: joint.lane,
501
+ tailSize: joint.size,
502
+ tailBeat: joint.beat,
503
+ tailTimeScaleGroup: `tsg:${joint.timeScaleGroup ?? 0}`,
504
+ endLane: end.lane,
505
+ endSize: end.size,
506
+ endBeat: end.beat,
507
+ endTimeScaleGroup: `tsg:${end.timeScaleGroup ?? 0}`,
508
+ },
509
+ sim: false,
510
+ });
511
+ }
512
+ };
513
+ const handlers = {
514
+ bpm,
515
+ single,
516
+ timeScaleGroup,
517
+ slide,
518
+ guide,
519
+ damage,
520
+ };
521
+ const getConnections = (object) => {
522
+ const connections = [...object.connections];
523
+ const beats = connections.map(({ beat }) => beat).sort((a, b) => a - b);
524
+ const min = beats[0];
525
+ const max = beats[beats.length - 1];
526
+ const start = Math.max(Math.ceil(min / 0.5) * 0.5, Math.floor(min / 0.5 + 1) * 0.5);
527
+ for (let beat = start; beat < max; beat += 0.5) {
528
+ connections.push({
529
+ type: "attach",
530
+ beat,
531
+ });
532
+ }
533
+ const startStep = connections.find(({ type }) => type === "start");
534
+ const endStep = connections.find(({ type }) => type === "end");
535
+ const steps = connections.filter(({ type }) => type === "tick" || type === "attach");
536
+ steps.sort((a, b) => a.beat - b.beat);
537
+ if (!startStep)
538
+ throw "Missing start";
539
+ if (!endStep)
540
+ throw "Missing end";
541
+ return [startStep, ...steps, endStep];
542
+ };
@@ -7,11 +7,11 @@ type BaseUSCObject = {
7
7
  beat: number;
8
8
  };
9
9
  export type USCBpmChange = BaseUSCObject & {
10
- type: 'bpm';
10
+ type: "bpm";
11
11
  bpm: number;
12
12
  };
13
13
  export type USCTimeScaleChange = BaseUSCObject & {
14
- type: 'timeScale';
14
+ type: "timeScale";
15
15
  timeScale: number;
16
16
  };
17
17
  type BaseUSCNote = BaseUSCObject & {
@@ -19,42 +19,42 @@ type BaseUSCNote = BaseUSCObject & {
19
19
  size: number;
20
20
  };
21
21
  export type USCSingleNote = BaseUSCNote & {
22
- type: 'single';
22
+ type: "single";
23
23
  trace: boolean;
24
24
  critical: boolean;
25
- direction?: 'left' | 'up' | 'right';
25
+ direction?: "left" | "up" | "right";
26
26
  };
27
27
  export type USCConnectionStartNote = BaseUSCNote & {
28
- type: 'start';
28
+ type: "start";
29
29
  trace: boolean;
30
30
  critical: boolean;
31
- ease: 'out' | 'linear' | 'in';
31
+ ease: "out" | "linear" | "in";
32
32
  };
33
33
  export type USCConnectionIgnoreNote = BaseUSCNote & {
34
- type: 'ignore';
35
- ease: 'out' | 'linear' | 'in';
34
+ type: "ignore";
35
+ ease: "out" | "linear" | "in";
36
36
  };
37
37
  export type USCConnectionTickNote = BaseUSCNote & {
38
- type: 'tick';
38
+ type: "tick";
39
39
  trace: boolean;
40
40
  critical: boolean;
41
- ease: 'out' | 'linear' | 'in';
41
+ ease: "out" | "linear" | "in";
42
42
  };
43
43
  export type USCConnectionHiddenNote = BaseUSCObject & {
44
- type: 'hidden';
44
+ type: "hidden";
45
45
  };
46
46
  export type USCConnectionAttachNote = BaseUSCObject & {
47
- type: 'attach';
47
+ type: "attach";
48
48
  critical: boolean;
49
49
  };
50
50
  export type USCConnectionEndNote = BaseUSCNote & {
51
- type: 'end';
51
+ type: "end";
52
52
  trace: boolean;
53
53
  critical: boolean;
54
- direction?: 'left' | 'up' | 'right';
54
+ direction?: "left" | "up" | "right";
55
55
  };
56
56
  export type USCSlideNote = {
57
- type: 'slide';
57
+ type: "slide";
58
58
  active: boolean;
59
59
  critical: boolean;
60
60
  connections: [
@@ -0,0 +1,91 @@
1
+ export type USC = {
2
+ offset: number;
3
+ objects: USCObject[];
4
+ };
5
+ export type USCObject = USCBpmChange | USCTimeScaleChange | USCSingleNote | USCSlideNote | USCGuideNote | USCDamageNote;
6
+ type BaseUSCObject = {
7
+ beat: number;
8
+ timeScaleGroup: number;
9
+ };
10
+ export type USCBpmChange = Omit<BaseUSCObject, "timeScaleGroup"> & {
11
+ type: "bpm";
12
+ bpm: number;
13
+ };
14
+ export type 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
+ };
45
+ export type USCConnectionAttachNote = Omit<BaseUSCObject, "timeScaleGroup"> & {
46
+ type: "attach";
47
+ critical?: boolean;
48
+ timeScaleGroup?: number;
49
+ };
50
+ export type USCConnectionEndNote = BaseUSCNote & {
51
+ type: "end";
52
+ critical: boolean;
53
+ direction?: "left" | "up" | "right";
54
+ judgeType: "normal" | "trace" | "none";
55
+ };
56
+ export type USCSlideNote = {
57
+ type: "slide";
58
+ critical: boolean;
59
+ connections: [
60
+ USCConnectionStartNote,
61
+ ...(USCConnectionTickNote | USCConnectionAttachNote)[],
62
+ USCConnectionEndNote
63
+ ];
64
+ };
65
+ export declare const USCColor: {
66
+ neutral: number;
67
+ red: number;
68
+ green: number;
69
+ blue: number;
70
+ yellow: number;
71
+ purple: number;
72
+ cyan: number;
73
+ black: number;
74
+ };
75
+ export type USCColor = keyof typeof USCColor;
76
+ export type USCGuideMidpointNote = BaseUSCNote & {
77
+ ease: "out" | "linear" | "in" | "inOut" | "outIn";
78
+ };
79
+ export declare const USCFade: {
80
+ in: number;
81
+ out: number;
82
+ none: number;
83
+ };
84
+ export type USCFade = keyof typeof USCFade;
85
+ export type USCGuideNote = {
86
+ type: "guide";
87
+ color: USCColor;
88
+ fade: USCFade;
89
+ midpoints: USCGuideMidpointNote[];
90
+ };
91
+ export {};