@rian8337/osu-base 2.2.0 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -311,7 +311,9 @@ class APIRequestBuilder {
311
311
  })
312
312
  .on("complete", async (response) => {
313
313
  ++this.fetchAttempts;
314
- if (response.statusCode !== 200 && this.fetchAttempts < 5) {
314
+ const { statusCode } = response;
315
+ if ((statusCode === 500 || statusCode === 503) &&
316
+ this.fetchAttempts < 5) {
315
317
  console.error(`Request to ${url} failed; ${this.fetchAttempts} attempts so far; retrying`);
316
318
  await Utils.sleep(0.2);
317
319
  return resolve(this.sendRequest());
@@ -342,9 +344,7 @@ class APIRequestBuilder {
342
344
  * @param param The parameter to remove.
343
345
  */
344
346
  removeParameter(param) {
345
- if (this.params.get(param)) {
346
- this.params.delete(param);
347
- }
347
+ this.params.delete(param);
348
348
  return this;
349
349
  }
350
350
  }
@@ -355,18 +355,6 @@ class DroidAPIRequestBuilder extends APIRequestBuilder {
355
355
  host = "https://osudroid.moe/api/";
356
356
  APIkey = process.env.DROID_API_KEY;
357
357
  APIkeyParam = `apiKey=${this.APIkey}&`;
358
- setEndpoint(endpoint) {
359
- // Compatibility with old API. Can be removed in v3.0.
360
- switch (endpoint) {
361
- case "banscore.php":
362
- endpoint = "single_score_wipe.php";
363
- break;
364
- case "rename.php":
365
- endpoint = "user_rename.php";
366
- break;
367
- }
368
- return super.setEndpoint(endpoint);
369
- }
370
358
  }
371
359
  /**
372
360
  * API request builder for osu!standard.
@@ -377,6 +365,15 @@ class OsuAPIRequestBuilder extends APIRequestBuilder {
377
365
  APIkeyParam = `k=${this.APIkey}&`;
378
366
  }
379
367
 
368
+ /**
369
+ * Mode enum to switch things between osu!droid and osu!standard.
370
+ */
371
+ exports.modes = void 0;
372
+ (function (modes) {
373
+ modes["droid"] = "droid";
374
+ modes["osu"] = "osu";
375
+ })(exports.modes || (exports.modes = {}));
376
+
380
377
  /**
381
378
  * Bitmask constant of object types. This is needed as osu! uses bits to determine object types.
382
379
  */
@@ -390,15 +387,15 @@ exports.objectTypes = void 0;
390
387
  })(exports.objectTypes || (exports.objectTypes = {}));
391
388
 
392
389
  /**
393
- * Based on `Vector2` class in C#.
390
+ * Represents a two-dimensional vector.
394
391
  */
395
392
  class Vector2 {
396
393
  /**
397
- * The x position of the vector.
394
+ * The x position of this vector.
398
395
  */
399
396
  x;
400
397
  /**
401
- * The y position of the vector.
398
+ * The y position of this vector.
402
399
  */
403
400
  y;
404
401
  constructor(x, y) {
@@ -406,11 +403,22 @@ class Vector2 {
406
403
  this.y = y;
407
404
  }
408
405
  /**
409
- * Multiplies the vector with another vector.
406
+ * Multiplies this vector with another vector.
407
+ *
408
+ * @param vec The other vector.
409
+ * @returns The multiplied vector.
410
410
  */
411
411
  multiply(vec) {
412
412
  return new Vector2(this.x * vec.x, this.y * vec.y);
413
413
  }
414
+ /**
415
+ * Divides this vector with a scalar.
416
+ *
417
+ * Attempting to divide by 0 will throw an error.
418
+ *
419
+ * @param divideFactor The factor to divide the vector by.
420
+ * @returns The divided vector.
421
+ */
414
422
  divide(divideFactor) {
415
423
  if (divideFactor === 0) {
416
424
  throw new Error("Division by 0");
@@ -418,37 +426,52 @@ class Vector2 {
418
426
  return new Vector2(this.x / divideFactor, this.y / divideFactor);
419
427
  }
420
428
  /**
421
- * Adds the vector with another vector.
429
+ * Adds this vector with another vector.
430
+ *
431
+ * @param vec The other vector.
432
+ * @returns The added vector.
422
433
  */
423
434
  add(vec) {
424
435
  return new Vector2(this.x + vec.x, this.y + vec.y);
425
436
  }
426
437
  /**
427
- * Subtracts the vector with another vector.
438
+ * Subtracts this vector with another vector.
439
+ *
440
+ * @param vec The other vector.
441
+ * @returns The subtracted vector.
428
442
  */
429
443
  subtract(vec) {
430
444
  return new Vector2(this.x - vec.x, this.y - vec.y);
431
445
  }
432
446
  /**
433
- * The length of the vector.
447
+ * The length of this vector.
434
448
  */
435
449
  get length() {
436
450
  return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
437
451
  }
438
452
  /**
439
453
  * Performs a dot multiplication with another vector.
454
+ *
455
+ * @param vec The other vector.
456
+ * @returns The dot product of both vectors.
440
457
  */
441
458
  dot(vec) {
442
459
  return this.x * vec.x + this.y * vec.y;
443
460
  }
444
461
  /**
445
- * Scales the vector.
462
+ * Scales this vector.
463
+ *
464
+ * @param scaleFactor The factor to scale the vector by.
465
+ * @returns The scaled vector.
446
466
  */
447
467
  scale(scaleFactor) {
448
468
  return new Vector2(this.x * scaleFactor, this.y * scaleFactor);
449
469
  }
450
470
  /**
451
471
  * Gets the distance between this vector and another vector.
472
+ *
473
+ * @param vec The other vector.
474
+ * @returns The distance between this vector and the other vector.
452
475
  */
453
476
  getDistance(vec) {
454
477
  const x = this.x - vec.x;
@@ -467,6 +490,7 @@ class Vector2 {
467
490
  * Checks whether this vector is equal to another vector.
468
491
  *
469
492
  * @param other The other vector.
493
+ * @returns Whether this vector is equal to the other vector.
470
494
  */
471
495
  equals(other) {
472
496
  return this.x === other.x && this.y === other.y;
@@ -509,31 +533,6 @@ class HitObject {
509
533
  get duration() {
510
534
  return this.endTime - this.startTime;
511
535
  }
512
- /**
513
- * The stacked position of the hitobject.
514
- */
515
- get stackedPosition() {
516
- if (this.type & exports.objectTypes.spinner) {
517
- return this.position;
518
- }
519
- return this.position.add(this.stackOffset);
520
- }
521
- /**
522
- * The stacked end position of the hitobject.
523
- */
524
- get stackedEndPosition() {
525
- if (this.type & exports.objectTypes.spinner) {
526
- return this.position;
527
- }
528
- return this.endPosition.add(this.stackOffset);
529
- }
530
- /**
531
- * The stack vector to calculate offset for stacked positions.
532
- */
533
- get stackOffset() {
534
- const coordinate = this.stackHeight * this.scale * -6.4;
535
- return new Vector2(coordinate, coordinate);
536
- }
537
536
  /**
538
537
  * Whether this hit object represents a new combo.
539
538
  */
@@ -554,15 +553,13 @@ class HitObject {
554
553
  */
555
554
  stackHeight = 0;
556
555
  /**
557
- * The scale used to calculate stacked position and radius.
556
+ * The osu!droid scale used to calculate stacked position and radius.
558
557
  */
559
- scale = 1;
558
+ droidScale = 1;
560
559
  /**
561
- * The radius of the hitobject.
560
+ * The osu!standard scale used to calculate stacked position and radius.
562
561
  */
563
- get radius() {
564
- return 64 * this.scale;
565
- }
562
+ osuScale = 1;
566
563
  constructor(values) {
567
564
  this.startTime = values.startTime;
568
565
  this.endTime = values.endTime ?? values.startTime;
@@ -572,6 +569,62 @@ class HitObject {
572
569
  this.isNewCombo = values.newCombo ?? false;
573
570
  this.comboOffset = values.comboOffset ?? 0;
574
571
  }
572
+ /**
573
+ * Evaluates the radius of the hitobject.
574
+ *
575
+ * @param mode The gamemode to evaluate for.
576
+ * @returns The radius of the hitobject with respect to the gamemode.
577
+ */
578
+ getRadius(mode) {
579
+ let radius = 64;
580
+ switch (mode) {
581
+ case exports.modes.droid:
582
+ radius *= this.droidScale;
583
+ break;
584
+ case exports.modes.osu:
585
+ radius *= this.osuScale;
586
+ break;
587
+ }
588
+ return radius;
589
+ }
590
+ /**
591
+ * Evaluates the stack offset vector of the hitobject.
592
+ *
593
+ * This is used to calculate offset for stacked positions.
594
+ *
595
+ * @param mode The gamemode to evaluate for.
596
+ * @returns The stack offset with respect to the gamemode.
597
+ */
598
+ getStackOffset(mode) {
599
+ let coordinate = this.stackHeight * -6.4;
600
+ switch (mode) {
601
+ case exports.modes.droid:
602
+ coordinate *= this.droidScale;
603
+ break;
604
+ case exports.modes.osu:
605
+ coordinate *= this.osuScale;
606
+ break;
607
+ }
608
+ return new Vector2(coordinate, coordinate);
609
+ }
610
+ /**
611
+ * Evaluates the stacked position of the hitobject.
612
+ *
613
+ * @param mode The gamemode to evaluate for.
614
+ * @returns The stacked position with respect to the gamemode.
615
+ */
616
+ getStackedPosition(mode) {
617
+ return this.evaluateStackedPosition(this.position, mode);
618
+ }
619
+ /**
620
+ * Evaluates the stacked end position of the hitobject.
621
+ *
622
+ * @param mode The gamemode to evaluate for.
623
+ * @returns The stacked end position with respect to the gamemode.
624
+ */
625
+ getStackedEndPosition(mode) {
626
+ return this.evaluateStackedPosition(this.endPosition, mode);
627
+ }
575
628
  /**
576
629
  * Returns the hitobject type.
577
630
  */
@@ -588,6 +641,19 @@ class HitObject {
588
641
  }
589
642
  return res.substring(0, Math.max(0, res.length - 3));
590
643
  }
644
+ /**
645
+ * Evaluates the stacked position of the specified position.
646
+ *
647
+ * @param position The position to evaluate.
648
+ * @param mode The gamemode to evaluate for.
649
+ * @returns The stacked position.
650
+ */
651
+ evaluateStackedPosition(position, mode) {
652
+ if (this.type & exports.objectTypes.spinner) {
653
+ return position;
654
+ }
655
+ return position.add(this.getStackOffset(mode));
656
+ }
591
657
  }
592
658
 
593
659
  /**
@@ -1305,11 +1371,10 @@ class DifficultyControlPoint extends ControlPoint {
1305
1371
  * This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
1306
1372
  */
1307
1373
  generateTicks;
1308
- // Generate ticks can be made required in 3.0.
1309
1374
  constructor(values) {
1310
1375
  super(values);
1311
1376
  this.speedMultiplier = values.speedMultiplier;
1312
- this.generateTicks = values.generateTicks ?? true;
1377
+ this.generateTicks = values.generateTicks;
1313
1378
  }
1314
1379
  isRedundant(existing) {
1315
1380
  return (this.speedMultiplier === existing.speedMultiplier &&
@@ -1332,6 +1397,7 @@ class DifficultyControlPointManager extends ControlPointManager {
1332
1397
  defaultControlPoint = new DifficultyControlPoint({
1333
1398
  time: 0,
1334
1399
  speedMultiplier: 1,
1400
+ generateTicks: true,
1335
1401
  });
1336
1402
  controlPointAt(time) {
1337
1403
  return this.binarySearchWithFallback(time);
@@ -1725,7 +1791,12 @@ class Beatmap {
1725
1791
  * @param stats The statistics used for calculation.
1726
1792
  */
1727
1793
  maxDroidScore(stats) {
1728
- let scoreMultiplier = stats.mods.reduce((a, v) => a * v.droidScoreMultiplier, 1);
1794
+ let scoreMultiplier = 1;
1795
+ for (const mod of stats.mods) {
1796
+ if (mod.isApplicableToDroid()) {
1797
+ scoreMultiplier *= mod.droidScoreMultiplier;
1798
+ }
1799
+ }
1729
1800
  const { speedMultiplier } = stats;
1730
1801
  if (speedMultiplier >= 1) {
1731
1802
  scoreMultiplier *= 1 + (speedMultiplier - 1) * 0.24;
@@ -1770,7 +1841,12 @@ class Beatmap {
1770
1841
  maxOsuScore(mods = []) {
1771
1842
  const accumulatedDiffPoints = this.difficulty.cs + this.difficulty.hp + this.difficulty.od;
1772
1843
  let difficultyMultiplier = 2;
1773
- const scoreMultiplier = mods.reduce((a, v) => a * v.pcScoreMultiplier, 1);
1844
+ let scoreMultiplier = 1;
1845
+ for (const mod of mods) {
1846
+ if (mod.isApplicableToOsu()) {
1847
+ scoreMultiplier *= mod.pcScoreMultiplier;
1848
+ }
1849
+ }
1774
1850
  switch (true) {
1775
1851
  case accumulatedDiffPoints <= 5:
1776
1852
  difficultyMultiplier = 2;
@@ -1881,15 +1957,6 @@ class BeatmapBackground {
1881
1957
  }
1882
1958
  }
1883
1959
 
1884
- /**
1885
- * Mode enum to switch things between osu!droid and osu!standard.
1886
- */
1887
- exports.modes = void 0;
1888
- (function (modes) {
1889
- modes["droid"] = "droid";
1890
- modes["osu"] = "osu";
1891
- })(exports.modes || (exports.modes = {}));
1892
-
1893
1960
  class HitWindow {
1894
1961
  /**
1895
1962
  * The overall difficulty of this hit window.
@@ -2018,18 +2085,28 @@ class OsuHitWindow extends HitWindow {
2018
2085
  }
2019
2086
  }
2020
2087
 
2021
- // TODO: separate droid/PC mod implementations
2022
2088
  /**
2023
2089
  * Represents a mod.
2024
2090
  */
2025
2091
  class Mod {
2092
+ /**
2093
+ * Whether this mod can be applied to osu!droid.
2094
+ */
2095
+ isApplicableToDroid() {
2096
+ return "droidRanked" in this;
2097
+ }
2098
+ /**
2099
+ * Whether this mod can be applied to osu!standard.
2100
+ */
2101
+ isApplicableToOsu() {
2102
+ return "pcRanked" in this;
2103
+ }
2026
2104
  }
2027
2105
 
2028
2106
  /**
2029
2107
  * Represents the DoubleTime mod.
2030
2108
  */
2031
2109
  class ModDoubleTime extends Mod {
2032
- scoreMultiplier = 1.12;
2033
2110
  acronym = "DT";
2034
2111
  name = "DoubleTime";
2035
2112
  droidRanked = true;
@@ -2038,14 +2115,12 @@ class ModDoubleTime extends Mod {
2038
2115
  pcScoreMultiplier = 1.12;
2039
2116
  bitwise = 1 << 6;
2040
2117
  droidString = "d";
2041
- droidOnly = false;
2042
2118
  }
2043
2119
 
2044
2120
  /**
2045
2121
  * Represents the HalfTime mod.
2046
2122
  */
2047
2123
  class ModHalfTime extends Mod {
2048
- scoreMultiplier = 0.3;
2049
2124
  acronym = "HT";
2050
2125
  name = "HalfTime";
2051
2126
  droidRanked = true;
@@ -2054,14 +2129,12 @@ class ModHalfTime extends Mod {
2054
2129
  pcScoreMultiplier = 0.3;
2055
2130
  bitwise = 1 << 8;
2056
2131
  droidString = "t";
2057
- droidOnly = false;
2058
2132
  }
2059
2133
 
2060
2134
  /**
2061
2135
  * Represents the NightCore mod.
2062
2136
  */
2063
2137
  class ModNightCore extends Mod {
2064
- scoreMultiplier = 1.12;
2065
2138
  acronym = "NC";
2066
2139
  name = "NightCore";
2067
2140
  droidRanked = true;
@@ -2070,14 +2143,12 @@ class ModNightCore extends Mod {
2070
2143
  pcScoreMultiplier = 1.12;
2071
2144
  bitwise = 1 << 9;
2072
2145
  droidString = "c";
2073
- droidOnly = false;
2074
2146
  }
2075
2147
 
2076
2148
  /**
2077
2149
  * Represents the HardRock mod.
2078
2150
  */
2079
2151
  class ModHardRock extends Mod {
2080
- scoreMultiplier = 1.06;
2081
2152
  acronym = "HR";
2082
2153
  name = "HardRock";
2083
2154
  bitwise = 1 << 4;
@@ -2086,14 +2157,12 @@ class ModHardRock extends Mod {
2086
2157
  droidScoreMultiplier = 1.06;
2087
2158
  pcScoreMultiplier = 1.06;
2088
2159
  droidString = "r";
2089
- droidOnly = false;
2090
2160
  }
2091
2161
 
2092
2162
  /**
2093
2163
  * Represents the Easy mod.
2094
2164
  */
2095
2165
  class ModEasy extends Mod {
2096
- scoreMultiplier = 0.5;
2097
2166
  acronym = "EZ";
2098
2167
  name = "Easy";
2099
2168
  droidRanked = true;
@@ -2102,62 +2171,45 @@ class ModEasy extends Mod {
2102
2171
  pcScoreMultiplier = 0.5;
2103
2172
  bitwise = 1 << 1;
2104
2173
  droidString = "e";
2105
- droidOnly = false;
2106
2174
  }
2107
2175
 
2108
2176
  /**
2109
2177
  * Represents the Precise mod.
2110
2178
  */
2111
2179
  class ModPrecise extends Mod {
2112
- scoreMultiplier = 1.06;
2113
2180
  acronym = "PR";
2114
2181
  name = "Precise";
2115
2182
  droidRanked = true;
2116
- pcRanked = false;
2117
2183
  droidScoreMultiplier = 1.06;
2118
- pcScoreMultiplier = 1.06;
2119
- bitwise = Number.NaN;
2120
2184
  droidString = "s";
2121
- droidOnly = true;
2122
2185
  }
2123
2186
 
2124
2187
  /**
2125
2188
  * Represents the SmallCircle mod.
2126
2189
  */
2127
2190
  class ModSmallCircle extends Mod {
2128
- scoreMultiplier = 1.06;
2129
2191
  acronym = "SC";
2130
2192
  name = "SmallCircle";
2131
2193
  droidRanked = false;
2132
- pcRanked = false;
2133
2194
  droidScoreMultiplier = 1.06;
2134
- pcScoreMultiplier = 1;
2135
- bitwise = Number.NaN;
2136
2195
  droidString = "m";
2137
- droidOnly = true;
2138
2196
  }
2139
2197
 
2140
2198
  /**
2141
2199
  * Represents the ReallyEasy mod.
2142
2200
  */
2143
2201
  class ModReallyEasy extends Mod {
2144
- scoreMultiplier = 0.4;
2145
2202
  acronym = "RE";
2146
2203
  name = "ReallyEasy";
2147
2204
  droidRanked = false;
2148
- pcRanked = false;
2149
2205
  droidScoreMultiplier = 0.4;
2150
- pcScoreMultiplier = 0.4;
2151
- bitwise = Number.NaN;
2152
2206
  droidString = "l";
2153
- droidOnly = true;
2154
2207
  }
2155
2208
 
2156
2209
  /**
2157
2210
  * Represents the Auto mod.
2158
2211
  */
2159
2212
  class ModAuto extends Mod {
2160
- scoreMultiplier = 0;
2161
2213
  acronym = "AT";
2162
2214
  name = "Autoplay";
2163
2215
  droidRanked = false;
@@ -2166,14 +2218,12 @@ class ModAuto extends Mod {
2166
2218
  pcScoreMultiplier = 1;
2167
2219
  bitwise = 1 << 11;
2168
2220
  droidString = "a";
2169
- droidOnly = false;
2170
2221
  }
2171
2222
 
2172
2223
  /**
2173
2224
  * Represents the Autopilot mod.
2174
2225
  */
2175
2226
  class ModAutopilot extends Mod {
2176
- scoreMultiplier = 0;
2177
2227
  acronym = "AP";
2178
2228
  name = "Autopilot";
2179
2229
  droidRanked = false;
@@ -2182,14 +2232,12 @@ class ModAutopilot extends Mod {
2182
2232
  pcScoreMultiplier = 0;
2183
2233
  bitwise = 1 << 13;
2184
2234
  droidString = "p";
2185
- droidOnly = false;
2186
2235
  }
2187
2236
 
2188
2237
  /**
2189
2238
  * Represents the Flashlight mod.
2190
2239
  */
2191
2240
  class ModFlashlight extends Mod {
2192
- scoreMultiplier = 1.12;
2193
2241
  acronym = "FL";
2194
2242
  name = "Flashlight";
2195
2243
  droidRanked = false;
@@ -2198,7 +2246,6 @@ class ModFlashlight extends Mod {
2198
2246
  pcScoreMultiplier = 1.12;
2199
2247
  bitwise = 1 << 10;
2200
2248
  droidString = "i";
2201
- droidOnly = false;
2202
2249
  }
2203
2250
 
2204
2251
  /**
@@ -2207,7 +2254,6 @@ class ModFlashlight extends Mod {
2207
2254
  class ModHidden extends Mod {
2208
2255
  static fadeInDurationMultiplier = 0.4;
2209
2256
  static fadeOutDurationMultiplier = 0.3;
2210
- scoreMultiplier = 1.06;
2211
2257
  acronym = "HD";
2212
2258
  name = "Hidden";
2213
2259
  bitwise = 1 << 3;
@@ -2216,14 +2262,12 @@ class ModHidden extends Mod {
2216
2262
  droidScoreMultiplier = 1.06;
2217
2263
  pcScoreMultiplier = 1.06;
2218
2264
  droidString = "h";
2219
- droidOnly = false;
2220
2265
  }
2221
2266
 
2222
2267
  /**
2223
2268
  * Represents the NoFail mod.
2224
2269
  */
2225
2270
  class ModNoFail extends Mod {
2226
- scoreMultiplier = 0.5;
2227
2271
  acronym = "NF";
2228
2272
  name = "NoFail";
2229
2273
  droidRanked = true;
@@ -2232,14 +2276,12 @@ class ModNoFail extends Mod {
2232
2276
  pcScoreMultiplier = 0.5;
2233
2277
  bitwise = 1 << 0;
2234
2278
  droidString = "n";
2235
- droidOnly = false;
2236
2279
  }
2237
2280
 
2238
2281
  /**
2239
2282
  * Represents the Perfect mod.
2240
2283
  */
2241
2284
  class ModPerfect extends Mod {
2242
- scoreMultiplier = 1;
2243
2285
  acronym = "PF";
2244
2286
  name = "Perfect";
2245
2287
  droidRanked = false;
@@ -2248,14 +2290,12 @@ class ModPerfect extends Mod {
2248
2290
  pcScoreMultiplier = 1;
2249
2291
  bitwise = 1 << 14;
2250
2292
  droidString = "f";
2251
- droidOnly = false;
2252
2293
  }
2253
2294
 
2254
2295
  /**
2255
2296
  * Represents the Relax mod.
2256
2297
  */
2257
2298
  class ModRelax extends Mod {
2258
- scoreMultiplier = 0;
2259
2299
  acronym = "RX";
2260
2300
  name = "Relax";
2261
2301
  droidRanked = false;
@@ -2264,14 +2304,12 @@ class ModRelax extends Mod {
2264
2304
  pcScoreMultiplier = 0;
2265
2305
  bitwise = 1 << 7;
2266
2306
  droidString = "x";
2267
- droidOnly = false;
2268
2307
  }
2269
2308
 
2270
2309
  /**
2271
2310
  * Represents the ScoreV2 mod.
2272
2311
  */
2273
2312
  class ModScoreV2 extends Mod {
2274
- scoreMultiplier = 1;
2275
2313
  acronym = "V2";
2276
2314
  name = "ScoreV2";
2277
2315
  droidRanked = false;
@@ -2280,30 +2318,23 @@ class ModScoreV2 extends Mod {
2280
2318
  pcScoreMultiplier = 1;
2281
2319
  bitwise = 1 << 29;
2282
2320
  droidString = "v";
2283
- droidOnly = false;
2284
2321
  }
2285
2322
 
2286
2323
  /**
2287
2324
  * Represents the SpunOut mod.
2288
2325
  */
2289
2326
  class ModSpunOut extends Mod {
2290
- scoreMultiplier = 0.9;
2291
2327
  acronym = "SO";
2292
2328
  name = "SpunOut";
2293
- droidRanked = false;
2294
2329
  pcRanked = true;
2295
- droidScoreMultiplier = 0.9;
2296
2330
  pcScoreMultiplier = 0.9;
2297
2331
  bitwise = 1 << 12;
2298
- droidString = "";
2299
- droidOnly = false;
2300
2332
  }
2301
2333
 
2302
2334
  /**
2303
2335
  * Represents the SuddenDeath mod.
2304
2336
  */
2305
2337
  class ModSuddenDeath extends Mod {
2306
- scoreMultiplier = 1;
2307
2338
  acronym = "SD";
2308
2339
  name = "Sudden Death";
2309
2340
  droidRanked = false;
@@ -2312,23 +2343,17 @@ class ModSuddenDeath extends Mod {
2312
2343
  pcScoreMultiplier = 1;
2313
2344
  bitwise = 1 << 5;
2314
2345
  droidString = "u";
2315
- droidOnly = false;
2316
2346
  }
2317
2347
 
2318
2348
  /**
2319
2349
  * Represents the TouchDevice mod.
2320
2350
  */
2321
2351
  class ModTouchDevice extends Mod {
2322
- scoreMultiplier = 1;
2323
2352
  acronym = "TD";
2324
2353
  name = "TouchDevice";
2325
- droidRanked = true;
2326
2354
  pcRanked = true;
2327
- droidScoreMultiplier = 1;
2328
2355
  pcScoreMultiplier = 1;
2329
2356
  bitwise = 1 << 2;
2330
- droidString = "";
2331
- droidOnly = false;
2332
2357
  }
2333
2358
 
2334
2359
  /**
@@ -2393,7 +2418,8 @@ class ModUtil {
2393
2418
  * @param options Options for parsing behavior.
2394
2419
  */
2395
2420
  static droidStringToMods(str, options) {
2396
- return this.processParsingOptions(this.allMods.filter((m) => m.droidString && str.toLowerCase().includes(m.droidString)), options);
2421
+ return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToDroid() &&
2422
+ str.toLowerCase().includes(m.droidString)), options);
2397
2423
  }
2398
2424
  /**
2399
2425
  * Gets a list of mods from a PC modbits.
@@ -2402,7 +2428,7 @@ class ModUtil {
2402
2428
  * @param options Options for parsing behavior.
2403
2429
  */
2404
2430
  static pcModbitsToMods(modbits, options) {
2405
- return this.processParsingOptions(this.allMods.filter((m) => m.bitwise & modbits), options);
2431
+ return this.processParsingOptions(this.allMods.filter((m) => m.isApplicableToOsu() && m.bitwise & modbits), options);
2406
2432
  }
2407
2433
  /**
2408
2434
  * Gets a list of mods from a PC mod string, such as "HDHR".
@@ -2443,11 +2469,9 @@ class ModUtil {
2443
2469
  */
2444
2470
  static checkIncompatibleMods(mods) {
2445
2471
  for (const incompatibleMod of this.incompatibleMods) {
2446
- const fulfilledMods = mods.filter((m) => incompatibleMod.map((v) => v.acronym).includes(m.acronym));
2472
+ const fulfilledMods = mods.filter((m) => incompatibleMod.some((v) => m.acronym === v.acronym));
2447
2473
  if (fulfilledMods.length > 1) {
2448
- mods = mods.filter((m) => !incompatibleMod
2449
- .map((v) => v.acronym)
2450
- .includes(m.acronym));
2474
+ mods = mods.filter((m) => incompatibleMod.every((v) => m.acronym !== v.acronym));
2451
2475
  // Keep the first selected mod
2452
2476
  mods.push(fulfilledMods[0]);
2453
2477
  }
@@ -4332,7 +4356,12 @@ class RGBColor {
4332
4356
  * Returns a string representation of the color.
4333
4357
  */
4334
4358
  toString() {
4335
- return `${this.r},${this.g},${this.b}`;
4359
+ if (this.a === 1) {
4360
+ return `${this.r},${this.g},${this.b}`;
4361
+ }
4362
+ else {
4363
+ return `${this.r},${this.g},${this.b},${this.a}`;
4364
+ }
4336
4365
  }
4337
4366
  /**
4338
4367
  * Checks whether this color is equal to another color.
@@ -4340,7 +4369,10 @@ class RGBColor {
4340
4369
  * @param other The other color.
4341
4370
  */
4342
4371
  equals(other) {
4343
- return this.r === other.r && this.g === other.g && this.b === other.b;
4372
+ return (this.r === other.r &&
4373
+ this.g === other.g &&
4374
+ this.b === other.b &&
4375
+ this.a === other.a);
4344
4376
  }
4345
4377
  }
4346
4378
 
@@ -4694,21 +4726,11 @@ class CommandTimelineGroup {
4694
4726
  this.flipHorizontal,
4695
4727
  this.flipVertical,
4696
4728
  ];
4697
- /**
4698
- * The earliest visible time. Will be `null` unless this group's first alpha command has a start value of zero.
4699
- */
4700
- get earliestDisplayedTime() {
4701
- const first = this.alpha.commands.at(0);
4702
- return first?.startValue === 0 ? first.startTime : null;
4703
- }
4704
4729
  /**
4705
4730
  * The start time of commands.
4706
4731
  */
4707
4732
  get commandsStartTime() {
4708
- // If the first alpha command starts at zero it should be given priority over anything else.
4709
- // This is due to it creating a state where the target is not present before that time, causing any other events to not be visible.
4710
- return (this.earliestDisplayedTime ??
4711
- Math.min(...this.timelines.map((t) => t.startTime)));
4733
+ return Math.min(...this.timelines.map((t) => t.startTime));
4712
4734
  }
4713
4735
  /**
4714
4736
  * The end time of commands.
@@ -4749,7 +4771,7 @@ class CommandTimelineGroup {
4749
4771
  /**
4750
4772
  * Gets the commands from a command timeline.
4751
4773
  *
4752
- * @param timelineSelector A function that returns a command timeline.
4774
+ * @param timelineSelector A function to select the command timeline to retrieve commands from.
4753
4775
  * @param offset The offset to apply to all commands.
4754
4776
  */
4755
4777
  getCommands(timelineSelector, offset = 0) {
@@ -4861,20 +4883,14 @@ class StoryboardElement {
4861
4883
  * Represents a storyboard sprite.
4862
4884
  */
4863
4885
  class StoryboardSprite extends StoryboardElement {
4864
- _loops = [];
4865
- _triggers = [];
4866
4886
  /**
4867
4887
  * The loop commands of the sprite.
4868
4888
  */
4869
- get loops() {
4870
- return this._loops;
4871
- }
4889
+ loops = [];
4872
4890
  /**
4873
4891
  * The trigger commands of the sprite.
4874
4892
  */
4875
- get triggers() {
4876
- return this._triggers;
4877
- }
4893
+ triggers = [];
4878
4894
  /**
4879
4895
  * The origin of the sprite.
4880
4896
  */
@@ -4888,28 +4904,51 @@ class StoryboardSprite extends StoryboardElement {
4888
4904
  */
4889
4905
  timelineGroup = new CommandTimelineGroup();
4890
4906
  get startTime() {
4891
- // Check for presence affecting commands as an initial pass.
4892
- let earliestStartTime = this.timelineGroup.earliestDisplayedTime ??
4893
- Number.POSITIVE_INFINITY;
4894
- for (const l of this._loops) {
4895
- const { earliestDisplayedTime: loopEarliestDisplayTime } = l;
4896
- if (loopEarliestDisplayTime !== null) {
4897
- earliestStartTime = Math.min(earliestStartTime, l.loopStartTime + loopEarliestDisplayTime);
4907
+ // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a start value of zero.
4908
+ // A start value of zero governs, above all else, the first valid display time of a sprite.
4909
+ //
4910
+ // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero,
4911
+ // anything before that point can be ignored (the sprite is not visible after all).
4912
+ const alphaCommands = [];
4913
+ let command = this.timelineGroup.alpha.commands[0];
4914
+ if (command) {
4915
+ alphaCommands.push({
4916
+ startTime: command.startTime,
4917
+ isZeroStartValue: command.startTime === 0,
4918
+ });
4919
+ }
4920
+ for (const l of this.loops) {
4921
+ command = l.alpha.commands[0];
4922
+ if (command) {
4923
+ alphaCommands.push({
4924
+ startTime: command.startTime + l.loopStartTime,
4925
+ isZeroStartValue: command.startTime === 0,
4926
+ });
4927
+ }
4928
+ }
4929
+ if (alphaCommands.length > 0) {
4930
+ const firstAlpha = alphaCommands.sort((a, b) => a.startTime - b.startTime)[0];
4931
+ if (firstAlpha.isZeroStartValue) {
4932
+ return firstAlpha.startTime;
4898
4933
  }
4899
4934
  }
4900
- return earliestStartTime !== Number.POSITIVE_INFINITY
4901
- ? earliestStartTime
4902
- : Math.min(this.timelineGroup.startTime, ...this._loops.map((l) => l.startTime));
4935
+ // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
4936
+ // The sprite's start time will be determined by the earliest command, regardless of type.
4937
+ let earliestStartTime = this.timelineGroup.startTime;
4938
+ for (const l of this.loops) {
4939
+ earliestStartTime = Math.min(earliestStartTime, l.startTime);
4940
+ }
4941
+ return earliestStartTime;
4903
4942
  }
4904
4943
  get endTime() {
4905
- return Math.max(this.timelineGroup.endTime, ...this._loops.map((l) => l.endTime));
4944
+ return Math.max(this.timelineGroup.endTime, ...this.loops.map((l) => l.endTime));
4906
4945
  }
4907
4946
  /**
4908
4947
  * Whether this sprite has at least one command.
4909
4948
  */
4910
4949
  get hasCommands() {
4911
4950
  return (this.timelineGroup.hasCommands ||
4912
- this._loops.some((l) => l.hasCommands));
4951
+ this.loops.some((l) => l.hasCommands));
4913
4952
  }
4914
4953
  constructor(path, origin, initialPosition) {
4915
4954
  super(path);
@@ -4925,7 +4964,7 @@ class StoryboardSprite extends StoryboardElement {
4925
4964
  */
4926
4965
  addLoop(startTime, repeatCount) {
4927
4966
  const loop = new CommandLoop(startTime, repeatCount);
4928
- this._loops.push(loop);
4967
+ this.loops.push(loop);
4929
4968
  return loop;
4930
4969
  }
4931
4970
  /**
@@ -4939,7 +4978,7 @@ class StoryboardSprite extends StoryboardElement {
4939
4978
  */
4940
4979
  addTrigger(triggerName, startTime, endTime, groupNumber) {
4941
4980
  const trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber);
4942
- this._triggers.push(trigger);
4981
+ this.triggers.push(trigger);
4943
4982
  return trigger;
4944
4983
  }
4945
4984
  toString() {
@@ -5379,16 +5418,23 @@ class BeatmapDecoder extends Decoder {
5379
5418
  else {
5380
5419
  hitObjectsDecoder.applyStackingOld();
5381
5420
  }
5382
- const circleSize = new MapStats({
5421
+ const droidCircleSize = new MapStats({
5422
+ cs: this.finalResult.difficulty.cs,
5423
+ mods,
5424
+ }).calculate({ mode: exports.modes.droid }).cs;
5425
+ const droidScale = (1 - (0.7 * (droidCircleSize - 5)) / 5) / 2;
5426
+ const osuCircleSize = new MapStats({
5383
5427
  cs: this.finalResult.difficulty.cs,
5384
5428
  mods,
5385
- }).calculate().cs;
5386
- const scale = (1 - (0.7 * (circleSize - 5)) / 5) / 2;
5429
+ }).calculate({ mode: exports.modes.osu }).cs;
5430
+ const osuScale = (1 - (0.7 * (osuCircleSize - 5)) / 5) / 2;
5387
5431
  this.finalResult.hitObjects.objects.forEach((h) => {
5388
- h.scale = scale;
5432
+ h.droidScale = droidScale;
5433
+ h.osuScale = osuScale;
5389
5434
  if (h instanceof Slider) {
5390
5435
  h.nestedHitObjects.forEach((n) => {
5391
- n.scale = scale;
5436
+ n.droidScale = droidScale;
5437
+ n.osuScale = osuScale;
5392
5438
  });
5393
5439
  }
5394
5440
  });