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.
@@ -1,2 +1,2 @@
1
- import { USC } from '../usc/index.cjs';
1
+ import { USC } from "../usc/index.cjs";
2
2
  export declare const susToUSC: (sus: string) => USC;
@@ -0,0 +1,5 @@
1
+ import { USC } from "../usc/index.js";
2
+ import { Score } from "./analyze.js";
3
+ /** Convert a SUS to a USC */
4
+ export declare const susToUSC: (sus: string) => USC;
5
+ export declare const chsLikeToUSC: (score: Score) => USC;
@@ -0,0 +1,301 @@
1
+ import { analyze } from "./analyze.js";
2
+ /** Convert a SUS to a USC */
3
+ export const susToUSC = (sus) => chsLikeToUSC(analyze(sus));
4
+ export const chsLikeToUSC = (score) => {
5
+ const flickMods = new Map();
6
+ const criticalMods = new Set();
7
+ const tickRemoveMods = new Set();
8
+ const judgeRemoveMods = new Set();
9
+ const easeMods = new Map();
10
+ const preventSingles = new Set();
11
+ const dedupeSingles = new Set();
12
+ const dedupeSlides = new Map();
13
+ const requests = {
14
+ sideLane: false,
15
+ laneOffset: 0,
16
+ };
17
+ const requestsRaw = score.meta.get("REQUEST");
18
+ if (requestsRaw) {
19
+ for (const request of requestsRaw) {
20
+ try {
21
+ const [key, value] = JSON.parse(request).split(" ", 2);
22
+ switch (key) {
23
+ case "side_lane":
24
+ requests.sideLane = value === "true";
25
+ break;
26
+ case "lane_offset":
27
+ requests.laneOffset = Number(value);
28
+ break;
29
+ }
30
+ }
31
+ catch (e) {
32
+ // Noop
33
+ }
34
+ }
35
+ }
36
+ for (const slides of [score.slides, score.guides]) {
37
+ for (const slide of slides) {
38
+ for (const note of slide) {
39
+ const key = getKey(note);
40
+ switch (note.type) {
41
+ case 1:
42
+ case 2:
43
+ case 3:
44
+ case 5:
45
+ preventSingles.add(key);
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ }
51
+ for (const note of score.directionalNotes) {
52
+ const key = getKey(note);
53
+ switch (note.type) {
54
+ case 1:
55
+ flickMods.set(key, "up");
56
+ break;
57
+ case 3:
58
+ flickMods.set(key, "left");
59
+ break;
60
+ case 4:
61
+ flickMods.set(key, "right");
62
+ break;
63
+ case 2:
64
+ easeMods.set(key, "in");
65
+ break;
66
+ case 5:
67
+ case 6:
68
+ easeMods.set(key, "out");
69
+ break;
70
+ }
71
+ }
72
+ for (const note of score.tapNotes) {
73
+ const key = getKey(note);
74
+ switch (note.type) {
75
+ case 2:
76
+ criticalMods.add(key);
77
+ break;
78
+ case 4:
79
+ judgeRemoveMods.add(key);
80
+ break;
81
+ case 3:
82
+ case 5:
83
+ tickRemoveMods.add(key);
84
+ break;
85
+ case 6:
86
+ criticalMods.add(key);
87
+ tickRemoveMods.add(key);
88
+ break;
89
+ case 7:
90
+ judgeRemoveMods.add(key);
91
+ break;
92
+ case 8:
93
+ criticalMods.add(key);
94
+ judgeRemoveMods.add(key);
95
+ break;
96
+ }
97
+ }
98
+ const objects = [];
99
+ for (const timeScaleChanges of score.timeScaleChanges) {
100
+ objects.push({
101
+ type: "timeScaleGroup",
102
+ changes: timeScaleChanges.map((timeScaleChange) => ({
103
+ beat: timeScaleChange.tick / score.ticksPerBeat,
104
+ timeScale: timeScaleChange.timeScale,
105
+ })),
106
+ });
107
+ }
108
+ for (const bpmChange of score.bpmChanges) {
109
+ objects.push({
110
+ type: "bpm",
111
+ beat: bpmChange.tick / score.ticksPerBeat,
112
+ bpm: bpmChange.bpm,
113
+ });
114
+ }
115
+ for (const note of score.tapNotes) {
116
+ if (!requests.sideLane && (note.lane <= 1 || note.lane >= 14))
117
+ continue;
118
+ const key = getKey(note);
119
+ if (preventSingles.has(key))
120
+ continue;
121
+ if (dedupeSingles.has(key))
122
+ continue;
123
+ dedupeSingles.add(key);
124
+ let object;
125
+ switch (note.type) {
126
+ case 1:
127
+ case 2:
128
+ case 3:
129
+ case 5:
130
+ case 6: {
131
+ object = {
132
+ type: "single",
133
+ beat: note.tick / score.ticksPerBeat,
134
+ lane: note.lane - 8 + note.width / 2 + requests.laneOffset,
135
+ size: note.width / 2,
136
+ critical: [2, 6].includes(note.type) || criticalMods.has(key),
137
+ trace: [3, 5, 6].includes(note.type) || tickRemoveMods.has(key),
138
+ timeScaleGroup: note.timeScaleGroup,
139
+ };
140
+ const flickMod = flickMods.get(key);
141
+ if (flickMod)
142
+ object.direction = flickMod;
143
+ if (easeMods.has(key))
144
+ object.direction = "none";
145
+ break;
146
+ }
147
+ case 4:
148
+ object = {
149
+ type: "damage",
150
+ beat: note.tick / score.ticksPerBeat,
151
+ lane: note.lane - 8 + note.width / 2 + requests.laneOffset,
152
+ size: note.width / 2,
153
+ timeScaleGroup: note.timeScaleGroup,
154
+ };
155
+ break;
156
+ default:
157
+ continue;
158
+ }
159
+ objects.push(object);
160
+ }
161
+ for (const [isDummy, slides] of [
162
+ [false, score.slides],
163
+ [true, score.guides],
164
+ ]) {
165
+ for (const slide of slides) {
166
+ const startNote = slide.find(({ type }) => type === 1 || type === 2);
167
+ if (!startNote)
168
+ continue;
169
+ const endNote = slide.find(({ type }) => type === 2);
170
+ if (!endNote)
171
+ continue;
172
+ const object = {
173
+ type: "slide",
174
+ critical: criticalMods.has(getKey(startNote)),
175
+ connections: [],
176
+ };
177
+ for (const note of slide) {
178
+ const key = getKey(note);
179
+ const beat = note.tick / score.ticksPerBeat;
180
+ const lane = note.lane - 8 + note.width / 2 + requests.laneOffset;
181
+ const size = note.width / 2;
182
+ const timeScaleGroup = note.timeScaleGroup;
183
+ const critical = ("critical" in object && object.critical) || criticalMods.has(key);
184
+ const ease = easeMods.get(key) ?? "linear";
185
+ switch (note.type) {
186
+ case 1: {
187
+ let judgeType = "normal";
188
+ if (tickRemoveMods.has(key))
189
+ judgeType = "trace";
190
+ if (judgeRemoveMods.has(key))
191
+ judgeType = "none";
192
+ const connection = {
193
+ type: "start",
194
+ beat,
195
+ lane,
196
+ size,
197
+ critical,
198
+ ease: easeMods.get(key) ?? "linear",
199
+ judgeType,
200
+ timeScaleGroup,
201
+ };
202
+ object.connections.push(connection);
203
+ break;
204
+ }
205
+ case 2: {
206
+ let judgeType = "normal";
207
+ if (tickRemoveMods.has(key))
208
+ judgeType = "trace";
209
+ if (judgeRemoveMods.has(key))
210
+ judgeType = "none";
211
+ const connection = {
212
+ type: "end",
213
+ beat,
214
+ lane,
215
+ size,
216
+ critical,
217
+ judgeType,
218
+ timeScaleGroup,
219
+ };
220
+ const flickMod = flickMods.get(key);
221
+ if (flickMod)
222
+ connection.direction = flickMod;
223
+ object.connections.push(connection);
224
+ break;
225
+ }
226
+ case 3: {
227
+ if (tickRemoveMods.has(key)) {
228
+ const connection = {
229
+ type: "attach",
230
+ beat,
231
+ critical,
232
+ timeScaleGroup,
233
+ };
234
+ object.connections.push(connection);
235
+ }
236
+ else {
237
+ const connection = {
238
+ type: "tick",
239
+ beat,
240
+ lane,
241
+ size,
242
+ critical,
243
+ ease,
244
+ timeScaleGroup,
245
+ };
246
+ object.connections.push(connection);
247
+ }
248
+ break;
249
+ }
250
+ case 5: {
251
+ if (tickRemoveMods.has(key))
252
+ break;
253
+ const connection = {
254
+ type: "tick",
255
+ beat,
256
+ lane,
257
+ size,
258
+ ease,
259
+ timeScaleGroup,
260
+ };
261
+ object.connections.push(connection);
262
+ break;
263
+ }
264
+ }
265
+ }
266
+ if (isDummy ||
267
+ (tickRemoveMods.has(getKey(startNote)) &&
268
+ judgeRemoveMods.has(getKey(startNote)))) {
269
+ objects.push({
270
+ type: "guide",
271
+ color: criticalMods.has(getKey(startNote)) ? "yellow" : "green",
272
+ fade: judgeRemoveMods.has(getKey(endNote)) ? "none" : "out",
273
+ midpoints: object.connections.flatMap((connection) => connection.type === "attach"
274
+ ? []
275
+ : [
276
+ {
277
+ beat: connection.beat,
278
+ lane: connection.lane,
279
+ size: connection.size,
280
+ ease: connection.type === "end" ? "linear" : connection.ease,
281
+ timeScaleGroup: connection.timeScaleGroup,
282
+ },
283
+ ]),
284
+ });
285
+ }
286
+ else {
287
+ objects.push(object);
288
+ }
289
+ const key = getKey(startNote);
290
+ const dupe = dedupeSlides.get(key);
291
+ if (dupe)
292
+ objects.splice(objects.indexOf(dupe), 1);
293
+ dedupeSlides.set(key, object);
294
+ }
295
+ }
296
+ return {
297
+ offset: score.offset,
298
+ objects,
299
+ };
300
+ };
301
+ const getKey = (note) => `${note.lane}-${note.tick}`;
@@ -26,8 +26,8 @@ const uscToLevelData = (usc, offset = 0) => {
26
26
  };
27
27
  if (intermediate.sim) {
28
28
  const beat = intermediate.data[core_1.EngineArchetypeDataName.Beat];
29
- if (typeof beat !== 'number')
30
- throw new Error('Unexpected beat');
29
+ if (typeof beat !== "number")
30
+ throw new Error("Unexpected beat");
31
31
  const intermediates = timeToIntermediates.get(beat);
32
32
  if (intermediates) {
33
33
  intermediates.push(intermediate);
@@ -44,7 +44,7 @@ const uscToLevelData = (usc, offset = 0) => {
44
44
  for (const [name, value] of Object.entries(intermediate.data)) {
45
45
  if (value === undefined)
46
46
  continue;
47
- if (typeof value === 'number') {
47
+ if (typeof value === "number") {
48
48
  entity.data.push({
49
49
  name,
50
50
  value,
@@ -59,12 +59,12 @@ const uscToLevelData = (usc, offset = 0) => {
59
59
  }
60
60
  };
61
61
  append({
62
- archetype: 'Initialization',
62
+ archetype: "Initialization",
63
63
  data: {},
64
64
  sim: false,
65
65
  });
66
66
  append({
67
- archetype: 'Stage',
67
+ archetype: "Stage",
68
68
  data: {},
69
69
  sim: false,
70
70
  });
@@ -74,7 +74,7 @@ const uscToLevelData = (usc, offset = 0) => {
74
74
  for (const intermediates of timeToIntermediates.values()) {
75
75
  for (let i = 1; i < intermediates.length; i++) {
76
76
  append({
77
- archetype: 'SimLine',
77
+ archetype: "SimLine",
78
78
  data: {
79
79
  a: intermediates[i - 1],
80
80
  b: intermediates[i],
@@ -126,18 +126,18 @@ const single = (object, append) => {
126
126
  archetype: object.direction
127
127
  ? object.trace
128
128
  ? object.critical
129
- ? 'CriticalTraceFlickNote'
130
- : 'NormalTraceFlickNote'
129
+ ? "CriticalTraceFlickNote"
130
+ : "NormalTraceFlickNote"
131
131
  : object.critical
132
- ? 'CriticalFlickNote'
133
- : 'NormalFlickNote'
132
+ ? "CriticalFlickNote"
133
+ : "NormalFlickNote"
134
134
  : object.trace
135
135
  ? object.critical
136
- ? 'CriticalTraceNote'
137
- : 'NormalTraceNote'
136
+ ? "CriticalTraceNote"
137
+ : "NormalTraceNote"
138
138
  : object.critical
139
- ? 'CriticalTapNote'
140
- : 'NormalTapNote',
139
+ ? "CriticalTapNote"
140
+ : "NormalTapNote",
141
141
  data: {
142
142
  [core_1.EngineArchetypeDataName.Beat]: object.beat,
143
143
  lane: object.lane,
@@ -157,10 +157,10 @@ const slide = (object, append) => {
157
157
  for (const [i, connection] of connections.entries()) {
158
158
  if (i === 0) {
159
159
  switch (connection.type) {
160
- case 'start': {
160
+ case "start": {
161
161
  let archetype;
162
162
  let sim = true;
163
- if ('judgeType' in connection) {
163
+ if ("judgeType" in connection) {
164
164
  if (connection.judgeType === "none") {
165
165
  archetype = "IgnoredSlideStartNote";
166
166
  sim = false;
@@ -185,11 +185,11 @@ const slide = (object, append) => {
185
185
  else {
186
186
  archetype = connection.trace
187
187
  ? connection.critical
188
- ? 'CriticalSlideTraceNote'
189
- : 'NormalSlideTraceNote'
188
+ ? "CriticalSlideTraceNote"
189
+ : "NormalSlideTraceNote"
190
190
  : connection.critical
191
- ? 'CriticalSlideStartNote'
192
- : 'NormalSlideStartNote';
191
+ ? "CriticalSlideStartNote"
192
+ : "NormalSlideStartNote";
193
193
  }
194
194
  const ci = {
195
195
  archetype,
@@ -199,15 +199,15 @@ const slide = (object, append) => {
199
199
  size: connection.size,
200
200
  },
201
201
  sim,
202
- ease: connection.ease
202
+ ease: connection.ease,
203
203
  };
204
204
  cis.push(ci);
205
205
  joints.push(ci);
206
206
  continue;
207
207
  }
208
- case 'ignore': {
208
+ case "ignore": {
209
209
  const ci = {
210
- archetype: 'IgnoredSlideTickNote',
210
+ archetype: "IgnoredSlideTickNote",
211
211
  data: {
212
212
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
213
213
  lane: connection.lane,
@@ -221,28 +221,28 @@ const slide = (object, append) => {
221
221
  continue;
222
222
  }
223
223
  default:
224
- throw new Error('Unexpected slide start');
224
+ throw new Error("Unexpected slide start");
225
225
  }
226
226
  }
227
227
  if (i === connections.length - 1) {
228
228
  switch (connection.type) {
229
- case 'end': {
229
+ case "end": {
230
230
  const ci = {
231
231
  archetype: connection.direction
232
232
  ? connection.trace
233
233
  ? connection.critical
234
- ? 'CriticalTraceFlickNote'
235
- : 'NormalTraceFlickNote'
234
+ ? "CriticalTraceFlickNote"
235
+ : "NormalTraceFlickNote"
236
236
  : connection.critical
237
- ? 'CriticalSlideEndFlickNote'
238
- : 'NormalSlideEndFlickNote'
237
+ ? "CriticalSlideEndFlickNote"
238
+ : "NormalSlideEndFlickNote"
239
239
  : connection.trace
240
240
  ? connection.critical
241
- ? 'CriticalSlideEndTraceNote'
242
- : 'NormalSlideEndTraceNote'
241
+ ? "CriticalSlideEndTraceNote"
242
+ : "NormalSlideEndTraceNote"
243
243
  : connection.critical
244
- ? 'CriticalSlideEndNote'
245
- : 'NormalSlideEndNote',
244
+ ? "CriticalSlideEndNote"
245
+ : "NormalSlideEndNote",
246
246
  data: {
247
247
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
248
248
  lane: connection.lane,
@@ -256,9 +256,9 @@ const slide = (object, append) => {
256
256
  ends.push(ci);
257
257
  continue;
258
258
  }
259
- case 'ignore': {
259
+ case "ignore": {
260
260
  const ci = {
261
- archetype: 'IgnoredSlideTickNote',
261
+ archetype: "IgnoredSlideTickNote",
262
262
  data: {
263
263
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
264
264
  lane: connection.lane,
@@ -272,13 +272,13 @@ const slide = (object, append) => {
272
272
  continue;
273
273
  }
274
274
  default:
275
- throw new Error('Unexpected slide end');
275
+ throw new Error("Unexpected slide end");
276
276
  }
277
277
  }
278
278
  switch (connection.type) {
279
- case 'ignore': {
279
+ case "ignore": {
280
280
  const ci = {
281
- archetype: 'IgnoredSlideTickNote',
281
+ archetype: "IgnoredSlideTickNote",
282
282
  data: {
283
283
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
284
284
  lane: connection.lane,
@@ -291,15 +291,15 @@ const slide = (object, append) => {
291
291
  joints.push(ci);
292
292
  break;
293
293
  }
294
- case 'tick': {
294
+ case "tick": {
295
295
  const ci = {
296
296
  archetype: connection.trace
297
297
  ? connection.critical
298
- ? 'CriticalSlideTraceNote'
299
- : 'NormalSlideTraceNote'
298
+ ? "CriticalSlideTraceNote"
299
+ : "NormalSlideTraceNote"
300
300
  : connection.critical
301
- ? 'CriticalSlideTickNote'
302
- : 'NormalSlideTickNote',
301
+ ? "CriticalSlideTickNote"
302
+ : "NormalSlideTickNote",
303
303
  data: {
304
304
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
305
305
  lane: connection.lane,
@@ -312,9 +312,9 @@ const slide = (object, append) => {
312
312
  joints.push(ci);
313
313
  break;
314
314
  }
315
- case 'hidden': {
315
+ case "hidden": {
316
316
  const ci = {
317
- archetype: 'HiddenSlideTickNote',
317
+ archetype: "HiddenSlideTickNote",
318
318
  data: {
319
319
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
320
320
  },
@@ -324,11 +324,11 @@ const slide = (object, append) => {
324
324
  attaches.push(ci);
325
325
  break;
326
326
  }
327
- case 'attach': {
327
+ case "attach": {
328
328
  const ci = {
329
329
  archetype: connection.critical
330
- ? 'CriticalAttachedSlideTickNote'
331
- : 'NormalAttachedSlideTickNote',
330
+ ? "CriticalAttachedSlideTickNote"
331
+ : "NormalAttachedSlideTickNote",
332
332
  data: {
333
333
  [core_1.EngineArchetypeDataName.Beat]: connection.beat,
334
334
  },
@@ -339,7 +339,7 @@ const slide = (object, append) => {
339
339
  break;
340
340
  }
341
341
  default:
342
- throw new Error('Unexpected slide tick');
342
+ throw new Error("Unexpected slide tick");
343
343
  }
344
344
  }
345
345
  const connectors = [];
@@ -350,15 +350,15 @@ const slide = (object, append) => {
350
350
  continue;
351
351
  const head = joints[i - 1];
352
352
  if (!head.ease)
353
- throw new Error('Unexpected missing ease');
353
+ throw new Error("Unexpected missing ease");
354
354
  connectors.push({
355
355
  archetype: object.active
356
356
  ? object.critical
357
- ? 'CriticalActiveSlideConnector'
358
- : 'NormalActiveSlideConnector'
357
+ ? "CriticalActiveSlideConnector"
358
+ : "NormalActiveSlideConnector"
359
359
  : object.critical
360
- ? 'CriticalSlideConnector'
361
- : 'NormalSlideConnector',
360
+ ? "CriticalSlideConnector"
361
+ : "NormalSlideConnector",
362
362
  data: {
363
363
  start,
364
364
  end,
@@ -385,26 +385,29 @@ const slide = (object, append) => {
385
385
  }
386
386
  };
387
387
  const guide = (object, append) => {
388
- const critical = object.color === 'yellow' ? true : false;
388
+ const critical = object.color === "yellow" ? true : false;
389
389
  // midpoints를 slide의 connections 형태로 변환
390
390
  const connections = object.midpoints.map((midpoint, i) => {
391
391
  if (i === 0) {
392
392
  return {
393
- type: 'start',
393
+ type: "start",
394
394
  beat: midpoint.beat,
395
395
  lane: midpoint.lane,
396
396
  size: midpoint.size,
397
397
  trace: false,
398
398
  critical: false,
399
- ease: midpoint.ease === 'out' ? 'out'
400
- : midpoint.ease === 'linear' ? 'linear'
401
- : midpoint.ease === 'in' ? 'in'
402
- : 'linear',
399
+ ease: midpoint.ease === "out"
400
+ ? "out"
401
+ : midpoint.ease === "linear"
402
+ ? "linear"
403
+ : midpoint.ease === "in"
404
+ ? "in"
405
+ : "linear",
403
406
  };
404
407
  }
405
408
  else if (i === object.midpoints.length - 1) {
406
409
  return {
407
- type: 'end',
410
+ type: "end",
408
411
  beat: midpoint.beat,
409
412
  lane: midpoint.lane,
410
413
  size: midpoint.size,
@@ -415,21 +418,24 @@ const guide = (object, append) => {
415
418
  }
416
419
  else {
417
420
  return {
418
- type: 'tick',
421
+ type: "tick",
419
422
  beat: midpoint.beat,
420
423
  lane: midpoint.lane,
421
424
  size: midpoint.size,
422
425
  trace: false,
423
426
  critical: false,
424
- ease: midpoint.ease === 'out' ? 'out'
425
- : midpoint.ease === 'linear' ? 'linear'
426
- : midpoint.ease === 'in' ? 'in'
427
- : 'linear',
427
+ ease: midpoint.ease === "out"
428
+ ? "out"
429
+ : midpoint.ease === "linear"
430
+ ? "linear"
431
+ : midpoint.ease === "in"
432
+ ? "in"
433
+ : "linear",
428
434
  };
429
435
  }
430
436
  });
431
437
  const slideObj = {
432
- type: 'slide',
438
+ type: "slide",
433
439
  active: true,
434
440
  critical,
435
441
  connections,
@@ -453,7 +459,7 @@ const getConnections = (object) => {
453
459
  const start = Math.max(Math.ceil(min / 0.5) * 0.5, Math.floor(min / 0.5 + 1) * 0.5);
454
460
  for (let beat = start; beat < max; beat += 0.5) {
455
461
  connections.push({
456
- type: 'hidden',
462
+ type: "hidden",
457
463
  beat,
458
464
  });
459
465
  }
@@ -1,3 +1,3 @@
1
- import { LevelData } from '@sonolus/core';
2
- import { USC } from './index.cjs';
1
+ import { LevelData } from "@sonolus/core";
2
+ import { USC } from "./index.cjs";
3
3
  export declare const uscToLevelData: (usc: USC, offset?: number) => LevelData;
@@ -0,0 +1,3 @@
1
+ import { type LevelData } from "@sonolus/core";
2
+ import { type USC } from "./index.js";
3
+ export declare const uscToLevelData: (usc: USC, offset?: number) => LevelData;