@rian8337/osu-base 4.0.0-beta.85 → 4.0.0-beta.87

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.
Files changed (3) hide show
  1. package/dist/index.js +803 -267
  2. package/package.json +5 -5
  3. package/typings/index.d.ts +1397 -1201
package/dist/index.js CHANGED
@@ -132,6 +132,16 @@ class MathUtils {
132
132
  static logistic(exponent, maxValue = 1) {
133
133
  return maxValue / (1 + Math.exp(exponent));
134
134
  }
135
+ /**
136
+ * Calculates the p-norm of an n-dimensional vector.
137
+ *
138
+ * @param p The order of the norm.
139
+ * @param coefficients The coefficients of the vector.
140
+ * @returns The p-norm of the vector.
141
+ */
142
+ static norm(p, ...coefficients) {
143
+ return Math.pow(coefficients.reduce((a, v) => a + Math.pow(v, p), 0), 1 / p);
144
+ }
135
145
  /**
136
146
  * Calculates an S-shaped {@link https://en.wikipedia.org/wiki/Logistic_function logistic function}
137
147
  * with offset at `x`.
@@ -404,7 +414,7 @@ class BankHitSampleInfo extends HitSampleInfo {
404
414
  break;
405
415
  }
406
416
  if (this.customSampleBank >= 2) {
407
- names.push(`${prefix}-${this.name}${this.customSampleBank}`);
417
+ names.push(`${prefix}-${this.name}${this.customSampleBank.toString()}`);
408
418
  }
409
419
  names.push(`${prefix}-${this.name}`, this.name);
410
420
  return names;
@@ -573,6 +583,7 @@ class PreciseDroidHitWindow extends HitWindow {
573
583
  }
574
584
  }
575
585
 
586
+ /* eslint-disable @typescript-eslint/prefer-literal-enum-member */
576
587
  /**
577
588
  * Bitmask constant of object types. This is needed as osu! uses bits to determine object types.
578
589
  */
@@ -713,7 +724,7 @@ class Vector2 {
713
724
  * Returns a string representation of the vector.
714
725
  */
715
726
  toString() {
716
- return `${this.x},${this.y}`;
727
+ return `${this.x.toString()},${this.y.toString()}`;
717
728
  }
718
729
  }
719
730
 
@@ -741,6 +752,7 @@ class BeatmapDifficulty {
741
752
  * @param min Minimum of the resulting range which will be achieved by a difficulty value of 0.
742
753
  * @param mid Midpoint of the resulting range which will be achieved by a difficulty value of 5.
743
754
  * @param max Maximum of the resulting range which will be achieved by a difficulty value of 10.
755
+ * @returns The value to which the difficulty value maps in the specified range.
744
756
  */
745
757
  static difficultyRange(difficulty, min, mid, max) {
746
758
  switch (true) {
@@ -752,6 +764,19 @@ class BeatmapDifficulty {
752
764
  return mid;
753
765
  }
754
766
  }
767
+ /**
768
+ * Maps a difficulty value [0, 10] to a two-piece linear range of values. Floors the value to an integer,
769
+ * usually to match osu!stable specifications.
770
+ *
771
+ * @param difficulty The difficulty value to be mapped.
772
+ * @param min Minimum of the resulting range which will be achieved by a difficulty value of 0.
773
+ * @param mid Midpoint of the resulting range which will be achieved by a difficulty value of 5.
774
+ * @param max Maximum of the resulting range which will be achieved by a difficulty value of 10.
775
+ * @returns The value to which the difficulty value maps in the specified range.
776
+ */
777
+ static difficultyRangeInt(difficulty, min, mid, max) {
778
+ return Math.trunc(BeatmapDifficulty.difficultyRange(difficulty, min, mid, max));
779
+ }
755
780
  /**
756
781
  * Inverse function to `difficultyRange`. Maps a value returned by the function back to the
757
782
  * difficulty that produced it.
@@ -1169,7 +1194,7 @@ class HitObject {
1169
1194
  if (this.hitWindow) {
1170
1195
  this.hitWindow.overallDifficulty = difficulty.od;
1171
1196
  }
1172
- this.timePreempt = BeatmapDifficulty.difficultyRange(difficulty.ar, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
1197
+ this.timePreempt = BeatmapDifficulty.difficultyRangeInt(difficulty.ar, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
1173
1198
  // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
1174
1199
  // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
1175
1200
  // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
@@ -1370,6 +1395,7 @@ class ModSetting {
1370
1395
  /**
1371
1396
  * The formatter to display the value of this `ModSetting`.
1372
1397
  */
1398
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1373
1399
  this.displayFormatter = (v) => `${v}`;
1374
1400
  this.valueChangedListeners = new Set();
1375
1401
  this.name = name;
@@ -1618,7 +1644,7 @@ class SliderNestedHitObject extends HitObject {
1618
1644
  this.spanStartTime = values.spanStartTime;
1619
1645
  }
1620
1646
  toString() {
1621
- return `Position: [${this._position.x}, ${this._position.y}], span index: ${this.spanIndex}, span start time: ${this.spanStartTime}`;
1647
+ return `Position: [${this.position.x.toString()}, ${this.position.y.toString()}], span index: ${this.spanIndex.toString()}, span start time: ${this.spanStartTime.toString()}`;
1622
1648
  }
1623
1649
  }
1624
1650
 
@@ -2016,7 +2042,9 @@ class Slider extends HitObject {
2016
2042
  this.tickDistanceMultiplier
2017
2043
  : Number.POSITIVE_INFINITY;
2018
2044
  this.createNestedHitObjects(controlPoints);
2019
- this.nestedHitObjects.forEach((v) => v.applyDefaults(controlPoints, difficulty, mode));
2045
+ this.nestedHitObjects.forEach((v) => {
2046
+ v.applyDefaults(controlPoints, difficulty, mode);
2047
+ });
2020
2048
  }
2021
2049
  applySamples(controlPoints) {
2022
2050
  super.applySamples(controlPoints);
@@ -2219,7 +2247,7 @@ class Slider extends HitObject {
2219
2247
  });
2220
2248
  }
2221
2249
  toString() {
2222
- return `Position: [${this.position.x}, ${this.position.y}], distance: ${this.path.expectedDistance}, repeat count: ${this.repeatCount}, slider ticks: ${this.nestedHitObjects.filter((v) => v instanceof SliderTick).length}`;
2250
+ return `Position: [${this.position.x.toString()}, ${this.position.y.toString()}], distance: ${this.path.expectedDistance.toString()}, repeat count: ${this.repeatCount.toString()}, slider ticks: ${this.ticks.toString()}`;
2223
2251
  }
2224
2252
  }
2225
2253
  Slider.baseNormalSlideSample = new BankHitSampleInfo("sliderslide");
@@ -2227,43 +2255,6 @@ Slider.baseWhistleSlideSample = new BankHitSampleInfo("sliderwhistle");
2227
2255
  Slider.baseTickSample = new BankHitSampleInfo("slidertick");
2228
2256
  Slider.legacyLastTickOffset = 36;
2229
2257
 
2230
- /**
2231
- * Represents the Freeze Frame mod.
2232
- */
2233
- class ModFreezeFrame extends Mod {
2234
- constructor() {
2235
- super();
2236
- this.name = "Freeze Frame";
2237
- this.acronym = "FR";
2238
- this.droidRanked = false;
2239
- this.isDroidRelevant = true;
2240
- this.droidScoreMultiplier = 1;
2241
- this.osuRanked = false;
2242
- this.isOsuRelevant = true;
2243
- this.osuScoreMultiplier = 1;
2244
- this.lastNewComboTime = 0;
2245
- this.incompatibleMods.add(ModApproachDifferent);
2246
- }
2247
- applyToBeatmap(beatmap) {
2248
- this.lastNewComboTime = 0;
2249
- for (const hitObject of beatmap.hitObjects) {
2250
- if (hitObject.isNewCombo) {
2251
- this.lastNewComboTime = hitObject.startTime;
2252
- }
2253
- this.applyFadeInAdjustment(hitObject);
2254
- }
2255
- }
2256
- applyFadeInAdjustment(hitObject) {
2257
- hitObject.timePreempt += hitObject.startTime - this.lastNewComboTime;
2258
- if (hitObject instanceof Slider) {
2259
- // Freezing slider ticks doesn't play well with snaking sliders, and slider repeats will not
2260
- // layer correctly if its preempt is changed.
2261
- this.applyFadeInAdjustment(hitObject.head);
2262
- this.applyFadeInAdjustment(hitObject.tail);
2263
- }
2264
- }
2265
- }
2266
-
2267
2258
  /**
2268
2259
  * Represents the Traceable mod.
2269
2260
  */
@@ -2315,7 +2306,10 @@ class ModHidden extends Mod {
2315
2306
  * The main object body will not fade when enabled.
2316
2307
  */
2317
2308
  this.onlyFadeApproachCircles = new BooleanModSetting("Only fade approach circles", "The main object body will not fade when enabled.", false);
2318
- this.incompatibleMods.add(ModTraceable).add(ModApproachDifferent);
2309
+ this.incompatibleMods
2310
+ .add(ModTraceable)
2311
+ .add(ModApproachDifferent)
2312
+ .add(ModFreezeFrame);
2319
2313
  }
2320
2314
  get droidScoreMultiplier() {
2321
2315
  return this.usesDefaultSettings ? 1.06 : 1;
@@ -2354,6 +2348,43 @@ class ModHidden extends Mod {
2354
2348
  ModHidden.fadeInDurationMultiplier = 0.4;
2355
2349
  ModHidden.fadeOutDurationMultiplier = 0.3;
2356
2350
 
2351
+ /**
2352
+ * Represents the Freeze Frame mod.
2353
+ */
2354
+ class ModFreezeFrame extends Mod {
2355
+ constructor() {
2356
+ super();
2357
+ this.name = "Freeze Frame";
2358
+ this.acronym = "FR";
2359
+ this.droidRanked = false;
2360
+ this.isDroidRelevant = true;
2361
+ this.droidScoreMultiplier = 1;
2362
+ this.osuRanked = false;
2363
+ this.isOsuRelevant = true;
2364
+ this.osuScoreMultiplier = 1;
2365
+ this.lastNewComboTime = 0;
2366
+ this.incompatibleMods.add(ModApproachDifferent).add(ModHidden);
2367
+ }
2368
+ applyToBeatmap(beatmap) {
2369
+ this.lastNewComboTime = 0;
2370
+ for (const hitObject of beatmap.hitObjects) {
2371
+ if (hitObject.isNewCombo) {
2372
+ this.lastNewComboTime = hitObject.startTime;
2373
+ }
2374
+ this.applyFadeInAdjustment(hitObject);
2375
+ }
2376
+ }
2377
+ applyFadeInAdjustment(hitObject) {
2378
+ hitObject.timePreempt += hitObject.startTime - this.lastNewComboTime;
2379
+ if (hitObject instanceof Slider) {
2380
+ // Freezing slider ticks doesn't play well with snaking sliders, and slider repeats will not
2381
+ // layer correctly if its preempt is changed.
2382
+ this.applyFadeInAdjustment(hitObject.head);
2383
+ this.applyFadeInAdjustment(hitObject.tail);
2384
+ }
2385
+ }
2386
+ }
2387
+
2357
2388
  /**
2358
2389
  * Represents a `Mod` specific setting that is constrained to a range of values.
2359
2390
  */
@@ -2410,13 +2441,13 @@ class NumberModSetting extends RangeConstrainedModSetting {
2410
2441
  super(name, description, defaultValue, min, max, step);
2411
2442
  this.displayFormatter = (v) => v.toString();
2412
2443
  if (min > max) {
2413
- throw new RangeError(`The minimum value (${min}) must be less than or equal to the maximum value (${max}).`);
2444
+ throw new RangeError(`The minimum value (${min.toString()}) must be less than or equal to the maximum value (${max.toString()}).`);
2414
2445
  }
2415
2446
  if (step < 0) {
2416
- throw new RangeError(`The step size (${step}) must be greater than or equal to 0.`);
2447
+ throw new RangeError(`The step size (${step.toString()}) must be greater than or equal to 0.`);
2417
2448
  }
2418
2449
  if (defaultValue < min || defaultValue > max) {
2419
- throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
2450
+ throw new RangeError(`The default value (${defaultValue.toString()}) must be between the minimum (${min.toString()}) and maximum (${max.toString()}) values.`);
2420
2451
  }
2421
2452
  }
2422
2453
  processValue(value) {
@@ -2438,7 +2469,7 @@ class DecimalModSetting extends NumberModSetting {
2438
2469
  }
2439
2470
  set precision(value) {
2440
2471
  if (value !== null && value < 0) {
2441
- throw new RangeError(`The precision (${value}) must be greater than or equal to 0.`);
2472
+ throw new RangeError(`The precision (${value.toString()}) must be greater than or equal to 0.`);
2442
2473
  }
2443
2474
  this._precision = value;
2444
2475
  if (value !== null) {
@@ -2454,7 +2485,7 @@ class DecimalModSetting extends NumberModSetting {
2454
2485
  return super.toDisplayString();
2455
2486
  };
2456
2487
  if (precision !== null && precision < 0) {
2457
- throw new RangeError(`The precision (${precision}) must be greater than or equal to 0.`);
2488
+ throw new RangeError(`The precision (${precision.toString()}) must be greater than or equal to 0.`);
2458
2489
  }
2459
2490
  this._precision = precision;
2460
2491
  }
@@ -2807,11 +2838,8 @@ class ModDeflate extends ModObjectScaleTween {
2807
2838
  * rectangle).
2808
2839
  */
2809
2840
  class Circle extends HitObject {
2810
- constructor(values) {
2811
- super(values);
2812
- }
2813
2841
  toString() {
2814
- return `Position: [${this._position.x}, ${this._position.y}]`;
2842
+ return `Position: [${this.position.x.toString()}, ${this.position.y.toString()}]`;
2815
2843
  }
2816
2844
  }
2817
2845
 
@@ -2862,7 +2890,7 @@ class Spinner extends HitObject {
2862
2890
  return new EmptyHitWindow();
2863
2891
  }
2864
2892
  toString() {
2865
- return `Position: [${this._position.x}, ${this._position.y}], duration: ${this.duration}`;
2893
+ return `Position: [${this.position.x.toString()}, ${this.position.y.toString()}], duration: ${this.duration.toString()}`;
2866
2894
  }
2867
2895
  }
2868
2896
  Spinner.baseSpinnerSpinSample = new BankHitSampleInfo("spinnerspin");
@@ -2992,26 +3020,15 @@ class HitObjectPositionInfo {
2992
3020
  * Precision utilities.
2993
3021
  */
2994
3022
  class Precision {
2995
- /**
2996
- * Checks if two numbers are equal with a given tolerance.
2997
- *
2998
- * @param value1 The first number.
2999
- * @param value2 The second number.
3000
- * @param acceptableDifference The acceptable difference as threshold. Default is `Precision.FLOAT_EPSILON = 1e-3`.
3001
- */
3002
- static almostEqualsNumber(value1, value2, acceptableDifference = this.FLOAT_EPSILON) {
3003
- return Math.abs(value1 - value2) <= acceptableDifference;
3004
- }
3005
- /**
3006
- * Checks if two vectors are equal with a given tolerance.
3007
- *
3008
- * @param vec1 The first vector.
3009
- * @param vec2 The second vector.
3010
- * @param acceptableDifference The acceptable difference as threshold. Default is `Precision.FLOAT_EPSILON = 1e-3`.
3011
- */
3012
- static almostEqualsVector(vec1, vec2, acceptableDifference = this.FLOAT_EPSILON) {
3013
- return (this.almostEqualsNumber(vec1.x, vec2.x, acceptableDifference) &&
3014
- this.almostEqualsNumber(vec1.y, vec2.y, acceptableDifference));
3023
+ static almostEquals(value1, value2, acceptableDifference = this.FLOAT_EPSILON) {
3024
+ if (value1 instanceof Vector2 && value2 instanceof Vector2) {
3025
+ return (this.almostEquals(value1.x, value2.x, acceptableDifference) &&
3026
+ this.almostEquals(value1.y, value2.y, acceptableDifference));
3027
+ }
3028
+ else {
3029
+ return (Math.abs(value1 - value2) <=
3030
+ acceptableDifference);
3031
+ }
3015
3032
  }
3016
3033
  /**
3017
3034
  * Checks whether two real numbers are almost equal.
@@ -3161,7 +3178,7 @@ class PathApproximator {
3161
3178
  const c = controlPoints[2];
3162
3179
  // If we have a degenerate triangle where a side-length is almost zero, then give up and fall
3163
3180
  // back to a more numerically stable method.
3164
- if (Precision.almostEqualsNumber(0, (b.y - a.y) * (c.x - a.x) - (b.x - a.x) * (c.y - a.y))) {
3181
+ if (Precision.almostEquals(0, (b.y - a.y) * (c.x - a.x) - (b.x - a.x) * (c.y - a.y))) {
3165
3182
  return this.approximateBezier(controlPoints);
3166
3183
  }
3167
3184
  // See: https://en.wikipedia.org/wiki/Circumscribed_circle#Cartesian_coordinates_2
@@ -3516,7 +3533,7 @@ class SliderPath {
3516
3533
  const d0 = this.cumulativeLength[i - 1];
3517
3534
  const d1 = this.cumulativeLength[i];
3518
3535
  // Avoid division by and almost-zero number in case two points are extremely close to each other.
3519
- if (Precision.almostEqualsNumber(d0, d1)) {
3536
+ if (Precision.almostEquals(d0, d1)) {
3520
3537
  return p0;
3521
3538
  }
3522
3539
  const w = (d - d0) / (d1 - d0);
@@ -3758,7 +3775,7 @@ class HitObjectGenerationUtils {
3758
3775
  /**
3759
3776
  * Determines whether a {@link HitObject} is on a beat.
3760
3777
  *
3761
- * @param beatmap The {@link Beatmap} the {@link HitObject} is a part of.
3778
+ * @param beatmap The {@link IBeatmap} the {@link HitObject} is a part of.
3762
3779
  * @param hitObject The {@link HitObject} to check.
3763
3780
  * @param downbeatsOnly If `true`, whether this method only returns `true` is on a downbeat.
3764
3781
  * @return `true` if the {@link HitObject} is on a (down-)beat, `false` otherwise.
@@ -3866,7 +3883,7 @@ class HitObjectGenerationUtils {
3866
3883
  this.getSliderRotation(current.hitObject)));
3867
3884
  const relativeRotation = Math.atan2(centerOfMassModified.y, centerOfMassModified.x) -
3868
3885
  Math.atan2(centerOfMassOriginal.y, centerOfMassOriginal.x);
3869
- if (!Precision.almostEqualsNumber(relativeRotation, 0)) {
3886
+ if (!Precision.almostEquals(relativeRotation, 0)) {
3870
3887
  this.rotateSlider(current.hitObject, relativeRotation);
3871
3888
  }
3872
3889
  }
@@ -4327,7 +4344,7 @@ class NullableDecimalModSetting extends RangeConstrainedModSetting {
4327
4344
  }
4328
4345
  set precision(value) {
4329
4346
  if (value !== null && value < 0) {
4330
- throw new RangeError(`The precision (${value}) must be greater than or equal to 0.`);
4347
+ throw new RangeError(`The precision (${value.toString()}) must be greater than or equal to 0.`);
4331
4348
  }
4332
4349
  this._precision = value;
4333
4350
  if (value !== null) {
@@ -4346,14 +4363,14 @@ class NullableDecimalModSetting extends RangeConstrainedModSetting {
4346
4363
  return super.toDisplayString();
4347
4364
  };
4348
4365
  if (min > max) {
4349
- throw new RangeError(`The minimum value (${min}) must be less than or equal to the maximum value (${max}).`);
4366
+ throw new RangeError(`The minimum value (${min.toString()}) must be less than or equal to the maximum value (${max.toString()}).`);
4350
4367
  }
4351
4368
  if (step < 0) {
4352
- throw new RangeError(`The step size (${step}) must be greater than or equal to 0.`);
4369
+ throw new RangeError(`The step size (${step.toString()}) must be greater than or equal to 0.`);
4353
4370
  }
4354
4371
  if (defaultValue !== null &&
4355
4372
  (defaultValue < min || defaultValue > max)) {
4356
- throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
4373
+ throw new RangeError(`The default value (${defaultValue.toString()}) must be between the minimum (${min.toString()}) and maximum (${max.toString()}) values.`);
4357
4374
  }
4358
4375
  this._precision = precision;
4359
4376
  }
@@ -4943,11 +4960,11 @@ class NullableIntegerModSetting extends RangeConstrainedModSetting {
4943
4960
  super(name, description, defaultValue, min, max, 1);
4944
4961
  this.displayFormatter = (v) => { var _a; return (_a = v === null || v === void 0 ? void 0 : v.toString()) !== null && _a !== void 0 ? _a : "None"; };
4945
4962
  if (min > max) {
4946
- throw new RangeError(`The minimum value (${min}) must be less than or equal to the maximum value (${max}).`);
4963
+ throw new RangeError(`The minimum value (${min.toString()}) must be less than or equal to the maximum value (${max.toString()}).`);
4947
4964
  }
4948
4965
  if (defaultValue !== null &&
4949
4966
  (defaultValue < min || defaultValue > max)) {
4950
- throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
4967
+ throw new RangeError(`The default value (${defaultValue.toString()}) must be between the minimum (${min.toString()}) and maximum (${max.toString()}) values.`);
4951
4968
  }
4952
4969
  }
4953
4970
  processValue(value) {
@@ -5107,7 +5124,7 @@ class ModRandom extends Mod {
5107
5124
  toString() {
5108
5125
  const settings = [];
5109
5126
  if (this.seed.value !== null) {
5110
- settings.push(`seed: ${this.seed.value}`);
5127
+ settings.push(`seed: ${this.seed.value.toString()}`);
5111
5128
  }
5112
5129
  settings.push(`angle sharpness: ${this.angleSharpness.toDisplayString()}`);
5113
5130
  return `${super.toString()} (${settings.join(", ")})`;
@@ -5143,12 +5160,8 @@ class ModSpunOut extends Mod {
5143
5160
  this.name = "SpunOut";
5144
5161
  this.osuRanked = true;
5145
5162
  this.bitwise = 1 << 12;
5146
- }
5147
- get isOsuRelevant() {
5148
- return true;
5149
- }
5150
- get osuScoreMultiplier() {
5151
- return 0.9;
5163
+ this.isOsuRelevant = true;
5164
+ this.osuScoreMultiplier = 0.9;
5152
5165
  }
5153
5166
  }
5154
5167
 
@@ -5179,12 +5192,8 @@ class ModTouchDevice extends Mod {
5179
5192
  this.name = "TouchDevice";
5180
5193
  this.osuRanked = true;
5181
5194
  this.bitwise = 1 << 2;
5182
- }
5183
- get isOsuRelevant() {
5184
- return true;
5185
- }
5186
- get osuScoreMultiplier() {
5187
- return 1;
5195
+ this.isOsuRelevant = true;
5196
+ this.osuScoreMultiplier = 1;
5188
5197
  }
5189
5198
  }
5190
5199
 
@@ -6144,8 +6153,7 @@ class BeatmapProcessor {
6144
6153
  if (objectN instanceof Spinner) {
6145
6154
  break;
6146
6155
  }
6147
- const stackThreshold = objectN.timePreempt *
6148
- this.beatmap.general.stackLeniency;
6156
+ const stackThreshold = this.calculateStackThreshold(objectN);
6149
6157
  if (objectN.startTime - stackBaseObject.endTime >
6150
6158
  stackThreshold) {
6151
6159
  // We are no longer within stacking range of the next object.
@@ -6187,7 +6195,7 @@ class BeatmapProcessor {
6187
6195
  if (objectI.stackHeight !== 0 || objectI instanceof Spinner) {
6188
6196
  continue;
6189
6197
  }
6190
- const stackThreshold = objectI.timePreempt * this.beatmap.general.stackLeniency;
6198
+ const stackThreshold = this.calculateStackThreshold(objectI);
6191
6199
  // If this object is a hit circle, then we enter this "special" case.
6192
6200
  // It either ends with a stack of hit circles only, or a stack of hit circles that are underneath a slider.
6193
6201
  // Any other case is handled by the "instanceof Slider" code below this.
@@ -6197,7 +6205,11 @@ class BeatmapProcessor {
6197
6205
  if (objectN instanceof Spinner) {
6198
6206
  continue;
6199
6207
  }
6200
- if (objectI.startTime - objectN.endTime > stackThreshold) {
6208
+ // Truncation to integer is required to match osu!stable - both quantities being subtracted there
6209
+ // are integers.
6210
+ if (Math.trunc(objectI.startTime) -
6211
+ Math.trunc(objectN.endTime) >
6212
+ stackThreshold) {
6201
6213
  // We are no longer within stacking range of the previous object.
6202
6214
  break;
6203
6215
  }
@@ -6266,7 +6278,7 @@ class BeatmapProcessor {
6266
6278
  }
6267
6279
  let startTime = currentObject.endTime;
6268
6280
  let sliderStack = 0;
6269
- const stackThreshold = currentObject.timePreempt * this.beatmap.general.stackLeniency;
6281
+ const stackThreshold = this.calculateStackThreshold(currentObject);
6270
6282
  for (let j = i + 1; j < objects.length; ++j) {
6271
6283
  if (objects[j].startTime - stackThreshold > startTime) {
6272
6284
  break;
@@ -6294,6 +6306,18 @@ class BeatmapProcessor {
6294
6306
  }
6295
6307
  }
6296
6308
  }
6309
+ /**
6310
+ * Truncation of {@link HitObject.timePreempt} to an integer, as well as keeping the result as a float,
6311
+ * are both done for the purposes of osu!stable compatibility.
6312
+ *
6313
+ * Note that top-level objects {@link HitObject.timePreempt} is supposed to be integral anyway; see
6314
+ * {@link HitObject.applyDefaults} using `BeatmapDifficulty.difficultyRangeInt` when calculating it.
6315
+ *
6316
+ * Slider ticks and end circles are the exception to that, but they do not matter for stacking.
6317
+ */
6318
+ calculateStackThreshold(object) {
6319
+ return (Math.trunc(object.timePreempt) * this.beatmap.general.stackLeniency);
6320
+ }
6297
6321
  }
6298
6322
  BeatmapProcessor.stackDistance = 3;
6299
6323
 
@@ -6393,13 +6417,13 @@ class TimingControlPoint extends ControlPoint {
6393
6417
  }
6394
6418
  toString() {
6395
6419
  return ("{ time: " +
6396
- this.time +
6420
+ this.time.toString() +
6397
6421
  ", " +
6398
6422
  "ms_per_beat: " +
6399
6423
  this.msPerBeat.toFixed(2) +
6400
6424
  ", " +
6401
6425
  "timeSignature: " +
6402
- this.timeSignature +
6426
+ this.timeSignature.toString() +
6403
6427
  " }");
6404
6428
  }
6405
6429
  }
@@ -6611,12 +6635,12 @@ class DifficultyControlPoint extends ControlPoint {
6611
6635
  }
6612
6636
  toString() {
6613
6637
  return ("{ time: " +
6614
- this.time +
6638
+ this.time.toString() +
6615
6639
  ", " +
6616
6640
  "speed multiplier: " +
6617
6641
  this.speedMultiplier.toFixed(2) +
6618
6642
  ", generate ticks: " +
6619
- this.generateTicks +
6643
+ this.generateTicks.toString() +
6620
6644
  " }");
6621
6645
  }
6622
6646
  }
@@ -6651,7 +6675,12 @@ class EffectControlPoint extends ControlPoint {
6651
6675
  return this.isKiai === existing.isKiai;
6652
6676
  }
6653
6677
  toString() {
6654
- return "{ time: " + this.time + ", " + "kiai: " + this.isKiai + " }";
6678
+ return ("{ time: " +
6679
+ this.time.toString() +
6680
+ ", " +
6681
+ "kiai: " +
6682
+ this.isKiai.toString() +
6683
+ " }");
6655
6684
  }
6656
6685
  }
6657
6686
 
@@ -6712,13 +6741,13 @@ class SampleControlPoint extends ControlPoint {
6712
6741
  }
6713
6742
  toString() {
6714
6743
  return ("{ time: " +
6715
- this.time +
6744
+ this.time.toString() +
6716
6745
  ", " +
6717
6746
  "sample bank: " +
6718
- this.sampleBank +
6747
+ this.sampleBank.toString() +
6719
6748
  ", " +
6720
6749
  "sample volume: " +
6721
- this.sampleVolume +
6750
+ this.sampleVolume.toString() +
6722
6751
  " }");
6723
6752
  }
6724
6753
  }
@@ -6774,6 +6803,7 @@ class BeatmapControlPoints {
6774
6803
  }
6775
6804
  }
6776
6805
 
6806
+ /* eslint-disable @typescript-eslint/prefer-literal-enum-member */
6777
6807
  /**
6778
6808
  * Represents the grid size setting in the editor.
6779
6809
  */
@@ -7060,7 +7090,7 @@ class Beatmap {
7060
7090
  var _a, _b, _c, _d, _e;
7061
7091
  // The last playable time in the beatmap - the last timing point extends to this time.
7062
7092
  // Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context.
7063
- const lastTime = (_d = (_b = (_a = this.hitObjects.objects[this.hitObjects.objects.length - 1]) === null || _a === void 0 ? void 0 : _a.endTime) !== null && _b !== void 0 ? _b : (_c = this.controlPoints.timing.points[this.controlPoints.timing.points.length - 1]) === null || _c === void 0 ? void 0 : _c.time) !== null && _d !== void 0 ? _d : 0;
7093
+ const lastTime = (_d = (_b = (_a = this.hitObjects.objects.at(-1)) === null || _a === void 0 ? void 0 : _a.endTime) !== null && _b !== void 0 ? _b : (_c = this.controlPoints.timing.points.at(-1)) === null || _c === void 0 ? void 0 : _c.time) !== null && _d !== void 0 ? _d : 0;
7064
7094
  const mostCommon =
7065
7095
  // Construct a set of {beatLength, duration} objects for each individual timing point.
7066
7096
  this.controlPoints.timing.points
@@ -7076,8 +7106,9 @@ class Beatmap {
7076
7106
  duration: nextTime - currentTime,
7077
7107
  };
7078
7108
  })
7079
- // Get the most common one, or 0 as a suitable default.
7080
- .sort((a, b) => b.duration - a.duration)[0];
7109
+ .sort((a, b) => b.duration - a.duration)
7110
+ .at(0);
7111
+ // Get the most common one, or 0 as a suitable default.
7081
7112
  return (_e = mostCommon === null || mostCommon === void 0 ? void 0 : mostCommon.beatLength) !== null && _e !== void 0 ? _e : 0;
7082
7113
  }
7083
7114
  getOffsetTime(time) {
@@ -7232,7 +7263,9 @@ class Beatmap {
7232
7263
  const processor = new BeatmapProcessor(converted);
7233
7264
  processor.preProcess();
7234
7265
  // Compute default values for hit objects, including creating nested hit objects in-case they're needed.
7235
- converted.hitObjects.objects.forEach((hitObject) => hitObject.applyDefaults(converted.controlPoints, converted.difficulty, mode));
7266
+ converted.hitObjects.objects.forEach((hitObject) => {
7267
+ hitObject.applyDefaults(converted.controlPoints, converted.difficulty, mode);
7268
+ });
7236
7269
  mods.forEach((mod) => {
7237
7270
  if (mod.isApplicableToHitObject()) {
7238
7271
  for (const hitObject of converted.hitObjects.objects) {
@@ -7275,25 +7308,25 @@ class Beatmap {
7275
7308
  "\n" +
7276
7309
  "\n" +
7277
7310
  "AR" +
7278
- MathUtils.round(this.difficulty.ar, 2) +
7311
+ MathUtils.round(this.difficulty.ar, 2).toString() +
7279
7312
  " " +
7280
7313
  "OD" +
7281
- MathUtils.round(this.difficulty.od, 2) +
7314
+ MathUtils.round(this.difficulty.od, 2).toString() +
7282
7315
  " " +
7283
7316
  "CS" +
7284
- MathUtils.round(this.difficulty.cs, 2) +
7317
+ MathUtils.round(this.difficulty.cs, 2).toString() +
7285
7318
  " " +
7286
7319
  "HP" +
7287
- MathUtils.round(this.difficulty.hp, 2) +
7320
+ MathUtils.round(this.difficulty.hp, 2).toString() +
7288
7321
  "\n" +
7289
- this.hitObjects.circles +
7322
+ this.hitObjects.circles.toString() +
7290
7323
  " circles, " +
7291
- this.hitObjects.sliders +
7324
+ this.hitObjects.sliders.toString() +
7292
7325
  " sliders, " +
7293
- this.hitObjects.spinners +
7326
+ this.hitObjects.spinners.toString() +
7294
7327
  " spinners" +
7295
7328
  "\n" +
7296
- this.maxCombo +
7329
+ this.maxCombo.toString() +
7297
7330
  " max combo";
7298
7331
  return res;
7299
7332
  }
@@ -7309,6 +7342,7 @@ class BeatmapBackground {
7309
7342
  }
7310
7343
  }
7311
7344
 
7345
+ /* eslint-disable @typescript-eslint/prefer-literal-enum-member */
7312
7346
  /**
7313
7347
  * Represents available hitsound types.
7314
7348
  */
@@ -7321,6 +7355,7 @@ exports.HitSoundType = void 0;
7321
7355
  HitSoundType[HitSoundType["clap"] = 8] = "clap";
7322
7356
  })(exports.HitSoundType || (exports.HitSoundType = {}));
7323
7357
 
7358
+ /* eslint-disable @typescript-eslint/no-duplicate-enum-values */
7324
7359
  /**
7325
7360
  * Constants for beatmap parser.
7326
7361
  */
@@ -7403,7 +7438,7 @@ class Decoder {
7403
7438
  * @returns The current decoder instance.
7404
7439
  */
7405
7440
  decode(str) {
7406
- var _a;
7441
+ var _a, _b;
7407
7442
  this.reset();
7408
7443
  for (let line of str.split("\n")) {
7409
7444
  this.currentLine = line;
@@ -7424,7 +7459,7 @@ class Decoder {
7424
7459
  if (line.startsWith("[") && line.endsWith("]")) {
7425
7460
  const section = line.substring(1, line.length - 1);
7426
7461
  if (!Object.values(BeatmapSection).includes(section)) {
7427
- console.warn(`Unknown section "${line}" at line ${this.line}`);
7462
+ console.warn(`Unknown section "${line}" at line ${this.line.toString()}`);
7428
7463
  continue;
7429
7464
  }
7430
7465
  this.section = section;
@@ -7443,7 +7478,7 @@ class Decoder {
7443
7478
  }
7444
7479
  catch (e) {
7445
7480
  console.error(e);
7446
- console.error(`at line ${this.line}\n${(_a = this.decoders[this.section]) === null || _a === void 0 ? void 0 : _a.logExceptionPosition()}`);
7481
+ console.error(`at line ${this.line.toString()}\n${(_b = (_a = this.decoders[this.section]) === null || _a === void 0 ? void 0 : _a.logExceptionPosition()) !== null && _b !== void 0 ? _b : ""}`);
7447
7482
  }
7448
7483
  }
7449
7484
  return this;
@@ -7533,7 +7568,7 @@ class SectionDecoder {
7533
7568
  * @param str The string to parse.
7534
7569
  * @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
7535
7570
  * @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
7536
- * @param allowNaN Whether to allow NaN.
7571
+ * @param allowNaN Whether to allow NaN. Defaults to `false`.
7537
7572
  * @returns The parsed integer.
7538
7573
  */
7539
7574
  tryParseInt(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
@@ -7557,7 +7592,7 @@ class SectionDecoder {
7557
7592
  * @param str The string to parse.
7558
7593
  * @param min The minimum threshold. Defaults to `-ParserConstants.MAX_PARSE_VALUE`.
7559
7594
  * @param max The maximum threshold. Defaults to `ParserConstants.MAX_PARSE_VALUE`.
7560
- * @param allowNaN Whether to allow NaN.
7595
+ * @param allowNaN Whether to allow NaN. Defaults to `false`.
7561
7596
  * @returns The parsed float.
7562
7597
  */
7563
7598
  tryParseFloat(str, min = -ParserConstants.MAX_PARSE_VALUE, max = ParserConstants.MAX_PARSE_VALUE, allowNaN = false) {
@@ -7662,7 +7697,7 @@ class BeatmapHitObjectsDecoder extends SectionDecoder {
7662
7697
  if (points.length !== 3) {
7663
7698
  pathType = exports.PathType.Bezier;
7664
7699
  }
7665
- else if (Precision.almostEqualsNumber(0, (points[1].y - points[0].y) *
7700
+ else if (Precision.almostEquals(0, (points[1].y - points[0].y) *
7666
7701
  (points[2].x - points[0].x) -
7667
7702
  (points[1].x - points[0].x) *
7668
7703
  (points[2].y - points[0].y))) {
@@ -7835,7 +7870,7 @@ class BeatmapGeneralDecoder extends SectionDecoder {
7835
7870
  this.target.general.previewTime = this.tryParseInt(p[1]);
7836
7871
  break;
7837
7872
  case "Countdown":
7838
- this.target.general.countdown = (this.tryParseInt(p[1]));
7873
+ this.target.general.countdown = this.tryParseInt(p[1]);
7839
7874
  break;
7840
7875
  case "SampleSet":
7841
7876
  switch (p[1]) {
@@ -7866,7 +7901,8 @@ class BeatmapGeneralDecoder extends SectionDecoder {
7866
7901
  this.target.general.useSkinSprites = !!this.tryParseInt(p[1]);
7867
7902
  break;
7868
7903
  case "OverlayPosition":
7869
- this.target.general.overlayPosition = (p[1]);
7904
+ this.target.general.overlayPosition =
7905
+ p[1];
7870
7906
  break;
7871
7907
  case "SkinPreference":
7872
7908
  this.target.general.skinPreference = (_a = p[1]) !== null && _a !== void 0 ? _a : "";
@@ -7889,6 +7925,7 @@ class BeatmapGeneralDecoder extends SectionDecoder {
7889
7925
  this.target.editor.bookmarks = p[1]
7890
7926
  .split(",")
7891
7927
  .map((v) => this.tryParseInt(v));
7928
+ break;
7892
7929
  }
7893
7930
  }
7894
7931
  }
@@ -7912,7 +7949,7 @@ class BeatmapEditorDecoder extends SectionDecoder {
7912
7949
  this.target.editor.beatDivisor = this.tryParseFloat(p[1]);
7913
7950
  break;
7914
7951
  case "GridSize":
7915
- this.target.editor.gridSize = (this.tryParseInt(p[1]));
7952
+ this.target.editor.gridSize = this.tryParseInt(p[1]);
7916
7953
  break;
7917
7954
  case "TimelineZoom":
7918
7955
  this.target.editor.timelineZoom = this.tryParseFloat(p[1]);
@@ -7942,15 +7979,15 @@ class BreakPoint {
7942
7979
  get duration() {
7943
7980
  return this.endTime - this.startTime;
7944
7981
  }
7945
- constructor(values) {
7946
- this.startTime = values.startTime;
7947
- this.endTime = values.endTime;
7982
+ constructor(startTime, endTime) {
7983
+ this.startTime = startTime;
7984
+ this.endTime = endTime;
7948
7985
  }
7949
7986
  /**
7950
7987
  * Returns a string representation of the class.
7951
7988
  */
7952
7989
  toString() {
7953
- return `Start time: ${this.startTime}, end time: ${this.endTime}, duration: ${this.duration}`;
7990
+ return `Start time: ${this.startTime.toString()}, end time: ${this.endTime.toString()}, duration: ${this.duration.toString()}`;
7954
7991
  }
7955
7992
  /**
7956
7993
  * Whether this break period contains a specified time.
@@ -8003,10 +8040,7 @@ class BeatmapEventsDecoder extends SectionDecoder {
8003
8040
  this.target.events.video = new BeatmapVideo(this.tryParseInt(this.setPosition(s[1])), this.setPosition(s[2]).replace(/"/g, ""), new Vector2(this.tryParseFloat(this.setPosition((_a = s[3]) !== null && _a !== void 0 ? _a : "0")), this.tryParseFloat(this.setPosition((_b = s[4]) !== null && _b !== void 0 ? _b : "0"))));
8004
8041
  }
8005
8042
  parseBreak(s) {
8006
- this.target.events.breaks.push(new BreakPoint({
8007
- startTime: this.target.getOffsetTime(this.tryParseInt(this.setPosition(s[1]))),
8008
- endTime: this.target.getOffsetTime(this.tryParseInt(this.setPosition(s[2]))),
8009
- }));
8043
+ this.target.events.breaks.push(new BreakPoint(this.target.getOffsetTime(this.tryParseInt(this.setPosition(s[1]))), this.target.getOffsetTime(this.tryParseInt(this.setPosition(s[2])))));
8010
8044
  }
8011
8045
  }
8012
8046
 
@@ -8079,6 +8113,7 @@ class BeatmapMetadataDecoder extends SectionDecoder {
8079
8113
  }
8080
8114
  }
8081
8115
 
8116
+ /* eslint-disable @typescript-eslint/prefer-literal-enum-member */
8082
8117
  /**
8083
8118
  * Effects that can occur in an effect control point.
8084
8119
  */
@@ -8177,10 +8212,10 @@ class RGBColor {
8177
8212
  */
8178
8213
  toString() {
8179
8214
  if (this.a === 1) {
8180
- return `${this.r},${this.g},${this.b}`;
8215
+ return `${this.r.toString()},${this.g.toString()},${this.b.toString()}`;
8181
8216
  }
8182
8217
  else {
8183
- return `${this.r},${this.g},${this.b},${this.a}`;
8218
+ return `${this.r.toString()},${this.g.toString()},${this.b.toString()},${this.a.toString()}`;
8184
8219
  }
8185
8220
  }
8186
8221
  /**
@@ -8462,7 +8497,8 @@ class Command {
8462
8497
  return this.parameterType !== undefined;
8463
8498
  }
8464
8499
  toString() {
8465
- return `${this.startTime} -> ${this.endTime}, ${this.startValue} -> ${this.endValue} ${this.easing}`;
8500
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
8501
+ return `${this.startTime.toString()} -> ${this.endTime.toString()}, ${this.startValue} -> ${this.endValue} ${this.easing.toString()}`;
8466
8502
  }
8467
8503
  }
8468
8504
 
@@ -8684,7 +8720,7 @@ class CommandLoop extends CommandTimelineGroup {
8684
8720
  return commands;
8685
8721
  }
8686
8722
  toString() {
8687
- return `${this.loopStartTime} x${this.totalIterations}`;
8723
+ return `${this.loopStartTime.toString()} x${this.totalIterations.toString()}`;
8688
8724
  }
8689
8725
  }
8690
8726
 
@@ -8700,7 +8736,7 @@ class CommandTrigger extends CommandTimelineGroup {
8700
8736
  this.groupNumber = groupNumber;
8701
8737
  }
8702
8738
  toString() {
8703
- return `${this.triggerName} ${this.triggerStartTime} -> ${this.triggerEndTime} (${this.groupNumber})`;
8739
+ return `${this.triggerName} ${this.triggerStartTime.toString()} -> ${this.triggerEndTime.toString()} (${this.groupNumber.toString()})`;
8704
8740
  }
8705
8741
  }
8706
8742
 
@@ -8736,7 +8772,7 @@ class StoryboardSprite extends StoryboardElement {
8736
8772
  // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero,
8737
8773
  // anything before that point can be ignored (the sprite is not visible after all).
8738
8774
  const alphaCommands = [];
8739
- let command = this.timelineGroup.alpha.commands[0];
8775
+ let command = this.timelineGroup.alpha.commands.at(0);
8740
8776
  if (command) {
8741
8777
  alphaCommands.push({
8742
8778
  startTime: command.startTime,
@@ -8744,7 +8780,7 @@ class StoryboardSprite extends StoryboardElement {
8744
8780
  });
8745
8781
  }
8746
8782
  for (const l of this.loops) {
8747
- command = l.alpha.commands[0];
8783
+ command = l.alpha.commands.at(0);
8748
8784
  if (command) {
8749
8785
  alphaCommands.push({
8750
8786
  startTime: command.startTime + l.loopStartTime,
@@ -8826,7 +8862,7 @@ class StoryboardSprite extends StoryboardElement {
8826
8862
  return trigger;
8827
8863
  }
8828
8864
  toString() {
8829
- return `${this.path}, ${this.origin}, ${this.initialPosition}`;
8865
+ return `${this.path}, ${this.origin}, ${this.initialPosition.toString()}`;
8830
8866
  }
8831
8867
  }
8832
8868
 
@@ -8963,7 +8999,7 @@ class StoryboardEventsDecoder extends SectionDecoder {
8963
8999
  if (!s[3]) {
8964
9000
  s[3] = this.setPosition(s[2]);
8965
9001
  }
8966
- const easing = (this.tryParseInt(this.setPosition(s[1])));
9002
+ const easing = this.tryParseInt(this.setPosition(s[1]));
8967
9003
  const startTime = this.tryParseInt(this.setPosition(s[2]));
8968
9004
  const endTime = this.tryParseInt(this.setPosition(s[3]));
8969
9005
  switch (s[0]) {
@@ -9221,13 +9257,13 @@ class BeatmapDecoder extends Decoder {
9221
9257
  /**
9222
9258
  * @param str The string to decode.
9223
9259
  * @param mode The mode to parse the beatmap as. Defaults to osu!standard.
9224
- * @param parseStoryboard Whether to parse the beatmap's storyboard.
9260
+ * @param parseStoryboard Whether to parse the beatmap's storyboard. Defaults to `true`.
9225
9261
  */
9226
9262
  decode(str, mode = exports.Modes.osu, parseStoryboard = true) {
9227
9263
  super.decode(str);
9228
9264
  this.finalResult.mode = mode;
9229
9265
  if (parseStoryboard) {
9230
- const eventsDecoder = (this.decoders[BeatmapSection.events]);
9266
+ const eventsDecoder = this.decoders[BeatmapSection.events];
9231
9267
  if (eventsDecoder.storyboardLines.length > 0) {
9232
9268
  this.finalResult.events.storyboard = new StoryboardDecoder(this.finalResult.formatVersion).decode(eventsDecoder.storyboardLines.join("\n")).result;
9233
9269
  }
@@ -9315,6 +9351,9 @@ class Encoder {
9315
9351
  * The base of all encoders.
9316
9352
  */
9317
9353
  class BaseEncoder {
9354
+ /**
9355
+ * @param encodeSections Whether sections should be encoded. Defaults to `true`.
9356
+ */
9318
9357
  constructor(encodeSections = true) {
9319
9358
  /**
9320
9359
  * The target of the encoding process.
@@ -9393,10 +9432,10 @@ class BeatmapColorEncoder extends BeatmapBaseEncoder {
9393
9432
  }
9394
9433
  for (let i = 0; i < colors.combo.length; ++i) {
9395
9434
  const color = colors.combo[i];
9396
- this.write(`Combo${i + 1}: `);
9397
- this.write(`${color.r},`);
9398
- this.write(`${color.g},`);
9399
- this.write(`${color.b}`);
9435
+ this.write(`Combo${(i + 1).toString()}: `);
9436
+ this.write(`${color.r.toString()},`);
9437
+ this.write(`${color.g.toString()},`);
9438
+ this.write(color.b.toString());
9400
9439
  this.writeLine();
9401
9440
  }
9402
9441
  }
@@ -9429,14 +9468,14 @@ class BeatmapControlPointsEncoder extends BeatmapBaseEncoder {
9429
9468
  for (const group of Object.values(this.controlPointGroups).sort((a, b) => a.time - b.time)) {
9430
9469
  // If the group contains a timing control point, it needs to be output separately.
9431
9470
  if (group.timing) {
9432
- this.write(`${group.timing.time},`);
9433
- this.write(`${group.timing.msPerBeat},`);
9471
+ this.write(`${group.timing.time.toString()},`);
9472
+ this.write(`${group.timing.msPerBeat.toString()},`);
9434
9473
  this.outputControlPointGroup(group, true);
9435
9474
  }
9436
9475
  // Output any remaining effects as secondary non-timing control point.
9437
- this.write(`${group.time},`);
9476
+ this.write(`${group.time.toString()},`);
9438
9477
  const difficultyPoint = (_a = group.difficulty) !== null && _a !== void 0 ? _a : this.map.controlPoints.difficulty.controlPointAt(group.time);
9439
- this.write(`${-100 / difficultyPoint.speedMultiplier},`);
9478
+ this.write(`${(-100 / difficultyPoint.speedMultiplier).toString()},`);
9440
9479
  this.outputControlPointGroup(group, false);
9441
9480
  }
9442
9481
  }
@@ -9484,7 +9523,7 @@ class BeatmapControlPointsEncoder extends BeatmapBaseEncoder {
9484
9523
  if (effectPoint.omitFirstBarLine) {
9485
9524
  effectFlags |= EffectFlags.omitFirstBarLine;
9486
9525
  }
9487
- this.write(`${((_c = group.timing) !== null && _c !== void 0 ? _c : this.map.controlPoints.timing.controlPointAt(group.time)).timeSignature},`);
9526
+ this.write(`${((_c = group.timing) !== null && _c !== void 0 ? _c : this.map.controlPoints.timing.controlPointAt(group.time)).timeSignature.toString()},`);
9488
9527
  this.write(`${samplePoint.sampleBank.toString()},`);
9489
9528
  this.write(`${samplePoint.customSampleBank.toString()},`);
9490
9529
  this.write(`${samplePoint.sampleVolume.toString()},`);
@@ -9503,12 +9542,12 @@ class BeatmapDifficultyEncoder extends BeatmapBaseEncoder {
9503
9542
  this.writeLine("[Difficulty]");
9504
9543
  }
9505
9544
  const { difficulty } = this.map;
9506
- this.writeLine(`HPDrainRate: ${difficulty.hp}`);
9507
- this.writeLine(`CircleSize: ${difficulty.cs}`);
9508
- this.writeLine(`OverallDifficulty: ${difficulty.od}`);
9509
- this.writeLine(`ApproachRate: ${difficulty.ar}`);
9510
- this.writeLine(`SliderMultiplier: ${difficulty.sliderMultiplier}`);
9511
- this.writeLine(`SliderTickRate: ${difficulty.sliderTickRate}`);
9545
+ this.writeLine(`HPDrainRate: ${difficulty.hp.toString()}`);
9546
+ this.writeLine(`CircleSize: ${difficulty.cs.toString()}`);
9547
+ this.writeLine(`OverallDifficulty: ${difficulty.od.toString()}`);
9548
+ this.writeLine(`ApproachRate: ${difficulty.ar.toString()}`);
9549
+ this.writeLine(`SliderMultiplier: ${difficulty.sliderMultiplier.toString()}`);
9550
+ this.writeLine(`SliderTickRate: ${difficulty.sliderTickRate.toString()}`);
9512
9551
  }
9513
9552
  }
9514
9553
 
@@ -9524,10 +9563,10 @@ class BeatmapEditorEncoder extends BeatmapBaseEncoder {
9524
9563
  if (editor.bookmarks.length > 0) {
9525
9564
  this.writeLine(editor.bookmarks.join());
9526
9565
  }
9527
- this.writeLine(`DistanceSpacing: ${editor.distanceSnap}`);
9528
- this.writeLine(`BeatDivisor: ${editor.beatDivisor}`);
9529
- this.writeLine(`GridSize: ${editor.gridSize}`);
9530
- this.writeLine(`TimelineZoom: ${editor.timelineZoom}`);
9566
+ this.writeLine(`DistanceSpacing: ${editor.distanceSnap.toString()}`);
9567
+ this.writeLine(`BeatDivisor: ${editor.beatDivisor.toString()}`);
9568
+ this.writeLine(`GridSize: ${editor.gridSize.toString()}`);
9569
+ this.writeLine(`TimelineZoom: ${editor.timelineZoom.toString()}`);
9531
9570
  }
9532
9571
  }
9533
9572
 
@@ -9584,10 +9623,10 @@ class StoryboardEventsEncoder extends StoryboardBaseEncoder {
9584
9623
  this.write(`${layerType},`);
9585
9624
  this.write(`${element.origin},`);
9586
9625
  this.write(`"${element.path}",`);
9587
- this.write(`${element.initialPosition},`);
9588
- this.write(`${element.frameCount},`);
9589
- this.write(`${element.frameDelay},`);
9590
- this.writeLine(`${element.loopType}`);
9626
+ this.write(`${element.initialPosition.toString()},`);
9627
+ this.write(`${element.frameCount.toString()},`);
9628
+ this.write(`${element.frameDelay.toString()},`);
9629
+ this.writeLine(element.loopType.toString());
9591
9630
  this.encodeElement(element);
9592
9631
  }
9593
9632
  else if (element instanceof StoryboardSprite) {
@@ -9595,15 +9634,15 @@ class StoryboardEventsEncoder extends StoryboardBaseEncoder {
9595
9634
  this.write(`${layerType},`);
9596
9635
  this.write(`${element.origin},`);
9597
9636
  this.write(`"${element.path}",`);
9598
- this.writeLine(`${element.initialPosition}`);
9637
+ this.writeLine(element.initialPosition.toString());
9599
9638
  this.encodeElement(element);
9600
9639
  }
9601
9640
  else if (element instanceof StoryboardSample) {
9602
9641
  this.write(`${exports.StoryboardEventType.sample},`);
9603
- this.write(`${element.startTime},`);
9642
+ this.write(`${element.startTime.toString()},`);
9604
9643
  this.write(`${layerType},`);
9605
9644
  this.write(`"${element.path}",`);
9606
- this.writeLine(`${element.volume}`);
9645
+ this.writeLine(element.volume.toString());
9607
9646
  }
9608
9647
  }
9609
9648
  }
@@ -9620,21 +9659,21 @@ class StoryboardEventsEncoder extends StoryboardBaseEncoder {
9620
9659
  if (group instanceof CommandLoop) {
9621
9660
  this.write(" ");
9622
9661
  this.write(`${exports.StoryboardCommandType.loop},`);
9623
- this.write(`${group.startTime},`);
9624
- this.write(`${group.totalIterations}`);
9662
+ this.write(`${group.startTime.toString()},`);
9663
+ this.write(group.totalIterations.toString());
9625
9664
  }
9626
9665
  else if (group instanceof CommandTrigger) {
9627
9666
  this.write(" ");
9628
9667
  this.write(`${exports.StoryboardCommandType.trigger},`);
9629
- this.write(`${group.triggerName}`);
9668
+ this.write(group.triggerName);
9630
9669
  if (group.triggerEndTime !== Number.MAX_SAFE_INTEGER) {
9631
9670
  this.write(",");
9632
- this.write(`${group.triggerStartTime},`);
9633
- this.write(`${group.triggerEndTime}`);
9671
+ this.write(`${group.triggerStartTime.toString()},`);
9672
+ this.write(group.triggerEndTime.toString());
9634
9673
  }
9635
9674
  if (group.groupNumber !== 0) {
9636
9675
  this.write(",");
9637
- this.write(`${group.groupNumber}`);
9676
+ this.write(group.groupNumber.toString());
9638
9677
  }
9639
9678
  }
9640
9679
  this.encodeTimeline(group.alpha);
@@ -9655,9 +9694,11 @@ class StoryboardEventsEncoder extends StoryboardBaseEncoder {
9655
9694
  encodeCommand(command) {
9656
9695
  this.write(" ");
9657
9696
  this.write(`${command.type},`);
9658
- this.write(`${command.easing},`);
9659
- this.write(`${command.startTime},`);
9660
- this.write(command.startTime !== command.endTime ? `${command.endTime}` : "");
9697
+ this.write(`${command.easing.toString()},`);
9698
+ this.write(`${command.startTime.toString()},`);
9699
+ this.write(command.startTime !== command.endTime
9700
+ ? command.endTime.toString()
9701
+ : "");
9661
9702
  this.write(",");
9662
9703
  if (command.startValue instanceof Vector2 &&
9663
9704
  command.endValue instanceof Vector2) {
@@ -9752,14 +9793,14 @@ class BeatmapEventsEncoder extends BeatmapBaseEncoder {
9752
9793
  this.writeLine("//Background and Video Events");
9753
9794
  const { events } = this.map;
9754
9795
  if (events.background) {
9755
- this.writeLine(`0,0,"${events.background.filename}",${events.background.offset.x},${events.background.offset.y}`);
9796
+ this.writeLine(`0,0,"${events.background.filename}",${events.background.offset.x.toString()},${events.background.offset.y.toString()}`);
9756
9797
  }
9757
9798
  if (events.video) {
9758
- this.writeLine(`Video,${events.video.startTime},"${events.video.filename}",${events.video.offset.x},${events.video.offset.y}`);
9799
+ this.writeLine(`Video,${events.video.startTime.toString()},"${events.video.filename}",${events.video.offset.x.toString()},${events.video.offset.y.toString()}`);
9759
9800
  }
9760
9801
  this.writeLine("//Break Periods");
9761
9802
  for (const b of events.breaks) {
9762
- this.writeLine(`2,${b.startTime},${b.endTime}`);
9803
+ this.writeLine(`2,${b.startTime.toString()},${b.endTime.toString()}`);
9763
9804
  }
9764
9805
  if (this.map.events.storyboard) {
9765
9806
  this.writeLine(new StoryboardEncoder(this.map.events.storyboard, false).encode().result);
@@ -9787,20 +9828,20 @@ class BeatmapGeneralEncoder extends BeatmapBaseEncoder {
9787
9828
  if (general.audioFilename) {
9788
9829
  this.writeLine(`AudioFilename: ${general.audioFilename}`);
9789
9830
  }
9790
- this.writeLine(`AudioLeadIn: ${general.audioLeadIn}`);
9791
- this.writeLine(`PreviewTime: ${general.previewTime}`);
9792
- this.writeLine(`Countdown: ${general.countdown}`);
9831
+ this.writeLine(`AudioLeadIn: ${general.audioLeadIn.toString()}`);
9832
+ this.writeLine(`PreviewTime: ${general.previewTime.toString()}`);
9833
+ this.writeLine(`Countdown: ${general.countdown.toString()}`);
9793
9834
  this.writeLine(`SampleSet: ${this.sampleBankToString(general.sampleBank)}`);
9794
- this.writeLine(`StackLeniency: ${general.stackLeniency}`);
9795
- this.writeLine(`Mode: ${general.mode}`);
9796
- this.writeLine(`LetterboxInBreaks: ${general.letterBoxInBreaks ? 1 : 0}`);
9835
+ this.writeLine(`StackLeniency: ${general.stackLeniency.toString()}`);
9836
+ this.writeLine(`Mode: ${general.mode.toString()}`);
9837
+ this.writeLine(`LetterboxInBreaks: ${general.letterBoxInBreaks ? "1" : "0"}`);
9797
9838
  if (general.epilepsyWarning) {
9798
9839
  this.writeLine("EpilepsyWarning: 1");
9799
9840
  }
9800
9841
  if (general.countdownOffset > 0) {
9801
- this.writeLine(`CountdownOffset: ${general.countdownOffset}`);
9842
+ this.writeLine(`CountdownOffset: ${general.countdownOffset.toString()}`);
9802
9843
  }
9803
- this.writeLine(`WidescreenStoryboard: ${general.widescreenStoryboard ? 1 : 0}`);
9844
+ this.writeLine(`WidescreenStoryboard: ${general.widescreenStoryboard ? "1" : "0"}`);
9804
9845
  if (general.samplesMatchPlaybackRate) {
9805
9846
  this.writeLine("SamplesMatchPlaybackRate: 1");
9806
9847
  }
@@ -9820,10 +9861,10 @@ class BeatmapHitObjectsEncoder extends BeatmapBaseEncoder {
9820
9861
  }
9821
9862
  }
9822
9863
  encodeHitObject(object) {
9823
- this.write(`${object.position.x},`);
9824
- this.write(`${object.position.y},`);
9825
- this.write(`${object.startTime},`);
9826
- this.write(`${object.type},`);
9864
+ this.write(`${object.position.x.toString()},`);
9865
+ this.write(`${object.position.y.toString()},`);
9866
+ this.write(`${object.startTime.toString()},`);
9867
+ this.write(`${object.type.toString()},`);
9827
9868
  this.write(`${this.samplesToHitSoundType(object.samples).toString()},`);
9828
9869
  if (object instanceof Slider) {
9829
9870
  this.addSliderPath(object);
@@ -9831,7 +9872,7 @@ class BeatmapHitObjectsEncoder extends BeatmapBaseEncoder {
9831
9872
  }
9832
9873
  else {
9833
9874
  if (object instanceof Spinner) {
9834
- this.write(`${object.endTime},`);
9875
+ this.write(`${object.endTime.toString()},`);
9835
9876
  }
9836
9877
  this.write(this.getSampleBank(object.samples));
9837
9878
  }
@@ -9864,11 +9905,11 @@ class BeatmapHitObjectsEncoder extends BeatmapBaseEncoder {
9864
9905
  // start position of the slider.
9865
9906
  for (let i = 1; i < slider.path.controlPoints.length; ++i) {
9866
9907
  const realPosition = slider.path.controlPoints[i].add(slider.position);
9867
- this.write(`${realPosition.x}:${realPosition.y}`);
9908
+ this.write(`${realPosition.x.toString()}:${realPosition.y.toString()}`);
9868
9909
  this.write(i != slider.path.controlPoints.length - 1 ? "|" : ",");
9869
9910
  }
9870
- this.write(`${slider.repeatCount + 1},`);
9871
- this.write(`${slider.path.expectedDistance},`);
9911
+ this.write(`${slider.spanCount.toString()},`);
9912
+ this.write(`${slider.path.expectedDistance.toString()},`);
9872
9913
  // edgeSamples
9873
9914
  for (let i = 0; i < slider.nodeSamples.length; ++i) {
9874
9915
  this.write(this.samplesToHitSoundType(slider.nodeSamples[i]).toString());
@@ -9887,14 +9928,14 @@ class BeatmapHitObjectsEncoder extends BeatmapBaseEncoder {
9887
9928
  const addBank = (_d = (_c = samples.find((s) => s instanceof BankHitSampleInfo &&
9888
9929
  s.name &&
9889
9930
  s.name !== BankHitSampleInfo.HIT_NORMAL)) === null || _c === void 0 ? void 0 : _c.bank) !== null && _d !== void 0 ? _d : exports.SampleBank.none;
9890
- let sampleBankString = `${normalBank}:${addBank}`;
9931
+ let sampleBankString = `${normalBank.toString()}:${addBank.toString()}`;
9891
9932
  if (!banksOnly) {
9892
- const firstSample = samples[0];
9933
+ const firstSample = samples.at(0);
9893
9934
  sampleBankString += ":";
9894
9935
  sampleBankString += `${firstSample instanceof BankHitSampleInfo
9895
- ? firstSample.customSampleBank
9896
- : 0}:`;
9897
- sampleBankString += `${(_e = firstSample === null || firstSample === void 0 ? void 0 : firstSample.volume) !== null && _e !== void 0 ? _e : 100}:`;
9936
+ ? firstSample.customSampleBank.toString()
9937
+ : "0"}:`;
9938
+ sampleBankString += `${((_e = firstSample === null || firstSample === void 0 ? void 0 : firstSample.volume) !== null && _e !== void 0 ? _e : 100).toString()}:`;
9898
9939
  if (firstSample instanceof FileHitSampleInfo) {
9899
9940
  sampleBankString += `${this.sampleBankToString(exports.SampleBank.none)}-${firstSample.filename}`;
9900
9941
  }
@@ -9916,7 +9957,6 @@ class BeatmapHitObjectsEncoder extends BeatmapBaseEncoder {
9916
9957
  */
9917
9958
  class BeatmapMetadataEncoder extends BeatmapBaseEncoder {
9918
9959
  encodeInternal() {
9919
- var _a, _b;
9920
9960
  if (this.encodeSections) {
9921
9961
  this.writeLine("[Metadata]");
9922
9962
  }
@@ -9937,11 +9977,11 @@ class BeatmapMetadataEncoder extends BeatmapBaseEncoder {
9937
9977
  if (metadata.tags.length > 0) {
9938
9978
  this.writeLine(`Tags: ${metadata.tags.join(" ")}`);
9939
9979
  }
9940
- if (((_a = metadata.beatmapId) !== null && _a !== void 0 ? _a : -1) > 0) {
9941
- this.writeLine(`BeatmapID: ${metadata.beatmapId}`);
9980
+ if (metadata.beatmapId !== undefined && metadata.beatmapId > 0) {
9981
+ this.writeLine(`BeatmapID: ${metadata.beatmapId.toString()}`);
9942
9982
  }
9943
- if (((_b = metadata.beatmapSetId) !== null && _b !== void 0 ? _b : -1) > 0) {
9944
- this.writeLine(`BeatmapSetID: ${metadata.beatmapSetId}`);
9983
+ if (metadata.beatmapSetId !== undefined && metadata.beatmapSetId > 0) {
9984
+ this.writeLine(`BeatmapSetID: ${metadata.beatmapSetId.toString()}`);
9945
9985
  }
9946
9986
  }
9947
9987
  }
@@ -9959,7 +9999,7 @@ class BeatmapEncoder extends Encoder {
9959
9999
  this.latestVersion = 14;
9960
10000
  }
9961
10001
  encodeInternal() {
9962
- this.writeLine(`osu file format v${this.latestVersion}`);
10002
+ this.writeLine(`osu file format v${this.latestVersion.toString()}`);
9963
10003
  this.writeLine();
9964
10004
  super.encodeInternal();
9965
10005
  }
@@ -10024,6 +10064,56 @@ exports.BeatmapLanguage = void 0;
10024
10064
  BeatmapLanguage[BeatmapLanguage["other"] = 14] = "other";
10025
10065
  })(exports.BeatmapLanguage || (exports.BeatmapLanguage = {}));
10026
10066
 
10067
+ class Bin {
10068
+ constructor() {
10069
+ this.difficulty = 0;
10070
+ this.time = 0;
10071
+ this.noteCount = 0;
10072
+ }
10073
+ /**
10074
+ * Creates a 2D grid of bins using bilinear interpolation.
10075
+ *
10076
+ * Notes are distributed across neighboring bins weighted by their fractional position.
10077
+ */
10078
+ static createBins(difficulties, times, difficultyDimensionLength, timeDimensionLength) {
10079
+ const maxDifficulty = MathUtils.max(difficulties);
10080
+ const endTime = MathUtils.max(times);
10081
+ const bins = Utils.initializeArray(timeDimensionLength * difficultyDimensionLength, () => new Bin());
10082
+ for (let timeIndex = 0; timeIndex < timeDimensionLength; ++timeIndex) {
10083
+ const time = (endTime * timeIndex) / (timeDimensionLength - 1);
10084
+ for (let diffIndex = 0; diffIndex < difficultyDimensionLength; ++diffIndex) {
10085
+ const binIndex = difficultyDimensionLength * timeIndex + diffIndex;
10086
+ bins[binIndex].time = time;
10087
+ // We do not create a 0 difficulty bin because 0 difficulty notes do not contribute to star rating.
10088
+ bins[binIndex].difficulty =
10089
+ (maxDifficulty * (diffIndex + 1)) /
10090
+ difficultyDimensionLength;
10091
+ }
10092
+ }
10093
+ for (let noteIndex = 0; noteIndex < difficulties.length; ++noteIndex) {
10094
+ const timeBinIndex = timeDimensionLength * (times[noteIndex] / endTime);
10095
+ const difficultyBinIndex = difficultyDimensionLength *
10096
+ (difficulties[noteIndex] / maxDifficulty) -
10097
+ 1;
10098
+ const timeLower = Math.min(Math.trunc(timeBinIndex), timeDimensionLength - 1);
10099
+ const timeUpper = Math.min(timeLower + 1, timeDimensionLength - 1);
10100
+ const timeWeight = timeBinIndex - timeLower;
10101
+ const difficultyLower = Math.floor(difficultyBinIndex);
10102
+ const difficultyUpper = Math.min(difficultyLower + 1, difficultyDimensionLength - 1);
10103
+ const difficultyWeight = difficultyBinIndex - difficultyLower;
10104
+ // The lower bound of difficulty can be -1, corresponding to buckets with 0 difficulty.
10105
+ // We do not store those since they do not contribute to star rating.
10106
+ if (difficultyLower >= 0) {
10107
+ bins[difficultyDimensionLength * timeLower + difficultyLower].noteCount += (1 - timeWeight) * (1 - difficultyWeight);
10108
+ bins[difficultyDimensionLength * timeUpper + difficultyLower].noteCount += timeWeight * (1 - difficultyWeight);
10109
+ }
10110
+ bins[difficultyDimensionLength * timeLower + difficultyUpper].noteCount += (1 - timeWeight) * difficultyWeight;
10111
+ bins[difficultyDimensionLength * timeUpper + difficultyUpper].noteCount += timeWeight * difficultyWeight;
10112
+ }
10113
+ return bins;
10114
+ }
10115
+ }
10116
+
10027
10117
  class ZeroCrossingBracketing {
10028
10118
  /**
10029
10119
  * Detect a range containing at least one root.
@@ -10254,6 +10344,93 @@ class Brent {
10254
10344
  }
10255
10345
  }
10256
10346
 
10347
+ /**
10348
+ * {@link https://www.sciencedirect.com/science/article/abs/pii/S0965997896000518?via%3Dihub Chandrupatla}'s root-finding algorithm.
10349
+ */
10350
+ class Chandrupatla {
10351
+ /**
10352
+ * Finds the root of a function using {@link https://www.sciencedirect.com/science/article/abs/pii/S0965997896000518?via%3Dihub Chandrupatla}'s
10353
+ * method, expanding the bounds if the root is not located within.
10354
+ *
10355
+ * Expansion only occurs for the upward bound, as this function is optimized for functions of range [0, x), which is
10356
+ * useful for finding positive roots.
10357
+ *
10358
+ * @param f The function of which to find the root.
10359
+ * @param guessLowerBound The lower bound of the function inputs.
10360
+ * @param guessUpperBound The upper bound of the function inputs.
10361
+ * @param options Options for the root-finding algorithm.
10362
+ * @returns The root of the function.
10363
+ */
10364
+ static findRootExpand(f, guessLowerBound, guessUpperBound, options) {
10365
+ const { maxIterations = 25, accuracy = 1e-6, expansionFactor = 2, maxExpansions = 32, } = options !== null && options !== void 0 ? options : {};
10366
+ let a = guessLowerBound;
10367
+ let b = guessUpperBound;
10368
+ let fa = f(a);
10369
+ let fb = f(b);
10370
+ let expansions = 0;
10371
+ while (fa * fb > 0) {
10372
+ a = b;
10373
+ b *= expansionFactor;
10374
+ fa = fb;
10375
+ fb = f(b);
10376
+ ++expansions;
10377
+ if (expansions > maxExpansions) {
10378
+ throw new Error("Chandrupatla: Maximum number of expansions exceeded.");
10379
+ }
10380
+ }
10381
+ let t = 0.5;
10382
+ for (let i = 0; i < maxIterations; ++i) {
10383
+ const xt = a + t * (b - a);
10384
+ const ft = f(xt);
10385
+ let c;
10386
+ let fc;
10387
+ if (Math.sign(ft) == Math.sign(fa)) {
10388
+ c = a;
10389
+ fc = fa;
10390
+ }
10391
+ else {
10392
+ c = b;
10393
+ b = a;
10394
+ fc = fb;
10395
+ fb = fa;
10396
+ }
10397
+ a = xt;
10398
+ fa = ft;
10399
+ let xm;
10400
+ let fm;
10401
+ if (Math.abs(fa) < Math.abs(fb)) {
10402
+ xm = a;
10403
+ fm = fa;
10404
+ }
10405
+ else {
10406
+ xm = b;
10407
+ fm = fb;
10408
+ }
10409
+ if (fm == 0) {
10410
+ return xm;
10411
+ }
10412
+ const tol = 2 * accuracy * Math.abs(xm) + 2 * accuracy;
10413
+ const tlim = tol / Math.abs(b - c);
10414
+ if (tlim > 0.5) {
10415
+ return xm;
10416
+ }
10417
+ const chi = (a - b) / (c - b);
10418
+ const phi = (fa - fb) / (fc - fb);
10419
+ const iqi = phi * phi < chi && (1 - phi) * (1 - phi) < chi;
10420
+ if (iqi) {
10421
+ t =
10422
+ ((fa / (fb - fa)) * fc) / (fb - fc) +
10423
+ (((((c - a) / (b - a)) * fa) / (fc - fa)) * fb) / (fc - fb);
10424
+ }
10425
+ else {
10426
+ t = 0.5;
10427
+ }
10428
+ t = Math.min(1 - tlim, Math.max(tlim, t));
10429
+ }
10430
+ return 0;
10431
+ }
10432
+ }
10433
+
10257
10434
  /******************************************************************************
10258
10435
  Copyright (c) Microsoft Corporation.
10259
10436
 
@@ -10268,7 +10445,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
10268
10445
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
10269
10446
  PERFORMANCE OF THIS SOFTWARE.
10270
10447
  ***************************************************************************** */
10271
- /* global Reflect, Promise, SuppressedError, Symbol */
10448
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
10272
10449
 
10273
10450
 
10274
10451
  function __awaiter(thisArg, _arguments, P, generator) {
@@ -10289,6 +10466,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
10289
10466
  /**
10290
10467
  * The base of API request builders.
10291
10468
  */
10469
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
10292
10470
  class APIRequestBuilder {
10293
10471
  constructor() {
10294
10472
  /**
@@ -10357,22 +10535,24 @@ class APIRequestBuilder {
10357
10535
  .then((res) => __awaiter(this, void 0, void 0, function* () {
10358
10536
  ++this.fetchAttempts;
10359
10537
  if (res.status >= 500 && this.fetchAttempts < 5) {
10360
- console.error(`Request to ${url} failed with the following error: ${yield res.text()}; ${this.fetchAttempts} attempts so far; retrying`);
10361
- return resolve(this.sendRequest());
10538
+ console.error(`Request to ${url} failed with the following error: ${yield res.text()}; ${this.fetchAttempts.toString()} attempts so far; retrying`);
10539
+ resolve(this.sendRequest());
10540
+ return;
10362
10541
  }
10363
10542
  this.fetchAttempts = 0;
10364
- return resolve({
10543
+ resolve({
10365
10544
  data: Buffer.from(yield res.arrayBuffer()),
10366
10545
  statusCode: res.status,
10367
10546
  });
10368
10547
  }))
10369
10548
  .catch((e) => {
10370
- console.error(`Request to ${url} failed with the following error: ${e.message}; ${this.fetchAttempts} attempts so far; aborting`);
10549
+ console.error(`Request to ${url} failed with the following error: ${e.message}; ${this.fetchAttempts.toString()} attempts so far; aborting`);
10371
10550
  this.fetchAttempts = 0;
10372
- return resolve({
10551
+ resolve({
10373
10552
  data: Buffer.from([]),
10374
10553
  statusCode: 400,
10375
10554
  });
10555
+ return;
10376
10556
  });
10377
10557
  });
10378
10558
  }
@@ -10581,6 +10761,196 @@ class Polynomial {
10581
10761
  }
10582
10762
  return sum;
10583
10763
  }
10764
+ /**
10765
+ * Solve for the exact real roots of any polynomial up to degree 4.
10766
+ *
10767
+ * @param coefficients The coefficients of the polynomial, in ascending order ([1, 3, 5] -> x^2 + 3x + 5).
10768
+ * @returns The real roots of the polynomial, and `null` if the root does not exist.
10769
+ */
10770
+ static solve(coefficients) {
10771
+ let xVals = [];
10772
+ switch (coefficients.length) {
10773
+ case 5:
10774
+ xVals = this.solveP4(coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4]).roots;
10775
+ break;
10776
+ case 4:
10777
+ xVals = this.solveP3(coefficients[0], coefficients[1], coefficients[2], coefficients[3]).roots;
10778
+ break;
10779
+ case 3:
10780
+ xVals = this.solveP2(coefficients[0], coefficients[1], coefficients[2]).roots;
10781
+ break;
10782
+ case 2:
10783
+ xVals = this.solveP2(0, coefficients[0], coefficients[1]).roots;
10784
+ break;
10785
+ }
10786
+ return xVals;
10787
+ }
10788
+ // https://github.com/sasamil/Quartic/blob/master/quartic.cpp
10789
+ static solveP4(a, b, c, d, e) {
10790
+ const result = {
10791
+ count: 0,
10792
+ roots: new Array(4),
10793
+ };
10794
+ if (a === 0) {
10795
+ const xValsCubic = this.solveP3(b, c, d, e);
10796
+ result.count = xValsCubic.count;
10797
+ result.roots[0] = xValsCubic.roots[0];
10798
+ result.roots[1] = xValsCubic.roots[1];
10799
+ result.roots[2] = xValsCubic.roots[2];
10800
+ result.roots[3] = null;
10801
+ return result;
10802
+ }
10803
+ b /= a;
10804
+ c /= a;
10805
+ d /= a;
10806
+ e /= a;
10807
+ const a3 = -c;
10808
+ const b3 = b * d - 4 * e;
10809
+ const c3 = -b * b * e - d * d + 4 * c * e;
10810
+ const x3 = this.solveP3(1, a3, b3, c3);
10811
+ let q1;
10812
+ let q2;
10813
+ let p1;
10814
+ let p2;
10815
+ let sqD;
10816
+ let y = x3.roots[0];
10817
+ // Get the y value with the highest absolute value.
10818
+ if (x3.count !== 1) {
10819
+ if (Math.abs(x3.roots[1]) > Math.abs(y)) {
10820
+ y = x3.roots[1];
10821
+ }
10822
+ if (Math.abs(x3.roots[2]) > Math.abs(y)) {
10823
+ y = x3.roots[2];
10824
+ }
10825
+ }
10826
+ let upperD = y * y - 4 * e;
10827
+ if (Precision.almostEquals(upperD, 0)) {
10828
+ q1 = q2 = y * 0.5;
10829
+ upperD = b * b - 4 * (c - y);
10830
+ if (Precision.almostEquals(upperD, 0)) {
10831
+ p1 = p2 = b * 0.5;
10832
+ }
10833
+ else {
10834
+ sqD = Math.sqrt(upperD);
10835
+ p1 = (b + sqD) * 0.5;
10836
+ p2 = (b - sqD) * 0.5;
10837
+ }
10838
+ }
10839
+ else {
10840
+ sqD = Math.sqrt(upperD);
10841
+ q1 = (y + sqD) * 0.5;
10842
+ q2 = (y - sqD) * 0.5;
10843
+ p1 = (b * q1 - d) / (q1 - q2);
10844
+ p2 = (d - b * q2) / (q1 - q2);
10845
+ }
10846
+ // Solving quadratic eq. - x^2 + p1*x + q1 = 0.
10847
+ upperD = p1 * p1 - 4 * q1;
10848
+ if (upperD >= 0) {
10849
+ result.count += 2;
10850
+ sqD = Math.sqrt(upperD);
10851
+ result.roots[0] = (-p1 + sqD) * 0.5;
10852
+ result.roots[1] = (-p1 - sqD) * 0.5;
10853
+ }
10854
+ // Solving quadratic eq. - x^2 + p2*x + q2 = 0.
10855
+ upperD = p2 * p2 - 4 * q2;
10856
+ if (upperD >= 0) {
10857
+ result.count += 2;
10858
+ sqD = Math.sqrt(upperD);
10859
+ result.roots[2] = (-p2 + sqD) * 0.5;
10860
+ result.roots[3] = (-p2 - sqD) * 0.5;
10861
+ }
10862
+ // Put the null roots at the end of the array.
10863
+ result.roots.sort((a, b) => {
10864
+ if (a === null && b === null) {
10865
+ return 0;
10866
+ }
10867
+ if (a === null) {
10868
+ return 1;
10869
+ }
10870
+ if (b === null) {
10871
+ return -1;
10872
+ }
10873
+ return 0;
10874
+ });
10875
+ return result;
10876
+ }
10877
+ static solveP3(a, b, c, d) {
10878
+ const result = {
10879
+ count: 0,
10880
+ roots: new Array(3),
10881
+ };
10882
+ if (a === 0) {
10883
+ const xValsQuadratic = this.solveP2(b, c, d);
10884
+ result.count = xValsQuadratic.count;
10885
+ result.roots[0] = xValsQuadratic.roots[0];
10886
+ result.roots[1] = xValsQuadratic.roots[1];
10887
+ result.roots[2] = null;
10888
+ return result;
10889
+ }
10890
+ b /= a;
10891
+ c /= a;
10892
+ d /= a;
10893
+ const a2 = b * b;
10894
+ let q = (a2 - 3 * c) / 9;
10895
+ const q3 = q * q * q;
10896
+ const r = (b * (2 * a2 - 9 * c) + 27 * d) / 54;
10897
+ const r2 = r * r;
10898
+ if (r2 < q3) {
10899
+ let t = r / Math.sqrt(q3);
10900
+ t = MathUtils.clamp(t, -1, 1);
10901
+ t = Math.acos(t);
10902
+ b /= 3;
10903
+ q = -2 * Math.sqrt(q);
10904
+ result.count = 3;
10905
+ result.roots[0] = q * Math.cos(t / 3) - b;
10906
+ result.roots[1] = q * Math.cos((t + Math.PI * 2) / 3) - b;
10907
+ result.roots[2] = q * Math.cos((t - Math.PI * 2) / 3) - b;
10908
+ return result;
10909
+ }
10910
+ let upperA = -Math.cbrt(Math.abs(r) + Math.sqrt(r2 - q3));
10911
+ if (r < 0) {
10912
+ upperA = -upperA;
10913
+ }
10914
+ const upperB = upperA == 0 ? 0 : q / upperA;
10915
+ b /= 3;
10916
+ result.count = 1;
10917
+ result.roots[0] = upperA + upperB - b;
10918
+ if (Precision.almostEquals(0.5 * Math.sqrt(3) * (upperA - upperB), 0)) {
10919
+ result.count = 2;
10920
+ result.roots[1] = -0.5 * (upperA + upperB) - b;
10921
+ return result;
10922
+ }
10923
+ return result;
10924
+ }
10925
+ static solveP2(a, b, c) {
10926
+ const result = {
10927
+ count: 0,
10928
+ roots: new Array(2),
10929
+ };
10930
+ if (a === 0) {
10931
+ if (b === 0) {
10932
+ return result;
10933
+ }
10934
+ result.count = 1;
10935
+ result.roots[0] = -c / b;
10936
+ }
10937
+ const discriminant = b * b - 4 * a * c;
10938
+ if (discriminant < 0) {
10939
+ return result;
10940
+ }
10941
+ switch (discriminant) {
10942
+ case 0:
10943
+ result.count = 1;
10944
+ result.roots[0] = -b / (2 * a);
10945
+ break;
10946
+ default:
10947
+ result.count = 2;
10948
+ result.roots[0] = (-b + Math.sqrt(discriminant)) / (2 * a);
10949
+ result.roots[1] = (-b - Math.sqrt(discriminant)) / (2 * a);
10950
+ break;
10951
+ }
10952
+ return result;
10953
+ }
10584
10954
  }
10585
10955
 
10586
10956
  /**
@@ -10976,7 +11346,7 @@ class ErrorFunction {
10976
11346
  * calculation for erf(x) in the interval [1e-10, 0.5].
10977
11347
  */
10978
11348
  ErrorFunction.erfImpAn = [
10979
- 0.003379167095512574, -0.0007369565304816795, -0.37473233739291961,
11349
+ 0.003379167095512574, -7369565304816795e-19, -0.3747323373929196,
10980
11350
  0.0817442448733587, -0.04210893199365486, 0.007016570951209576,
10981
11351
  -0.004950912559824351, 0.0008716465990379225,
10982
11352
  ];
@@ -10987,7 +11357,7 @@ ErrorFunction.erfImpAn = [
10987
11357
  ErrorFunction.erfImpAd = [
10988
11358
  1, -0.2180882180879246, 0.4125429727254421, -0.08418911478731068,
10989
11359
  0.0655338856400242, -0.01200196044549418, 0.00408165558926174,
10990
- -0.0006159007215577697,
11360
+ -6159007215577697e-19,
10991
11361
  ];
10992
11362
  /**
10993
11363
  * Polynomial coefficients for a numerator in erfImp
@@ -11038,7 +11408,7 @@ ErrorFunction.erfImpDn = [
11038
11408
  ErrorFunction.erfImpDd = [
11039
11409
  1, 1.7596709814716753, 1.3288357143796112, 0.5525285965087576,
11040
11410
  0.1337930569413329, 0.017950964517628076, 0.001047124400199374,
11041
- -0.10664038182035734e-7,
11411
+ -1.0664038182035734e-8,
11042
11412
  ];
11043
11413
  /**
11044
11414
  * Polynomial coefficients for a numerator in erfImp
@@ -11073,7 +11443,7 @@ ErrorFunction.erfImpFn = [
11073
11443
  ErrorFunction.erfImpFd = [
11074
11444
  1, 1.210196977736308, 0.6209146682211439, 0.1730384306611428,
11075
11445
  0.0276550813773432, 0.002406259744243097, 0.8918118172513366e-4,
11076
- -0.4655288362833827e-11,
11446
+ -4655288362833827e-27,
11077
11447
  ];
11078
11448
  /**
11079
11449
  * Polynomial coefficients for a numerator in erfImp
@@ -11112,7 +11482,7 @@ ErrorFunction.erfImpHd = [
11112
11482
  * calculation for erfc(x) in the interval [11.5, 17].
11113
11483
  */
11114
11484
  ErrorFunction.erfImpIn = [
11115
- -0.0005690799360109496, 0.0001694985403737623, 0.5184723545811009e-4,
11485
+ -5690799360109496e-19, 0.0001694985403737623, 0.5184723545811009e-4,
11116
11486
  0.38281931223192885e-5, 0.8249899312818944e-7,
11117
11487
  ];
11118
11488
  /**
@@ -11121,14 +11491,14 @@ ErrorFunction.erfImpIn = [
11121
11491
  */
11122
11492
  ErrorFunction.erfImpId = [
11123
11493
  1, 0.3396372500511393, 0.04347264787031066, 0.002485493352246371,
11124
- 0.5356333053371529e-4, -0.11749094440545958e-12,
11494
+ 0.5356333053371529e-4, -11749094440545958e-29,
11125
11495
  ];
11126
11496
  /**
11127
11497
  * Polynomial coefficients for a numerator in erfImp
11128
11498
  * calculation for erfc(x) in the interval [17, 24].
11129
11499
  */
11130
11500
  ErrorFunction.erfImpJn = [
11131
- -0.000241313599483991337, 0.5742249752025015e-4, 0.11599896292738377e-4,
11501
+ -24131359948399134e-20, 0.5742249752025015e-4, 0.11599896292738377e-4,
11132
11502
  0.581762134402594e-6, 0.8539715550856736e-8,
11133
11503
  ];
11134
11504
  /**
@@ -11144,7 +11514,7 @@ ErrorFunction.erfImpJd = [
11144
11514
  * calculation for erfc(x) in the interval [24, 38].
11145
11515
  */
11146
11516
  ErrorFunction.erfImpKn = [
11147
- -0.00014667469927776036, 0.1626665521122805e-4, 0.26911624850916523e-5,
11517
+ -14667469927776036e-20, 0.1626665521122805e-4, 0.26911624850916523e-5,
11148
11518
  0.979584479468092e-7, 0.10199464762572346e-8,
11149
11519
  ];
11150
11520
  /**
@@ -11160,7 +11530,7 @@ ErrorFunction.erfImpKd = [
11160
11530
  * calculation for erfc(x) in the interval [38, 60].
11161
11531
  */
11162
11532
  ErrorFunction.erfImpLn = [
11163
- -0.5839057976297718e-4, 0.4125103251054962e-5, 0.43179092242025094e-6,
11533
+ -5839057976297718e-20, 0.4125103251054962e-5, 0.43179092242025094e-6,
11164
11534
  0.9933651555900132e-8, 0.653480510020105e-10,
11165
11535
  ];
11166
11536
  /**
@@ -11176,7 +11546,7 @@ ErrorFunction.erfImpLd = [
11176
11546
  * calculation for erfc(x) in the interval [60, 85].
11177
11547
  */
11178
11548
  ErrorFunction.erfImpMn = [
11179
- -0.196457797609229579e-4, 0.1572438876668007e-5, 0.5439025111927009e-7,
11549
+ -19645779760922958e-21, 0.1572438876668007e-5, 0.5439025111927009e-7,
11180
11550
  0.3174724923691177e-9,
11181
11551
  ];
11182
11552
  /**
@@ -11192,7 +11562,7 @@ ErrorFunction.erfImpMd = [
11192
11562
  * calculation for erfc(x) in the interval [85, 110].
11193
11563
  */
11194
11564
  ErrorFunction.erfImpNn = [
11195
- -0.789224703978723e-5, 0.622088451660987e-6, 0.1457284456768824e-7,
11565
+ -789224703978723e-20, 0.622088451660987e-6, 0.1457284456768824e-7,
11196
11566
  0.603715505542715e-10,
11197
11567
  ];
11198
11568
  /**
@@ -11209,7 +11579,7 @@ ErrorFunction.erfImpNd = [
11209
11579
  * calculation for erf^-1(z) in the interval [0, 0.5].
11210
11580
  */
11211
11581
  ErrorFunction.ervInvImpAn = [
11212
- -0.0005087819496582806, -0.0083687481974173677, 0.033480662540974461,
11582
+ -5087819496582806e-19, -0.008368748197417368, 0.033480662540974461,
11213
11583
  -0.012692614766297402, -0.03656379714117627, 0.02198786811111689,
11214
11584
  0.008226878746769157, -0.005387729650712429,
11215
11585
  ];
@@ -11247,8 +11617,8 @@ ErrorFunction.ervInvImpBd = [
11247
11617
  ErrorFunction.ervInvImpCn = [
11248
11618
  -0.1311027816799519, -0.1637940471933171, 0.11703015634199525,
11249
11619
  0.387079738972604337, 0.3377855389120359, 0.1428695344081572,
11250
- 0.029015791000532906, 0.002145589953888053, -0.6794655751811264e-6,
11251
- 0.2852253317822171e-7, -0.681149956853777e-9,
11620
+ 0.029015791000532906, 0.002145589953888053, -6.794655751811264e-7,
11621
+ 0.2852253317822171e-7, -681149956853777e-24,
11252
11622
  ];
11253
11623
  /**
11254
11624
  * Polynomial coefficients for a denominator of erfInvImp
@@ -11266,7 +11636,7 @@ ErrorFunction.ervInvImpCd = [
11266
11636
  ErrorFunction.ervInvImpDn = [
11267
11637
  -0.0350353787183178, -0.002224265292134479, 0.018557330651423107,
11268
11638
  0.009508047013259196, 0.001871234928195592, 0.00015754461742496055,
11269
- 0.460469890584318e-5, -0.2304047769118826e-9, 0.266339227425782e-11,
11639
+ 0.460469890584318e-5, -2304047769118826e-25, 0.266339227425782e-11,
11270
11640
  ];
11271
11641
  /**
11272
11642
  * Polynomial coefficients for a denominator of erfInvImp
@@ -11283,7 +11653,7 @@ ErrorFunction.ervInvImpDd = [
11283
11653
  ErrorFunction.ervInvImpEn = [
11284
11654
  -0.016743100507663373, -0.001129514387455803, 0.001056288621524929,
11285
11655
  0.0002093863174875881, 0.14962478375834237e-4, 0.4496967899277065e-6,
11286
- 0.4625961635228786e-8, -0.281128735628831791e-13,
11656
+ 0.4625961635228786e-8, -2811287356288318e-29,
11287
11657
  0.9905570997331033e-16,
11288
11658
  ];
11289
11659
  /**
@@ -11299,9 +11669,9 @@ ErrorFunction.ervInvImpEd = [
11299
11669
  * calculation for erf^-1(z) in the interval [0.75, 1] with x between 18 and 44.
11300
11670
  */
11301
11671
  ErrorFunction.ervInvImpFn = [
11302
- -0.0024978212791898131, -0.779190719229054e-5, 0.2547230374130275e-4,
11672
+ -0.002497821279189813, -779190719229054e-20, 0.2547230374130275e-4,
11303
11673
  0.1623977773425109e-5, 0.3963410113048011685e-7, 0.4116328311909442e-9,
11304
- 0.145596286718675e-11, -0.11676501239718427e-17,
11674
+ 0.145596286718675e-11, -11676501239718427e-34,
11305
11675
  ];
11306
11676
  /**
11307
11677
  * Polynomial coefficients for a denominator of erfInvImp
@@ -11316,9 +11686,9 @@ ErrorFunction.ervInvImpFd = [
11316
11686
  * calculation for erf^-1(z) in the interval [0.75, 1] with x greater than 44.
11317
11687
  */
11318
11688
  ErrorFunction.ervInvImpGn = [
11319
- -0.0005390429110190786, -0.2839875900472772e-6, 0.8994651148922914e-6,
11689
+ -5390429110190786e-19, -2.839875900472772e-7, 0.8994651148922914e-6,
11320
11690
  0.2293458592659209e-7, 0.2255614448635001e-9, 0.9478466275030226e-12,
11321
- 0.13588013010892486e-14, -0.3488903933999489e-21,
11691
+ 0.13588013010892486e-14, -3488903933999489e-37,
11322
11692
  ];
11323
11693
  /**
11324
11694
  * Polynomial coefficients for a denominator of erfInvImp
@@ -11329,6 +11699,132 @@ ErrorFunction.ervInvImpGd = [
11329
11699
  0.3999688121938621e-6, 0.1618092908879045e-8, 0.2315586083102596e-11,
11330
11700
  ];
11331
11701
 
11702
+ /**
11703
+ * Continuous Univariate Normal distribution, also known as Gaussian distribution.
11704
+ *
11705
+ * For details about this distribution, see {@link http://en.wikipedia.org/wiki/Normal_distribution Wikipedia - Normal distribution}.
11706
+ *
11707
+ * This class shares the same implementation as {@link https://numerics.mathdotnet.com/ Math.NET Numerics}.
11708
+ */
11709
+ class NormalDistribution {
11710
+ /**
11711
+ * Computes the cumulative distribution function (CDF) of the distribution at x, i.e. P(X ≤ x).
11712
+ *
11713
+ * @param mean The mean (μ) of the normal distribution.
11714
+ * @param stdDev The standard deviation (σ) of the normal distribution. Range: σ ≥ 0.
11715
+ * @param x The location at which to compute the cumulative distribution function.
11716
+ * @returns The cumulative distribution at {@link x}.
11717
+ */
11718
+ static cdf(mean, stdDev, x) {
11719
+ if (stdDev < 0) {
11720
+ throw new RangeError("Invalid parametrization for the distribution.");
11721
+ }
11722
+ if (mean === x && stdDev === 0) {
11723
+ return 0;
11724
+ }
11725
+ return 0.5 * ErrorFunction.erfc((mean - x) / (stdDev * Math.SQRT2));
11726
+ }
11727
+ /**
11728
+ * Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x.
11729
+ *
11730
+ * In MATLAB, this is known as `normpdf`.
11731
+ *
11732
+ * @param mean The mean (μ) of the normal distribution.
11733
+ * @param stdDev The standard deviation (σ) of the normal distribution. Range: σ ≥ 0.
11734
+ * @param x The location at which to compute the density.
11735
+ * @returns The density at {@link x}.
11736
+ */
11737
+ static pdf(mean, stdDev, x) {
11738
+ if (stdDev < 0) {
11739
+ throw new RangeError("Invalid parametrization for the distribution.");
11740
+ }
11741
+ const d = (x - mean) / stdDev;
11742
+ return Math.exp(-0.5 * d * d) / (stdDev * Math.sqrt(2 * Math.PI));
11743
+ }
11744
+ /**
11745
+ * Computes the inverse of the cumulative distribution function (InvCDF) for the distribution
11746
+ * at the given probability. This is also known as the quantile or percent point function.
11747
+ *
11748
+ * @param mean The mean (μ) of the normal distribution.
11749
+ * @param stdDev The standard deviation (σ) of the normal distribution. Range: σ ≥ 0.
11750
+ * @param p The location at which to compute the inverse cumulative density.
11751
+ * @returns The inverse cumulative density at `p`.
11752
+ */
11753
+ static invCDF(mean, stdDev, p) {
11754
+ if (stdDev < 0) {
11755
+ throw new RangeError("Invalid parametrization for the distribution.");
11756
+ }
11757
+ return mean - stdDev * Math.SQRT2 * ErrorFunction.erfcInv(2 * p);
11758
+ }
11759
+ }
11760
+
11761
+ /**
11762
+ * Approximation of the {@link https://en.wikipedia.org/wiki/Poisson_binomial_distribution Poisson binomial distribution}, which can be
11763
+ * updated iteratively.
11764
+ *
11765
+ * For the approximation method, see "Refined Normal Approximation (RNA)" from:
11766
+ *
11767
+ * {@link https://www.researchgate.net/publication/257017356_On_computing_the_distribution_function_for_the_Poisson_binomial_distribution Hong, Y. (2013). On computing the distribution function for the Poisson binomial distribution. Computational Statistics and Data Analysis, Vol. 59, pp. 41-51}.
11768
+ *
11769
+ * This has been verified against a reference implementation provided by the authors in the R package "poibin",
11770
+ * which can be viewed {@link https://rdrr.io/cran/poibin/man/poibin-package.html here}.
11771
+ */
11772
+ class IterativePoissonBinomial {
11773
+ constructor() {
11774
+ this.mu = 0;
11775
+ this.var = 0;
11776
+ this.gamma = 0;
11777
+ }
11778
+ /**
11779
+ * Adds a new trial with the provided probability of success to the distribution.
11780
+ *
11781
+ * @param p The probability of success for the new trial.
11782
+ */
11783
+ addProbability(p) {
11784
+ this.mu += p;
11785
+ this.var += p * (1 - p);
11786
+ this.gamma += p * (1 - p) * (1 - 2 * p);
11787
+ }
11788
+ /**
11789
+ * Adds multiple trials with the same probability of success to the distribution.
11790
+ *
11791
+ * @param p The probability of success for the new trials.
11792
+ * @param count The number of trials to add.
11793
+ */
11794
+ addBinnedProbabilities(p, count) {
11795
+ this.mu += p * count;
11796
+ this.var += p * (1 - p) * count;
11797
+ this.gamma += p * (1 - p) * (1 - 2 * p) * count;
11798
+ }
11799
+ /**
11800
+ * Computes the value of the cumulative distribution function for this distribution.
11801
+ *
11802
+ * @param count The argument of the CDF to sample the distribution for. In the discrete case (when it is a whole number),
11803
+ * this corresponds to the number of successful Bernoulli trials to query the CDF for.
11804
+ * @returns The value of CDF at {@link count}. In the discrete case, this corresponds to the probability that at most
11805
+ * {@link count} Bernoulli trials ended in a success.
11806
+ */
11807
+ cdf(count) {
11808
+ if (this.var === 0) {
11809
+ return this.mu <= count ? 1 : 0;
11810
+ }
11811
+ const sigma = Math.sqrt(this.var);
11812
+ const v = this.gamma / (6 * Math.pow(sigma, 3));
11813
+ const k = (count + 0.5 - this.mu) / sigma;
11814
+ const result = NormalDistribution.cdf(0, 1, k) +
11815
+ v * (1 - k * k) * NormalDistribution.pdf(0, 1, k);
11816
+ return MathUtils.clamp(result, 0, 1);
11817
+ }
11818
+ /**
11819
+ * Resets the distribution to an empty state.
11820
+ */
11821
+ reset() {
11822
+ this.mu = 0;
11823
+ this.var = 0;
11824
+ this.gamma = 0;
11825
+ }
11826
+ }
11827
+
11332
11828
  /**
11333
11829
  * Ranking status of a beatmap.
11334
11830
  */
@@ -11553,13 +12049,13 @@ class MapInfo {
11553
12049
  * The osu! site link to this beatmap.
11554
12050
  */
11555
12051
  get beatmapLink() {
11556
- return `https://osu.ppy.sh/b/${this.beatmapId}`;
12052
+ return `https://osu.ppy.sh/b/${this.beatmapId.toString()}`;
11557
12053
  }
11558
12054
  /**
11559
12055
  * The osu! site link to this beatmapset.
11560
12056
  */
11561
12057
  get beatmapSetLink() {
11562
- return `https://osu.ppy.sh/s/${this.beatmapSetId}`;
12058
+ return `https://osu.ppy.sh/s/${this.beatmapSetId.toString()}`;
11563
12059
  }
11564
12060
  static getInformation(beatmapIdOrHash, downloadBeatmap) {
11565
12061
  return __awaiter(this, void 0, void 0, function* () {
@@ -11570,7 +12066,7 @@ class MapInfo {
11570
12066
  if (result.statusCode !== 200) {
11571
12067
  throw new Error("osu! API error");
11572
12068
  }
11573
- const mapinfo = JSON.parse(result.data.toString("utf-8"))[0];
12069
+ const mapinfo = JSON.parse(result.data.toString("utf-8")).at(0);
11574
12070
  if (!mapinfo) {
11575
12071
  return null;
11576
12072
  }
@@ -11655,7 +12151,7 @@ class MapInfo {
11655
12151
  toAPIResponse() {
11656
12152
  var _a, _b, _c, _d, _e, _f, _g, _h;
11657
12153
  const padDateNumber = (num) => num.toString().padStart(2, "0");
11658
- const convertDate = (date) => `${date.getUTCFullYear()}-${padDateNumber(date.getUTCMonth() + 1)}-${padDateNumber(date.getUTCDate())} ${padDateNumber(date.getUTCHours())}:${padDateNumber(date.getUTCMinutes())}:${padDateNumber(date.getUTCSeconds())}`;
12154
+ const convertDate = (date) => `${date.getUTCFullYear().toString()}-${padDateNumber(date.getUTCMonth() + 1)}-${padDateNumber(date.getUTCDate())} ${padDateNumber(date.getUTCHours())}:${padDateNumber(date.getUTCMinutes())}:${padDateNumber(date.getUTCSeconds())}`;
11659
12155
  return {
11660
12156
  approved: this.approved.toString(),
11661
12157
  submit_date: convertDate(this.submitDate),
@@ -11667,7 +12163,7 @@ class MapInfo {
11667
12163
  beatmap_id: this.beatmapId.toString(),
11668
12164
  beatmapset_id: this.beatmapSetId.toString(),
11669
12165
  bpm: this.bpm.toString(),
11670
- creator: this.creator.toString(),
12166
+ creator: this.creator,
11671
12167
  creator_id: this.creatorId.toString(),
11672
12168
  difficultyrating: (_b = (_a = this.totalDifficulty) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : null,
11673
12169
  diff_aim: (_d = (_c = this.aimDifficulty) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : null,
@@ -11677,13 +12173,13 @@ class MapInfo {
11677
12173
  diff_approach: this.ar.toString(),
11678
12174
  diff_drain: this.hp.toString(),
11679
12175
  hit_length: this.hitLength.toString(),
11680
- source: this.source.toString(),
12176
+ source: this.source,
11681
12177
  genre_id: this.genre.toString(),
11682
12178
  language_id: this.language.toString(),
11683
- title: this.title.toString(),
12179
+ title: this.title,
11684
12180
  total_length: this.totalLength.toString(),
11685
- version: this.version.toString(),
11686
- file_md5: this.hash.toString(),
12181
+ version: this.version,
12182
+ file_md5: this.hash,
11687
12183
  // Guaranteed to be osu!standard for the time being.
11688
12184
  mode: "0",
11689
12185
  tags: this.tags.join(" "),
@@ -11720,7 +12216,7 @@ class MapInfo {
11720
12216
  if (this.hasDownloadedBeatmap() && !force) {
11721
12217
  return;
11722
12218
  }
11723
- const url = `https://osu.ppy.sh/osu/${this.beatmapId}`;
12219
+ const url = `https://osu.ppy.sh/osu/${this.beatmapId.toString()}`;
11724
12220
  return fetch(url)
11725
12221
  .then((res) => __awaiter(this, void 0, void 0, function* () {
11726
12222
  const text = yield res.text();
@@ -11730,7 +12226,7 @@ class MapInfo {
11730
12226
  this.cachedBeatmap = new BeatmapDecoder().decode(text).result;
11731
12227
  }))
11732
12228
  .catch((e) => {
11733
- console.error(`Request to ${url} failed with the following error: ${e.message}; aborting`);
12229
+ console.error(`Request to ${url} failed with an error, aborting`, e);
11734
12230
  });
11735
12231
  });
11736
12232
  }
@@ -11748,7 +12244,8 @@ class MapInfo {
11748
12244
  * Returns a string representative of the class.
11749
12245
  */
11750
12246
  toString() {
11751
- return `${this.fullTitle}\nCS: ${this.cs} - AR: ${this.ar} - OD: ${this.od} - HP: ${this.hp}\nBPM: ${this.bpm} - Length: ${this.hitLength}/${this.totalLength} - Max Combo: ${this.maxCombo}\nLast Update: ${this.lastUpdate}`;
12247
+ var _a, _b;
12248
+ return `${this.fullTitle}\nCS: ${this.cs.toString()} - AR: ${this.ar.toString()} - OD: ${this.od.toString()} - HP: ${this.hp.toString()}\nBPM: ${this.bpm.toString()} - Length: ${this.hitLength.toString()}/${this.totalLength.toString()} - Max Combo: ${(_b = (_a = this.maxCombo) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "N/A"}\nLast Update: ${this.lastUpdate.toUTCString()}`;
11752
12249
  }
11753
12250
  }
11754
12251
 
@@ -11787,27 +12284,62 @@ class ModOldNightCore extends ModNightCore {
11787
12284
  }
11788
12285
 
11789
12286
  /**
11790
- * Continuous Univariate Normal distribution, also known as Gaussian distribution.
12287
+ * Approximation of the {@link https://en.wikipedia.org/wiki/Poisson_binomial_distribution Poisson binomial distribution}.
11791
12288
  *
11792
- * For details about this distribution, see {@link http://en.wikipedia.org/wiki/Normal_distribution Wikipedia - Normal distribution}.
12289
+ * For the approximation method, see "Refined Normal Approximation (RNA)" from:
11793
12290
  *
11794
- * This class shares the same implementation as {@link https://numerics.mathdotnet.com/ Math.NET Numerics}.
12291
+ * {@link https://www.researchgate.net/publication/257017356_On_computing_the_distribution_function_for_the_Poisson_binomial_distribution Hong, Y. (2013). On computing the distribution function for the Poisson binomial distribution. Computational Statistics and Data Analysis, Vol. 59, pp. 41-51}.
12292
+ *
12293
+ * This has been verified against a reference implementation provided by the authors in the R package "poibin",
12294
+ * which can be viewed {@link https://rdrr.io/cran/poibin/man/poibin-package.html here}.
11795
12295
  */
11796
- class NormalDistribution {
12296
+ class PoissonBinomial {
11797
12297
  /**
11798
- * Computes the inverse of the cumulative distribution function (InvCDF) for the distribution
11799
- * at the given probability. This is also known as the quantile or percent point function.
12298
+ * Creates a new Poisson binomial distribution based on N trials with the provided list or bins of
12299
+ * difficulties, skill, and method for getting the miss probabilities.
11800
12300
  *
11801
- * @param mean The mean (μ) of the normal distribution.
11802
- * @param stdDev The standard deviation (σ) of the normal distribution. Range: σ ≥ 0.
11803
- * @param p The location at which to compute the inverse cumulative density.
11804
- * @returns The inverse cumulative density at `p`.
12301
+ * @param difficulties The list or bins of difficulties.
12302
+ * @param skill The skill level to get the miss probabilities with.
12303
+ * @param hitProbability The method for converting difficulties and skill into miss probabilities.
11805
12304
  */
11806
- static invCDF(mean, stdDev, p) {
11807
- if (stdDev < 0) {
11808
- throw new RangeError("Invalid parametrization for the distribution.");
12305
+ constructor(difficulties, skill, hitProbability) {
12306
+ this.mu = 0;
12307
+ let variance = 0;
12308
+ let gamma = 0;
12309
+ for (const d of difficulties) {
12310
+ if (d instanceof Bin) {
12311
+ const p = 1 - hitProbability(skill, d.difficulty);
12312
+ this.mu += p * d.noteCount;
12313
+ variance += p * (1 - p) * d.noteCount;
12314
+ gamma += p * (1 - p) * (1 - 2 * p) * d.noteCount;
12315
+ }
12316
+ else {
12317
+ const p = 1 - hitProbability(skill, d);
12318
+ this.mu += p;
12319
+ variance += p * (1 - p);
12320
+ gamma += p * (1 - p) * (1 - 2 * p);
12321
+ }
11809
12322
  }
11810
- return mean - stdDev * Math.SQRT2 * ErrorFunction.erfcInv(2 * p);
12323
+ this.sigma = Math.sqrt(variance);
12324
+ this.v = gamma / (6 * Math.pow(this.sigma, 3));
12325
+ }
12326
+ /**
12327
+ * Computes the value of the cumulative distribution function for this distribution.
12328
+ *
12329
+ * @param count The argument of the CDF to sample the distribution for. In the discrete case (when it is a whole number),
12330
+ * this corresponds to the number of successful Bernoulli trials to query the CDF for.
12331
+ * @returns The value of CDF at {@link count}. In the discrete case, this corresponds to the probability that at most
12332
+ * {@link count} Bernoulli trials ended in a success.
12333
+ */
12334
+ cdf(count) {
12335
+ if (this.sigma === 0) {
12336
+ return 1;
12337
+ }
12338
+ const k = (count + 0.5 - this.mu) / this.sigma;
12339
+ // See equation (14) of the cited paper.
12340
+ const result = NormalDistribution.cdf(0, 1, k) +
12341
+ this.v * (1 - k * k) * NormalDistribution.pdf(0, 1, k);
12342
+ return MathUtils.clamp(result, 0, 1);
11811
12343
  }
11812
12344
  }
11813
12345
 
@@ -11828,10 +12360,12 @@ exports.BeatmapHitObjects = BeatmapHitObjects;
11828
12360
  exports.BeatmapMetadata = BeatmapMetadata;
11829
12361
  exports.BeatmapProcessor = BeatmapProcessor;
11830
12362
  exports.BeatmapVideo = BeatmapVideo;
12363
+ exports.Bin = Bin;
11831
12364
  exports.BlendingParameters = BlendingParameters;
11832
12365
  exports.BooleanModSetting = BooleanModSetting;
11833
12366
  exports.BreakPoint = BreakPoint;
11834
12367
  exports.Brent = Brent;
12368
+ exports.Chandrupatla = Chandrupatla;
11835
12369
  exports.Circle = Circle;
11836
12370
  exports.CircleSizeCalculator = CircleSizeCalculator;
11837
12371
  exports.Command = Command;
@@ -11858,6 +12392,7 @@ exports.HitSampleInfo = HitSampleInfo;
11858
12392
  exports.HitWindow = HitWindow;
11859
12393
  exports.IntegerModSetting = IntegerModSetting;
11860
12394
  exports.Interpolation = Interpolation;
12395
+ exports.IterativePoissonBinomial = IterativePoissonBinomial;
11861
12396
  exports.MapInfo = MapInfo;
11862
12397
  exports.MathUtils = MathUtils;
11863
12398
  exports.Mod = Mod;
@@ -11913,6 +12448,7 @@ exports.OsuPlayableBeatmap = OsuPlayableBeatmap;
11913
12448
  exports.PathApproximator = PathApproximator;
11914
12449
  exports.PlayableBeatmap = PlayableBeatmap;
11915
12450
  exports.Playfield = Playfield;
12451
+ exports.PoissonBinomial = PoissonBinomial;
11916
12452
  exports.Polynomial = Polynomial;
11917
12453
  exports.PreciseDroidHitWindow = PreciseDroidHitWindow;
11918
12454
  exports.Precision = Precision;