sonolus-next-rush-engine 1.0.11 → 1.0.13

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.
Binary file
Binary file
Binary file
Binary file
@@ -348,6 +348,8 @@ export const extendedToLevelData = (data, offset = 0) => {
348
348
  note.set('size', getNum(e, 'size', 0.0));
349
349
  note.set('direction', flickDirectionMapping[getNum(e, 'direction', 0)] ?? FlickDirection.UP_OMNI);
350
350
  note.set('segmentKind', ConnectorKind.ACTIVE_NORMAL);
351
+ note.set('segmentAlpha', 1);
352
+ note.set('segmentLayer', 0);
351
353
  note.set('isAttached', 0);
352
354
  note.set('connectorEase', 0);
353
355
  note.set('isSeparator', 0);
@@ -363,6 +365,10 @@ export const extendedToLevelData = (data, offset = 0) => {
363
365
  return notesByName.get(ref);
364
366
  return undefined;
365
367
  }
368
+ function getTimeScaleAt(changes, beat) {
369
+ const change = [...changes].reverse().find((change) => change.beat < beat - 1e-6);
370
+ return change?.timeScale ?? 1;
371
+ }
366
372
  function shouldUseStartAsHead(startRef, headRef) {
367
373
  if (startRef === headRef)
368
374
  return false;
@@ -378,6 +384,27 @@ export const extendedToLevelData = (data, offset = 0) => {
378
384
  nearlyEqual(getNum(start, 'lane'), getNum(head, 'lane')) &&
379
385
  nearlyEqual(getNum(start, 'size'), getNum(head, 'size')));
380
386
  }
387
+ function findExistingConnectorSplitNote(beat, lane, size, tsg) {
388
+ const splitTsg = tsg ?? defaultTsg;
389
+ for (const { idx, e } of ext.notes) {
390
+ const note = notesByIndex.get(idx);
391
+ if (!note)
392
+ continue;
393
+ if (getField(e, 'attach') !== undefined)
394
+ continue;
395
+ if (!nearlyEqual(getNum(e, '#BEAT'), beat))
396
+ continue;
397
+ if (!nearlyEqual(getNum(e, 'lane'), lane))
398
+ continue;
399
+ if (!nearlyEqual(getNum(e, 'size'), size))
400
+ continue;
401
+ const noteTsg = getTSG(getField(e, 'timeScaleGroup')) ?? defaultTsg;
402
+ if (noteTsg !== splitTsg)
403
+ continue;
404
+ return note;
405
+ }
406
+ return undefined;
407
+ }
381
408
  function createConnectorAnchor(beat, lane, size, tsg, kind) {
382
409
  const anchor = new EntityBuilder('AnchorNote');
383
410
  anchor.set('#BEAT', beat);
@@ -404,8 +431,12 @@ export const extendedToLevelData = (data, offset = 0) => {
404
431
  const headChanges = getTSGChanges(headTsgRef);
405
432
  const tailChanges = getTSGChanges(tailTsgRef);
406
433
  const splitBeats = headChanges
407
- .map(({ beat }) => beat)
408
- .filter((beat) => headBeat + 1e-6 < beat && beat < tailBeat - 1e-6);
434
+ .filter((change) => {
435
+ if (!(headBeat + 1e-6 < change.beat && change.beat < tailBeat - 1e-6))
436
+ return false;
437
+ return !nearlyEqual(change.timeScale, getTimeScaleAt(headChanges, change.beat));
438
+ })
439
+ .map(({ beat }) => beat);
409
440
  if (splitBeats.length === 0)
410
441
  return [];
411
442
  const headScaledTime = timeToScaledTime(beatToTime(headBeat), headChanges);
@@ -433,7 +464,10 @@ export const extendedToLevelData = (data, offset = 0) => {
433
464
  const scaledTime = timeToScaledTime(beatToTime(beat), headChanges);
434
465
  const frac = unlerp(headScaledTime, tailScaledTime, scaledTime);
435
466
  const easedFrac = applyEase(ease, frac);
436
- return createConnectorAnchor(beat, lerp(headLane, tailLane, easedFrac), lerp(headSize, tailSize, easedFrac), tsg, kind);
467
+ const lane = lerp(headLane, tailLane, easedFrac);
468
+ const size = lerp(headSize, tailSize, easedFrac);
469
+ return (findExistingConnectorSplitNote(beat, lane, size, tsg) ??
470
+ createConnectorAnchor(beat, lane, size, tsg, kind));
437
471
  });
438
472
  }
439
473
  function isReverseHiddenPopConnector(headOriginal, tailOriginal) {
@@ -445,6 +479,31 @@ export const extendedToLevelData = (data, offset = 0) => {
445
479
  return false;
446
480
  return getNum(tailOriginal, '#BEAT') < getNum(headOriginal, '#BEAT') - 1e-6;
447
481
  }
482
+ function getUltimateTailRef(startRef, tailRef) {
483
+ let ultimateTailRef = tailRef;
484
+ let ultimateTailBeat = getNum(resolveOriginal(ext, tailRef) ?? { archetype: '', data: [] }, '#BEAT');
485
+ const visited = new Set();
486
+ function visit(headRef) {
487
+ const key = `${String(startRef)}|${String(headRef)}`;
488
+ if (headRef === undefined || visited.has(key))
489
+ return;
490
+ visited.add(key);
491
+ const nextConnectors = ext.connectors.filter((c) => getField(c.e, 'head') === headRef && getField(c.e, 'start') === startRef);
492
+ if (nextConnectors.length === 0) {
493
+ const beat = getNum(resolveOriginal(ext, headRef) ?? { archetype: '', data: [] }, '#BEAT');
494
+ if (beat >= ultimateTailBeat) {
495
+ ultimateTailBeat = beat;
496
+ ultimateTailRef = headRef;
497
+ }
498
+ return;
499
+ }
500
+ for (const nextConnector of nextConnectors) {
501
+ visit(getField(nextConnector.e, 'tail'));
502
+ }
503
+ }
504
+ visit(tailRef);
505
+ return ultimateTailRef;
506
+ }
448
507
  for (const { idx, e } of ext.connectors) {
449
508
  const startRef = getField(e, 'start');
450
509
  const headRef = getField(e, 'head');
@@ -460,21 +519,7 @@ export const extendedToLevelData = (data, offset = 0) => {
460
519
  const endRef = getField(e, 'end');
461
520
  let activeTail = getNote(endRef);
462
521
  if (!activeTail) {
463
- const currentTailRef = getField(e, 'tail');
464
- let ultimateTailRef = currentTailRef;
465
- const visited = new Set();
466
- while (ultimateTailRef !== undefined && !visited.has(ultimateTailRef)) {
467
- visited.add(ultimateTailRef);
468
- const nextConn = ext.connectors.find((c) => getField(c.e, 'head') === ultimateTailRef &&
469
- getField(c.e, 'start') === startRef);
470
- if (nextConn) {
471
- ultimateTailRef = getField(nextConn.e, 'tail');
472
- }
473
- else {
474
- break;
475
- }
476
- }
477
- activeTail = getNote(ultimateTailRef);
522
+ activeTail = getNote(getUltimateTailRef(startRef, getField(e, 'tail')));
478
523
  }
479
524
  if (!activeTail) {
480
525
  activeTail = tail;
@@ -490,6 +535,7 @@ export const extendedToLevelData = (data, offset = 0) => {
490
535
  : [];
491
536
  const segmentEase = splitAnchors.length > 0 ? EaseType.LINEAR : ease;
492
537
  const segmentNotes = [head, ...splitAnchors, tail];
538
+ const segments = [];
493
539
  if (reverseHiddenPopConnector && rawHeadOriginal && tailOriginal) {
494
540
  const segmentHead = createConnectorAnchor(getNum(rawHeadOriginal, '#BEAT'), getNum(rawHeadOriginal, 'lane'), getNum(rawHeadOriginal, 'size'), getTSG(getField(rawHeadOriginal, 'timeScaleGroup')) ?? tsg, kind);
495
541
  const connector = new EntityBuilder('Connector');
@@ -501,6 +547,7 @@ export const extendedToLevelData = (data, offset = 0) => {
501
547
  connector.set('activeHead', activeHead);
502
548
  connector.set('activeTail', activeTail);
503
549
  finalEntities.push(connector);
550
+ segments.push({ head: segmentHead, tail });
504
551
  }
505
552
  else {
506
553
  for (let i = 0; i < segmentNotes.length - 1; i++) {
@@ -514,13 +561,16 @@ export const extendedToLevelData = (data, offset = 0) => {
514
561
  connector.set('activeHead', activeHead);
515
562
  connector.set('activeTail', activeTail);
516
563
  finalEntities.push(connector);
564
+ segments.push({ head: segmentHead, tail: segmentTail });
517
565
  }
518
566
  }
519
- const connectorLink = new EntityBuilder('Connector');
520
- connectorLink.set('head', head);
521
- connectorLink.set('tail', tail);
522
- connectorLink.set('activeHead', activeHead);
523
- connectorLink.set('activeTail', activeTail);
567
+ const connectorLink = {
568
+ head,
569
+ tail,
570
+ activeHead,
571
+ activeTail,
572
+ segments,
573
+ };
524
574
  for (const segmentHead of segmentNotes.slice(0, -1)) {
525
575
  segmentHead.set('connectorEase', segmentEase);
526
576
  segmentHead.set('segmentKind', kind);
@@ -540,6 +590,18 @@ export const extendedToLevelData = (data, offset = 0) => {
540
590
  return connectorsByName.get(ref);
541
591
  return undefined;
542
592
  }
593
+ function getAttachSegment(conn, beat) {
594
+ for (const segment of conn.segments) {
595
+ const headBeat = segment.head.getBeat();
596
+ const tailBeat = segment.tail.getBeat();
597
+ const minBeat = Math.min(headBeat, tailBeat);
598
+ const maxBeat = Math.max(headBeat, tailBeat);
599
+ if (minBeat - 1e-6 <= beat && beat <= maxBeat + 1e-6) {
600
+ return segment;
601
+ }
602
+ }
603
+ return { head: conn.head, tail: conn.tail };
604
+ }
543
605
  for (const [idx, note] of notesByIndex.entries()) {
544
606
  const e = ext.get(idx);
545
607
  const tsgRef = getField(e, 'timeScaleGroup');
@@ -548,17 +610,18 @@ export const extendedToLevelData = (data, offset = 0) => {
548
610
  const attachRef = getField(e, 'attach');
549
611
  if (attachRef !== undefined) {
550
612
  const attachConn = getConn(attachRef);
551
- if (attachConn?.refs.head && attachConn.refs.tail) {
552
- note.set('attachHead', attachConn.refs.head);
553
- note.set('attachTail', attachConn.refs.tail);
613
+ if (attachConn) {
614
+ const attachSegment = getAttachSegment(attachConn, getNum(e, '#BEAT'));
615
+ note.set('attachHead', attachSegment.head);
616
+ note.set('attachTail', attachSegment.tail);
554
617
  note.set('isAttached', 1);
555
618
  }
556
619
  }
557
620
  const slideRef = getField(e, 'slide');
558
621
  if (slideRef !== undefined) {
559
622
  const slideConn = getConn(slideRef);
560
- if (slideConn?.refs.activeHead) {
561
- note.set('activeHead', slideConn.refs.activeHead);
623
+ if (slideConn) {
624
+ note.set('activeHead', slideConn.activeHead);
562
625
  }
563
626
  }
564
627
  }
@@ -575,6 +638,7 @@ export const extendedToLevelData = (data, offset = 0) => {
575
638
  const anchorsByBeat = new Map();
576
639
  const anchorPositions = new Map();
577
640
  function getAnchor(beat, lane, size, tsg, pos, segmentKind = -1, segmentAlpha = -1, connectorEase = -1) {
641
+ const anchorTsg = tsg ?? defaultTsg;
578
642
  const anchors = anchorsByBeat.get(beat) || [];
579
643
  for (const anchor of anchors) {
580
644
  const positions = anchorPositions.get(anchor);
@@ -582,7 +646,7 @@ export const extendedToLevelData = (data, offset = 0) => {
582
646
  continue;
583
647
  if (anchor.values.lane === lane &&
584
648
  anchor.values.size === size &&
585
- anchor.refs['#TIMESCALE_GROUP'] === tsg &&
649
+ anchor.refs['#TIMESCALE_GROUP'] === anchorTsg &&
586
650
  (segmentKind === -1 ||
587
651
  anchor.values.segmentKind === segmentKind ||
588
652
  anchor.values.segmentKind === -1) &&
@@ -606,10 +670,11 @@ export const extendedToLevelData = (data, offset = 0) => {
606
670
  newAnchor.set('#BEAT', beat);
607
671
  newAnchor.set('lane', lane);
608
672
  newAnchor.set('size', size);
609
- if (tsg)
610
- newAnchor.set('#TIMESCALE_GROUP', tsg);
673
+ newAnchor.set('direction', FlickDirection.UP_OMNI);
674
+ newAnchor.set('#TIMESCALE_GROUP', anchorTsg);
611
675
  newAnchor.set('segmentKind', segmentKind);
612
676
  newAnchor.set('segmentAlpha', segmentAlpha);
677
+ newAnchor.set('segmentLayer', 0);
613
678
  newAnchor.set('connectorEase', connectorEase);
614
679
  newAnchor.set('isAttached', 0);
615
680
  newAnchor.set('isSeparator', 0);
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { USC } from './usc/index.js';
7
7
  export * from './usc/index.js';
8
8
  export { type ExtendedEntityData, type ExtendedEntityDataField, extendedToLevelData, mmwsToUSC, susToUSC, ucmmwsToLevelData, uscToLevelData, };
9
9
  export declare const convertToLevelData: (input: string | Uint8Array | USC | LevelData, offset?: number) => LevelData;
10
- export declare const version = "1.0.11";
10
+ export declare const version = "1.0.13";
11
11
  export declare const databaseEngineItem: {
12
12
  readonly name: "next-rush";
13
13
  readonly version: 13;
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ export const convertToLevelData = (input, offset = 0) => {
65
65
  }
66
66
  return uscToLevelData(usc, offset, true, true);
67
67
  };
68
- export const version = '1.0.11';
68
+ export const version = '1.0.13';
69
69
  export const databaseEngineItem = {
70
70
  name: 'next-rush',
71
71
  version: 13,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonolus-next-rush-engine",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Perspective-lane rhythm game for Sonolus",
5
5
  "author": "Hyeon2",
6
6
  "repository": {