@rian8337/osu-base 3.0.0-beta.1 → 3.0.0-beta.10

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/dist/index.js CHANGED
@@ -344,9 +344,7 @@ class APIRequestBuilder {
344
344
  * @param param The parameter to remove.
345
345
  */
346
346
  removeParameter(param) {
347
- if (this.params.get(param)) {
348
- this.params.delete(param);
349
- }
347
+ this.params.delete(param);
350
348
  return this;
351
349
  }
352
350
  }
@@ -357,18 +355,6 @@ class DroidAPIRequestBuilder extends APIRequestBuilder {
357
355
  host = "https://osudroid.moe/api/";
358
356
  APIkey = process.env.DROID_API_KEY;
359
357
  APIkeyParam = `apiKey=${this.APIkey}&`;
360
- setEndpoint(endpoint) {
361
- // Compatibility with old API. Can be removed in v3.0.
362
- switch (endpoint) {
363
- case "banscore.php":
364
- endpoint = "single_score_wipe.php";
365
- break;
366
- case "rename.php":
367
- endpoint = "user_rename.php";
368
- break;
369
- }
370
- return super.setEndpoint(endpoint);
371
- }
372
358
  }
373
359
  /**
374
360
  * API request builder for osu!standard.
@@ -379,6 +365,15 @@ class OsuAPIRequestBuilder extends APIRequestBuilder {
379
365
  APIkeyParam = `k=${this.APIkey}&`;
380
366
  }
381
367
 
368
+ /**
369
+ * Mode enum to switch things between osu!droid and osu!standard.
370
+ */
371
+ exports.modes = void 0;
372
+ (function (modes) {
373
+ modes["droid"] = "droid";
374
+ modes["osu"] = "osu";
375
+ })(exports.modes || (exports.modes = {}));
376
+
382
377
  /**
383
378
  * Bitmask constant of object types. This is needed as osu! uses bits to determine object types.
384
379
  */
@@ -396,11 +391,11 @@ exports.objectTypes = void 0;
396
391
  */
397
392
  class Vector2 {
398
393
  /**
399
- * The x position of the vector.
394
+ * The x position of this vector.
400
395
  */
401
396
  x;
402
397
  /**
403
- * The y position of the vector.
398
+ * The y position of this vector.
404
399
  */
405
400
  y;
406
401
  constructor(x, y) {
@@ -408,11 +403,22 @@ class Vector2 {
408
403
  this.y = y;
409
404
  }
410
405
  /**
411
- * Multiplies the vector with another vector.
406
+ * Multiplies this vector with another vector.
407
+ *
408
+ * @param vec The other vector.
409
+ * @returns The multiplied vector.
412
410
  */
413
411
  multiply(vec) {
414
412
  return new Vector2(this.x * vec.x, this.y * vec.y);
415
413
  }
414
+ /**
415
+ * Divides this vector with a scalar.
416
+ *
417
+ * Attempting to divide by 0 will throw an error.
418
+ *
419
+ * @param divideFactor The factor to divide the vector by.
420
+ * @returns The divided vector.
421
+ */
416
422
  divide(divideFactor) {
417
423
  if (divideFactor === 0) {
418
424
  throw new Error("Division by 0");
@@ -420,37 +426,52 @@ class Vector2 {
420
426
  return new Vector2(this.x / divideFactor, this.y / divideFactor);
421
427
  }
422
428
  /**
423
- * Adds the vector with another vector.
429
+ * Adds this vector with another vector.
430
+ *
431
+ * @param vec The other vector.
432
+ * @returns The added vector.
424
433
  */
425
434
  add(vec) {
426
435
  return new Vector2(this.x + vec.x, this.y + vec.y);
427
436
  }
428
437
  /**
429
- * Subtracts the vector with another vector.
438
+ * Subtracts this vector with another vector.
439
+ *
440
+ * @param vec The other vector.
441
+ * @returns The subtracted vector.
430
442
  */
431
443
  subtract(vec) {
432
444
  return new Vector2(this.x - vec.x, this.y - vec.y);
433
445
  }
434
446
  /**
435
- * The length of the vector.
447
+ * The length of this vector.
436
448
  */
437
449
  get length() {
438
450
  return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
439
451
  }
440
452
  /**
441
453
  * Performs a dot multiplication with another vector.
454
+ *
455
+ * @param vec The other vector.
456
+ * @returns The dot product of both vectors.
442
457
  */
443
458
  dot(vec) {
444
459
  return this.x * vec.x + this.y * vec.y;
445
460
  }
446
461
  /**
447
- * Scales the vector.
462
+ * Scales this vector.
463
+ *
464
+ * @param scaleFactor The factor to scale the vector by.
465
+ * @returns The scaled vector.
448
466
  */
449
467
  scale(scaleFactor) {
450
468
  return new Vector2(this.x * scaleFactor, this.y * scaleFactor);
451
469
  }
452
470
  /**
453
471
  * Gets the distance between this vector and another vector.
472
+ *
473
+ * @param vec The other vector.
474
+ * @returns The distance between this vector and the other vector.
454
475
  */
455
476
  getDistance(vec) {
456
477
  const x = this.x - vec.x;
@@ -469,6 +490,7 @@ class Vector2 {
469
490
  * Checks whether this vector is equal to another vector.
470
491
  *
471
492
  * @param other The other vector.
493
+ * @returns Whether this vector is equal to the other vector.
472
494
  */
473
495
  equals(other) {
474
496
  return this.x === other.x && this.y === other.y;
@@ -511,31 +533,6 @@ class HitObject {
511
533
  get duration() {
512
534
  return this.endTime - this.startTime;
513
535
  }
514
- /**
515
- * The stacked position of the hitobject.
516
- */
517
- get stackedPosition() {
518
- if (this.type & exports.objectTypes.spinner) {
519
- return this.position;
520
- }
521
- return this.position.add(this.stackOffset);
522
- }
523
- /**
524
- * The stacked end position of the hitobject.
525
- */
526
- get stackedEndPosition() {
527
- if (this.type & exports.objectTypes.spinner) {
528
- return this.position;
529
- }
530
- return this.endPosition.add(this.stackOffset);
531
- }
532
- /**
533
- * The stack vector to calculate offset for stacked positions.
534
- */
535
- get stackOffset() {
536
- const coordinate = this.stackHeight * this.scale * -6.4;
537
- return new Vector2(coordinate, coordinate);
538
- }
539
536
  /**
540
537
  * Whether this hit object represents a new combo.
541
538
  */
@@ -556,15 +553,13 @@ class HitObject {
556
553
  */
557
554
  stackHeight = 0;
558
555
  /**
559
- * The scale used to calculate stacked position and radius.
556
+ * The osu!droid scale used to calculate stacked position and radius.
560
557
  */
561
- scale = 1;
558
+ droidScale = 1;
562
559
  /**
563
- * The radius of the hitobject.
560
+ * The osu!standard scale used to calculate stacked position and radius.
564
561
  */
565
- get radius() {
566
- return 64 * this.scale;
567
- }
562
+ osuScale = 1;
568
563
  constructor(values) {
569
564
  this.startTime = values.startTime;
570
565
  this.endTime = values.endTime ?? values.startTime;
@@ -574,6 +569,62 @@ class HitObject {
574
569
  this.isNewCombo = values.newCombo ?? false;
575
570
  this.comboOffset = values.comboOffset ?? 0;
576
571
  }
572
+ /**
573
+ * Evaluates the radius of the hitobject.
574
+ *
575
+ * @param mode The gamemode to evaluate for.
576
+ * @returns The radius of the hitobject with respect to the gamemode.
577
+ */
578
+ getRadius(mode) {
579
+ let radius = 64;
580
+ switch (mode) {
581
+ case exports.modes.droid:
582
+ radius *= this.droidScale;
583
+ break;
584
+ case exports.modes.osu:
585
+ radius *= this.osuScale;
586
+ break;
587
+ }
588
+ return radius;
589
+ }
590
+ /**
591
+ * Evaluates the stack offset vector of the hitobject.
592
+ *
593
+ * This is used to calculate offset for stacked positions.
594
+ *
595
+ * @param mode The gamemode to evaluate for.
596
+ * @returns The stack offset with respect to the gamemode.
597
+ */
598
+ getStackOffset(mode) {
599
+ let coordinate = this.stackHeight * -6.4;
600
+ switch (mode) {
601
+ case exports.modes.droid:
602
+ coordinate *= this.droidScale;
603
+ break;
604
+ case exports.modes.osu:
605
+ coordinate *= this.osuScale;
606
+ break;
607
+ }
608
+ return new Vector2(coordinate, coordinate);
609
+ }
610
+ /**
611
+ * Evaluates the stacked position of the hitobject.
612
+ *
613
+ * @param mode The gamemode to evaluate for.
614
+ * @returns The stacked position with respect to the gamemode.
615
+ */
616
+ getStackedPosition(mode) {
617
+ return this.evaluateStackedPosition(this.position, mode);
618
+ }
619
+ /**
620
+ * Evaluates the stacked end position of the hitobject.
621
+ *
622
+ * @param mode The gamemode to evaluate for.
623
+ * @returns The stacked end position with respect to the gamemode.
624
+ */
625
+ getStackedEndPosition(mode) {
626
+ return this.evaluateStackedPosition(this.endPosition, mode);
627
+ }
577
628
  /**
578
629
  * Returns the hitobject type.
579
630
  */
@@ -590,6 +641,19 @@ class HitObject {
590
641
  }
591
642
  return res.substring(0, Math.max(0, res.length - 3));
592
643
  }
644
+ /**
645
+ * Evaluates the stacked position of the specified position.
646
+ *
647
+ * @param position The position to evaluate.
648
+ * @param mode The gamemode to evaluate for.
649
+ * @returns The stacked position.
650
+ */
651
+ evaluateStackedPosition(position, mode) {
652
+ if (this.type & exports.objectTypes.spinner) {
653
+ return position;
654
+ }
655
+ return position.add(this.getStackOffset(mode));
656
+ }
593
657
  }
594
658
 
595
659
  /**
@@ -1193,15 +1257,7 @@ class ControlPointManager {
1193
1257
  }
1194
1258
  existing = this.controlPointAt(controlPoint.time);
1195
1259
  }
1196
- // Get the index at which to add the control point.
1197
- for (let i = 0; i < this._points.length; ++i) {
1198
- if (this._points[i].time >= controlPoint.time) {
1199
- this._points.splice(i - 1, 0, controlPoint);
1200
- return true;
1201
- }
1202
- }
1203
- // Append the control point if it hasn't been added yet.
1204
- this._points.push(controlPoint);
1260
+ this._points.splice(this.findInsertionIndex(controlPoint.time), 0, controlPoint);
1205
1261
  return true;
1206
1262
  }
1207
1263
  /**
@@ -1274,9 +1330,37 @@ class ControlPointManager {
1274
1330
  return this._points[pivot];
1275
1331
  }
1276
1332
  }
1277
- // l will be the first control point with time > this.controlPoints[l].time, but we want the one before it
1333
+ // l will be the first control point with time > this._points[l].time, but we want the one before it
1278
1334
  return this._points[l - 1];
1279
1335
  }
1336
+ /**
1337
+ * Finds the insertion index of a control point in a given time.
1338
+ *
1339
+ * @param time The start time of the control point.
1340
+ */
1341
+ findInsertionIndex(time) {
1342
+ if (this._points.length === 0 || time < this._points[0].time) {
1343
+ return 0;
1344
+ }
1345
+ if (time >= this._points.at(-1).time) {
1346
+ return this._points.length;
1347
+ }
1348
+ let l = 0;
1349
+ let r = this._points.length - 2;
1350
+ while (l <= r) {
1351
+ const pivot = l + ((r - l) >> 1);
1352
+ if (this._points[pivot].time < time) {
1353
+ l = pivot + 1;
1354
+ }
1355
+ else if (this._points[pivot].time > time) {
1356
+ r = pivot - 1;
1357
+ }
1358
+ else {
1359
+ return pivot;
1360
+ }
1361
+ }
1362
+ return l;
1363
+ }
1280
1364
  }
1281
1365
 
1282
1366
  /**
@@ -1573,7 +1657,7 @@ class BeatmapHitObjects {
1573
1657
  }
1574
1658
  }
1575
1659
  }
1576
- else if (object instanceof Spinner) {
1660
+ else {
1577
1661
  ++this._spinners;
1578
1662
  }
1579
1663
  }
@@ -1622,12 +1706,28 @@ class BeatmapHitObjects {
1622
1706
  * @param startTime The start time of the hitobject.
1623
1707
  */
1624
1708
  findInsertionIndex(startTime) {
1625
- for (let i = 0; i < this.objects.length; ++i) {
1626
- if (this.objects[i].startTime > startTime) {
1627
- return i;
1709
+ if (this._objects.length === 0 ||
1710
+ startTime < this._objects[0].startTime) {
1711
+ return 0;
1712
+ }
1713
+ if (startTime >= this._objects.at(-1).startTime) {
1714
+ return this._objects.length;
1715
+ }
1716
+ let l = 0;
1717
+ let r = this._objects.length - 2;
1718
+ while (l <= r) {
1719
+ const pivot = l + ((r - l) >> 1);
1720
+ if (this._objects[pivot].startTime < startTime) {
1721
+ l = pivot + 1;
1722
+ }
1723
+ else if (this._objects[pivot].startTime > startTime) {
1724
+ r = pivot - 1;
1725
+ }
1726
+ else {
1727
+ return pivot;
1628
1728
  }
1629
1729
  }
1630
- return this.objects.length;
1730
+ return l;
1631
1731
  }
1632
1732
  }
1633
1733
 
@@ -1893,15 +1993,6 @@ class BeatmapBackground {
1893
1993
  }
1894
1994
  }
1895
1995
 
1896
- /**
1897
- * Mode enum to switch things between osu!droid and osu!standard.
1898
- */
1899
- exports.modes = void 0;
1900
- (function (modes) {
1901
- modes["droid"] = "droid";
1902
- modes["osu"] = "osu";
1903
- })(exports.modes || (exports.modes = {}));
1904
-
1905
1996
  class HitWindow {
1906
1997
  /**
1907
1998
  * The overall difficulty of this hit window.
@@ -2373,7 +2464,7 @@ class ModUtil {
2373
2464
  * @param options Options for parsing behavior.
2374
2465
  */
2375
2466
  static pcModbitsToMods(modbits, options) {
2376
- return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToOsu() && (m.bitwise & modbits)), options);
2467
+ return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToOsu() && m.bitwise & modbits), options);
2377
2468
  }
2378
2469
  /**
2379
2470
  * Gets a list of mods from a PC mod string, such as "HDHR".
@@ -2416,8 +2507,7 @@ class ModUtil {
2416
2507
  for (const incompatibleMod of this.incompatibleMods) {
2417
2508
  const fulfilledMods = mods.filter((m) => incompatibleMod.some((v) => m.acronym === v.acronym));
2418
2509
  if (fulfilledMods.length > 1) {
2419
- mods = mods.filter((m) => incompatibleMod
2420
- .every((v) => m.acronym !== v.acronym));
2510
+ mods = mods.filter((m) => incompatibleMod.every((v) => m.acronym !== v.acronym));
2421
2511
  // Keep the first selected mod
2422
2512
  mods.push(fulfilledMods[0]);
2423
2513
  }
@@ -2743,6 +2833,49 @@ class Precision {
2743
2833
  return (this.almostEqualsNumber(vec1.x, vec2.x, acceptableDifference) &&
2744
2834
  this.almostEqualsNumber(vec1.y, vec2.y, acceptableDifference));
2745
2835
  }
2836
+ /**
2837
+ * Checks whether two real numbers are almost equal.
2838
+ *
2839
+ * @param a The first number.
2840
+ * @param b The second number.
2841
+ * @param maximumError The accuracy required for being almost equal. Defaults to `10 * 2^(-53)`.
2842
+ * @returns Whether the two values differ by no more than 10 * 2^(-52).
2843
+ */
2844
+ static almostEqualRelative(a, b, maximumError = 10 * Math.pow(2, -53)) {
2845
+ return this.almostEqualNormRelative(a, b, a - b, maximumError);
2846
+ }
2847
+ /**
2848
+ * Compares two numbers and determines if they are equal within the specified maximum error.
2849
+ *
2850
+ * @param a The norm of the first value (can be negative).
2851
+ * @param b The norm of the second value (can be negative).
2852
+ * @param diff The norm of the difference of the two values (can be negative).
2853
+ * @param maximumError The accuracy required for being almost equal.
2854
+ * @returns Whether both numbers are almost equal up to the specified maximum error.
2855
+ */
2856
+ static almostEqualNormRelative(a, b, diff, maximumError) {
2857
+ // If A or B are infinity (positive or negative) then
2858
+ // only return true if they are exactly equal to each other -
2859
+ // that is, if they are both infinities of the same sign.
2860
+ if (!Number.isFinite(a) || !Number.isFinite(b)) {
2861
+ return a === b;
2862
+ }
2863
+ // If A or B are a NAN, return false. NANs are equal to nothing,
2864
+ // not even themselves.
2865
+ if (Number.isNaN(a) || Number.isNaN(b)) {
2866
+ return false;
2867
+ }
2868
+ // If one is almost zero, fall back to absolute equality.
2869
+ const doublePrecision = Math.pow(2, -53);
2870
+ if (Math.abs(a) < doublePrecision || Math.abs(b) < doublePrecision) {
2871
+ return Math.abs(diff) < maximumError;
2872
+ }
2873
+ if ((a === 0 && Math.abs(b) < maximumError) ||
2874
+ (b === 0 && Math.abs(a) < maximumError)) {
2875
+ return true;
2876
+ }
2877
+ return (Math.abs(diff) < maximumError * Math.max(Math.abs(a), Math.abs(b)));
2878
+ }
2746
2879
  }
2747
2880
 
2748
2881
  /**
@@ -4302,7 +4435,12 @@ class RGBColor {
4302
4435
  * Returns a string representation of the color.
4303
4436
  */
4304
4437
  toString() {
4305
- return `${this.r},${this.g},${this.b}`;
4438
+ if (this.a === 1) {
4439
+ return `${this.r},${this.g},${this.b}`;
4440
+ }
4441
+ else {
4442
+ return `${this.r},${this.g},${this.b},${this.a}`;
4443
+ }
4306
4444
  }
4307
4445
  /**
4308
4446
  * Checks whether this color is equal to another color.
@@ -4310,7 +4448,10 @@ class RGBColor {
4310
4448
  * @param other The other color.
4311
4449
  */
4312
4450
  equals(other) {
4313
- return this.r === other.r && this.g === other.g && this.b === other.b;
4451
+ return (this.r === other.r &&
4452
+ this.g === other.g &&
4453
+ this.b === other.b &&
4454
+ this.a === other.a);
4314
4455
  }
4315
4456
  }
4316
4457
 
@@ -4664,21 +4805,11 @@ class CommandTimelineGroup {
4664
4805
  this.flipHorizontal,
4665
4806
  this.flipVertical,
4666
4807
  ];
4667
- /**
4668
- * The earliest visible time. Will be `null` unless this group's first alpha command has a start value of zero.
4669
- */
4670
- get earliestDisplayedTime() {
4671
- const first = this.alpha.commands.at(0);
4672
- return first?.startValue === 0 ? first.startTime : null;
4673
- }
4674
4808
  /**
4675
4809
  * The start time of commands.
4676
4810
  */
4677
4811
  get commandsStartTime() {
4678
- // If the first alpha command starts at zero it should be given priority over anything else.
4679
- // This is due to it creating a state where the target is not present before that time, causing any other events to not be visible.
4680
- return (this.earliestDisplayedTime ??
4681
- Math.min(...this.timelines.map((t) => t.startTime)));
4812
+ return Math.min(...this.timelines.map((t) => t.startTime));
4682
4813
  }
4683
4814
  /**
4684
4815
  * The end time of commands.
@@ -4719,7 +4850,7 @@ class CommandTimelineGroup {
4719
4850
  /**
4720
4851
  * Gets the commands from a command timeline.
4721
4852
  *
4722
- * @param timelineSelector A function that returns a command timeline.
4853
+ * @param timelineSelector A function to select the command timeline to retrieve commands from.
4723
4854
  * @param offset The offset to apply to all commands.
4724
4855
  */
4725
4856
  getCommands(timelineSelector, offset = 0) {
@@ -4831,20 +4962,14 @@ class StoryboardElement {
4831
4962
  * Represents a storyboard sprite.
4832
4963
  */
4833
4964
  class StoryboardSprite extends StoryboardElement {
4834
- _loops = [];
4835
- _triggers = [];
4836
4965
  /**
4837
4966
  * The loop commands of the sprite.
4838
4967
  */
4839
- get loops() {
4840
- return this._loops;
4841
- }
4968
+ loops = [];
4842
4969
  /**
4843
4970
  * The trigger commands of the sprite.
4844
4971
  */
4845
- get triggers() {
4846
- return this._triggers;
4847
- }
4972
+ triggers = [];
4848
4973
  /**
4849
4974
  * The origin of the sprite.
4850
4975
  */
@@ -4858,28 +4983,57 @@ class StoryboardSprite extends StoryboardElement {
4858
4983
  */
4859
4984
  timelineGroup = new CommandTimelineGroup();
4860
4985
  get startTime() {
4861
- // Check for presence affecting commands as an initial pass.
4862
- let earliestStartTime = this.timelineGroup.earliestDisplayedTime ??
4863
- Number.POSITIVE_INFINITY;
4864
- for (const l of this._loops) {
4865
- const { earliestDisplayedTime: loopEarliestDisplayTime } = l;
4866
- if (loopEarliestDisplayTime !== null) {
4867
- earliestStartTime = Math.min(earliestStartTime, l.loopStartTime + loopEarliestDisplayTime);
4986
+ // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a start value of zero.
4987
+ // A start value of zero governs, above all else, the first valid display time of a sprite.
4988
+ //
4989
+ // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero,
4990
+ // anything before that point can be ignored (the sprite is not visible after all).
4991
+ const alphaCommands = [];
4992
+ let command = this.timelineGroup.alpha.commands[0];
4993
+ if (command) {
4994
+ alphaCommands.push({
4995
+ startTime: command.startTime,
4996
+ isZeroStartValue: command.startValue === 0,
4997
+ });
4998
+ }
4999
+ for (const l of this.loops) {
5000
+ command = l.alpha.commands[0];
5001
+ if (command) {
5002
+ alphaCommands.push({
5003
+ startTime: command.startTime + l.loopStartTime,
5004
+ isZeroStartValue: command.startValue === 0,
5005
+ });
4868
5006
  }
4869
5007
  }
4870
- return earliestStartTime !== Number.POSITIVE_INFINITY
4871
- ? earliestStartTime
4872
- : Math.min(this.timelineGroup.startTime, ...this._loops.map((l) => l.startTime));
5008
+ if (alphaCommands.length > 0) {
5009
+ const firstAlpha = alphaCommands.sort((a, b) => a.startTime - b.startTime)[0];
5010
+ if (firstAlpha.isZeroStartValue) {
5011
+ return firstAlpha.startTime;
5012
+ }
5013
+ }
5014
+ return this.earliestTransformTime;
5015
+ }
5016
+ /**
5017
+ * The time at which the first transformation occurs.
5018
+ */
5019
+ get earliestTransformTime() {
5020
+ // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
5021
+ // The sprite's start time will be determined by the earliest command, regardless of type.
5022
+ let earliestStartTime = this.timelineGroup.startTime;
5023
+ for (const l of this.loops) {
5024
+ earliestStartTime = Math.min(earliestStartTime, l.startTime);
5025
+ }
5026
+ return earliestStartTime;
4873
5027
  }
4874
5028
  get endTime() {
4875
- return Math.max(this.timelineGroup.endTime, ...this._loops.map((l) => l.endTime));
5029
+ return Math.max(this.timelineGroup.endTime, ...this.loops.map((l) => l.endTime));
4876
5030
  }
4877
5031
  /**
4878
5032
  * Whether this sprite has at least one command.
4879
5033
  */
4880
5034
  get hasCommands() {
4881
5035
  return (this.timelineGroup.hasCommands ||
4882
- this._loops.some((l) => l.hasCommands));
5036
+ this.loops.some((l) => l.hasCommands));
4883
5037
  }
4884
5038
  constructor(path, origin, initialPosition) {
4885
5039
  super(path);
@@ -4895,7 +5049,7 @@ class StoryboardSprite extends StoryboardElement {
4895
5049
  */
4896
5050
  addLoop(startTime, repeatCount) {
4897
5051
  const loop = new CommandLoop(startTime, repeatCount);
4898
- this._loops.push(loop);
5052
+ this.loops.push(loop);
4899
5053
  return loop;
4900
5054
  }
4901
5055
  /**
@@ -4909,7 +5063,7 @@ class StoryboardSprite extends StoryboardElement {
4909
5063
  */
4910
5064
  addTrigger(triggerName, startTime, endTime, groupNumber) {
4911
5065
  const trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber);
4912
- this._triggers.push(trigger);
5066
+ this.triggers.push(trigger);
4913
5067
  return trigger;
4914
5068
  }
4915
5069
  toString() {
@@ -5349,16 +5503,23 @@ class BeatmapDecoder extends Decoder {
5349
5503
  else {
5350
5504
  hitObjectsDecoder.applyStackingOld();
5351
5505
  }
5352
- const circleSize = new MapStats({
5506
+ const droidCircleSize = new MapStats({
5507
+ cs: this.finalResult.difficulty.cs,
5508
+ mods,
5509
+ }).calculate({ mode: exports.modes.droid }).cs;
5510
+ const droidScale = (1 - (0.7 * (droidCircleSize - 5)) / 5) / 2;
5511
+ const osuCircleSize = new MapStats({
5353
5512
  cs: this.finalResult.difficulty.cs,
5354
5513
  mods,
5355
- }).calculate().cs;
5356
- const scale = (1 - (0.7 * (circleSize - 5)) / 5) / 2;
5514
+ }).calculate({ mode: exports.modes.osu }).cs;
5515
+ const osuScale = (1 - (0.7 * (osuCircleSize - 5)) / 5) / 2;
5357
5516
  this.finalResult.hitObjects.objects.forEach((h) => {
5358
- h.scale = scale;
5517
+ h.droidScale = droidScale;
5518
+ h.osuScale = osuScale;
5359
5519
  if (h instanceof Slider) {
5360
5520
  h.nestedHitObjects.forEach((n) => {
5361
- n.scale = scale;
5521
+ n.droidScale = droidScale;
5522
+ n.osuScale = osuScale;
5362
5523
  });
5363
5524
  }
5364
5525
  });
@@ -6118,6 +6279,236 @@ class BeatmapEncoder extends Encoder {
6118
6279
  }
6119
6280
  }
6120
6281
 
6282
+ class ZeroCrossingBracketing {
6283
+ /**
6284
+ * Detect a range containing at least one root.
6285
+ *
6286
+ * This iterative method stops when two values with opposite signs are found.
6287
+ *
6288
+ * @param f The function to detect roots from.
6289
+ * @param bounds The upper and lower value of the range.
6290
+ * @param factor The growing factor of research. Defaults to 1.6.
6291
+ * @param maxIterations Maximum number of iterations. Defaults to 50.
6292
+ * @returns Whether the bracketing operation succeeded.
6293
+ */
6294
+ static expand(f, bounds, factor = 1.6, maxIterations = 50) {
6295
+ const originalUpperBound = bounds.upperBound;
6296
+ const originalLowerBound = bounds.lowerBound;
6297
+ if (originalLowerBound >= originalUpperBound) {
6298
+ throw new RangeError("Upper bound must be greater than lower bound.");
6299
+ }
6300
+ let fmin = f(originalLowerBound);
6301
+ let fmax = f(originalUpperBound);
6302
+ for (let i = 0; i < maxIterations; ++i) {
6303
+ if (Math.sign(fmin) !== Math.sign(fmax)) {
6304
+ return true;
6305
+ }
6306
+ if (Math.abs(fmin) < Math.abs(fmax)) {
6307
+ bounds.lowerBound +=
6308
+ factor * (bounds.lowerBound - bounds.upperBound);
6309
+ fmin = f(bounds.lowerBound);
6310
+ }
6311
+ else {
6312
+ bounds.upperBound +=
6313
+ factor * (bounds.upperBound - bounds.lowerBound);
6314
+ fmax = f(bounds.upperBound);
6315
+ }
6316
+ }
6317
+ bounds.lowerBound = originalLowerBound;
6318
+ bounds.upperBound = originalUpperBound;
6319
+ return false;
6320
+ }
6321
+ static reduce(f, bounds, subdivisions = 1000) {
6322
+ const originalUpperBound = bounds.upperBound;
6323
+ const originalLowerBound = bounds.lowerBound;
6324
+ if (originalLowerBound >= originalUpperBound) {
6325
+ throw new RangeError("Upper bound must be greater than lower bound.");
6326
+ }
6327
+ // TODO: Consider binary-style search instead of linear scan
6328
+ const fmin = f(bounds.lowerBound);
6329
+ const fmax = f(bounds.upperBound);
6330
+ if (Math.sign(fmin) != Math.sign(fmax)) {
6331
+ return true;
6332
+ }
6333
+ const subdiv = (bounds.upperBound - bounds.lowerBound) / subdivisions;
6334
+ let smin = bounds.lowerBound;
6335
+ const sign = Math.sign(fmin);
6336
+ for (let i = 0; i < subdivisions; ++i) {
6337
+ const smax = smin + subdiv;
6338
+ const sfmax = f(smax);
6339
+ if (!Number.isFinite(sfmax)) {
6340
+ // expand interval to include pole
6341
+ smin = smax;
6342
+ continue;
6343
+ }
6344
+ if (Math.sign(sfmax) != sign) {
6345
+ bounds.upperBound = smax;
6346
+ bounds.lowerBound = smin;
6347
+ return true;
6348
+ }
6349
+ smin = smax;
6350
+ }
6351
+ bounds.lowerBound = originalLowerBound;
6352
+ bounds.upperBound = originalUpperBound;
6353
+ return false;
6354
+ }
6355
+ static expandReduce(f, bounds, expansionFactor = 1.6, expansionMaxIterations = 50, reduceSubdivisions = 100) {
6356
+ return (this.expand(f, bounds, expansionFactor, expansionMaxIterations) ||
6357
+ this.reduce(f, bounds, reduceSubdivisions));
6358
+ }
6359
+ }
6360
+
6361
+ /**
6362
+ * Algorithm by Brent, Van Wijngaarden, Dekker et al.
6363
+ *
6364
+ * Implementation inspired by Press, Teukolsky, Vetterling, and Flannery, "Numerical Recipes in C", 2nd edition, Cambridge University Press.
6365
+ */
6366
+ class Brent {
6367
+ /**
6368
+ * Finds a solution to the equation f(x) = 0.
6369
+ *
6370
+ * @param f The function to find roots from.
6371
+ * @param bounds The upper and lower root bounds.
6372
+ * @param accuracy The desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Defaults to 1e-8. Must be greater than 0.
6373
+ * @param maxIterations The maximum number of iterations. Defaults to 100.
6374
+ * @param expandFactor The factor at which to expand the bounds, if needed. Defaults to 1.6.
6375
+ * @param maxExpandIterations The maximum number of expand iterations. Defaults to 100.
6376
+ * @returns The root with the specified accuracy. Throws an error if the algorithm failed to converge.
6377
+ */
6378
+ static findRootExpand(f, bounds, accuracy = 1e-8, maxIterations = 100, expandFactor = 1.6, maxExpandIterations = 100) {
6379
+ ZeroCrossingBracketing.expandReduce(f, bounds, expandFactor, maxExpandIterations, maxExpandIterations * 10);
6380
+ return this.findRoot(f, bounds, accuracy, maxIterations);
6381
+ }
6382
+ /**
6383
+ * Finds a solution to the equation f(x) = 0.
6384
+ *
6385
+ * @param f The function to find roots from.
6386
+ * @param bounds The upper and lower root bounds.
6387
+ * @param accuracy The desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Defaults to 1e-8. Must be greater than 0.
6388
+ * @param maxIterations The maximum number of iterations. Defaults to 100.
6389
+ * @returns The root with the specified accuracy. Throws an error if the algorithm failed to converge.
6390
+ */
6391
+ static findRoot(f, bounds, accuracy = 1e-8, maxIterations = 100) {
6392
+ const root = this.tryFindRoot(f, bounds, accuracy, maxIterations);
6393
+ if (root === null) {
6394
+ throw new Error("The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds.");
6395
+ }
6396
+ return root;
6397
+ }
6398
+ /**
6399
+ * Finds a solution to the equation f(x) = 0.
6400
+ *
6401
+ * @param f The function to find roots from.
6402
+ * @param bounds The upper and lower root bounds.
6403
+ * @param accuracy The desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Must be greater than 0.
6404
+ * @param maxIterations The maximum number of iterations. Usually 100.
6405
+ * @returns The root with the specified accuracy, `null` if not found.
6406
+ */
6407
+ static tryFindRoot(f, bounds, accuracy, maxIterations) {
6408
+ if (accuracy <= 0) {
6409
+ throw new RangeError("Accuracy must be greater than 0.");
6410
+ }
6411
+ let { lowerBound, upperBound } = bounds;
6412
+ let fmin = f(lowerBound);
6413
+ let fmax = f(upperBound);
6414
+ let froot = fmax;
6415
+ let d = 0;
6416
+ let e = 0;
6417
+ let root = upperBound;
6418
+ let xMid = Number.NaN;
6419
+ // Root must be bracketed.
6420
+ if (Math.sign(fmin) === Math.sign(fmax)) {
6421
+ return null;
6422
+ }
6423
+ for (let i = 0; i <= maxIterations; ++i) {
6424
+ // Adjust bounds.
6425
+ if (Math.sign(froot) === Math.sign(fmax)) {
6426
+ upperBound = lowerBound;
6427
+ fmax = fmin;
6428
+ e = d = root - lowerBound;
6429
+ }
6430
+ if (Math.abs(fmax) < Math.abs(froot)) {
6431
+ lowerBound = root;
6432
+ root = upperBound;
6433
+ upperBound = lowerBound;
6434
+ fmin = froot;
6435
+ froot = fmax;
6436
+ fmax = fmin;
6437
+ }
6438
+ // Convergence check
6439
+ const xAcc1 = 2 * Math.pow(2, -53) * Math.abs(root) + accuracy / 2;
6440
+ const xMidOld = xMid;
6441
+ xMid = (upperBound - root) / 2;
6442
+ if (Math.abs(xMid) <= xAcc1 ||
6443
+ Precision.almostEqualNormRelative(froot, 0, froot, accuracy)) {
6444
+ return root;
6445
+ }
6446
+ if (xMid === xMidOld) {
6447
+ // accuracy not sufficient, but cannot be improved further
6448
+ return null;
6449
+ }
6450
+ if (Math.abs(e) >= xAcc1 && Math.abs(fmin) > Math.abs(froot)) {
6451
+ // Attempt inverse quadratic interpolation
6452
+ const s = froot / fmin;
6453
+ let p;
6454
+ let q;
6455
+ if (Precision.almostEqualRelative(lowerBound, upperBound)) {
6456
+ p = 2 * xMid * s;
6457
+ q = 1 - s;
6458
+ }
6459
+ else {
6460
+ q = fmin / fmax;
6461
+ const r = froot / fmax;
6462
+ p =
6463
+ s *
6464
+ (2 * xMid * q * (q - r) -
6465
+ (root - lowerBound) * (r - 1));
6466
+ q = (q - 1) * (r - 1) * (s - 1);
6467
+ }
6468
+ if (p > 0) {
6469
+ // Check whether in bounds
6470
+ q = -q;
6471
+ }
6472
+ p = Math.abs(p);
6473
+ if (2 * p <
6474
+ Math.min(3 * xMid * q - Math.abs(xAcc1 * q), Math.abs(e * q))) {
6475
+ // Accept interpolation
6476
+ e = d;
6477
+ d = p / q;
6478
+ }
6479
+ else {
6480
+ // Interpolation failed, use bisection
6481
+ d = xMid;
6482
+ e = d;
6483
+ }
6484
+ }
6485
+ else {
6486
+ // Bounds decreasing too slowly, use bisection
6487
+ d = xMid;
6488
+ e = d;
6489
+ }
6490
+ lowerBound = root;
6491
+ fmin = froot;
6492
+ if (Math.abs(d) > xAcc1) {
6493
+ root += d;
6494
+ }
6495
+ else {
6496
+ root += this.sign(xAcc1, xMid);
6497
+ }
6498
+ froot = f(root);
6499
+ }
6500
+ return null;
6501
+ }
6502
+ /**
6503
+ * Helper method useful for preventing rounding errors.
6504
+ *
6505
+ * @returns a * sign(b)
6506
+ */
6507
+ static sign(a, b) {
6508
+ return b >= 0 ? (a >= 0 ? a : -a) : a >= 0 ? -a : a;
6509
+ }
6510
+ }
6511
+
6121
6512
  /**
6122
6513
  * Types of easing.
6123
6514
  *
@@ -6583,6 +6974,29 @@ class ErrorFunction {
6583
6974
  }
6584
6975
  return this.erfImp(x, false);
6585
6976
  }
6977
+ /**
6978
+ * Calculates the complementary error function.
6979
+ *
6980
+ * @param x The value to evaluate.
6981
+ * @returns The complementary error function evaluated at given value, or:
6982
+ * - 0 if `x === Number.POSITIVE_INFINITY`;
6983
+ * - 2 if `x === Number.NEGATIVE_INFINITY`.
6984
+ */
6985
+ static erfc(x) {
6986
+ if (x === 0) {
6987
+ return 1;
6988
+ }
6989
+ if (x === Number.POSITIVE_INFINITY) {
6990
+ return 0;
6991
+ }
6992
+ if (x === Number.NEGATIVE_INFINITY) {
6993
+ return 2;
6994
+ }
6995
+ if (Number.isNaN(x)) {
6996
+ return Number.NaN;
6997
+ }
6998
+ return this.erfImp(x, true);
6999
+ }
6586
7000
  /**
6587
7001
  * Calculates the inverse error function evaluated at z.
6588
7002
  *
@@ -7426,6 +7840,16 @@ class MapInfo {
7426
7840
  }
7427
7841
  }
7428
7842
 
7843
+ /**
7844
+ * Represents the osu! playfield.
7845
+ */
7846
+ class Playfield {
7847
+ /**
7848
+ * The size of the playfield, which is 512x384.
7849
+ */
7850
+ static baseSize = new Vector2(512, 384);
7851
+ }
7852
+
7429
7853
  dotenv.config();
7430
7854
 
7431
7855
  exports.Accuracy = Accuracy;
@@ -7444,6 +7868,7 @@ exports.BeatmapMetadata = BeatmapMetadata;
7444
7868
  exports.BeatmapVideo = BeatmapVideo;
7445
7869
  exports.BlendingParameters = BlendingParameters;
7446
7870
  exports.BreakPoint = BreakPoint;
7871
+ exports.Brent = Brent;
7447
7872
  exports.Circle = Circle;
7448
7873
  exports.Command = Command;
7449
7874
  exports.CommandLoop = CommandLoop;
@@ -7488,6 +7913,7 @@ exports.ModUtil = ModUtil;
7488
7913
  exports.OsuAPIRequestBuilder = OsuAPIRequestBuilder;
7489
7914
  exports.OsuHitWindow = OsuHitWindow;
7490
7915
  exports.PathApproximator = PathApproximator;
7916
+ exports.Playfield = Playfield;
7491
7917
  exports.Polynomial = Polynomial;
7492
7918
  exports.Precision = Precision;
7493
7919
  exports.RGBColor = RGBColor;
@@ -7513,4 +7939,5 @@ exports.TimingControlPoint = TimingControlPoint;
7513
7939
  exports.TimingControlPointManager = TimingControlPointManager;
7514
7940
  exports.Utils = Utils;
7515
7941
  exports.Vector2 = Vector2;
7942
+ exports.ZeroCrossingBracketing = ZeroCrossingBracketing;
7516
7943
  //# sourceMappingURL=index.js.map