@rian8337/osu-base 4.0.0-beta.68 → 4.0.0-beta.70

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
@@ -1040,6 +1040,35 @@ class HitObject {
1040
1040
  set scale(value) {
1041
1041
  this._scale = value;
1042
1042
  }
1043
+ /**
1044
+ * The multiplier for the stack offset of this hitobject.
1045
+ *
1046
+ * This determines how much hitobjects are stacked - and to which direction.
1047
+ */
1048
+ get stackOffsetMultiplier() {
1049
+ return this._stackOffsetMultiplier;
1050
+ }
1051
+ set stackOffsetMultiplier(value) {
1052
+ this._stackOffsetMultiplier = value;
1053
+ }
1054
+ /**
1055
+ * The stack offset vector of this hitobject.
1056
+ */
1057
+ get stackOffset() {
1058
+ return new Vector2(this.stackHeight * this.scale * this.stackOffsetMultiplier);
1059
+ }
1060
+ /**
1061
+ * The stacked position of this hitobject.
1062
+ */
1063
+ get stackedPosition() {
1064
+ return this.evaluateStackedPosition(this.position);
1065
+ }
1066
+ /**
1067
+ * The stacked end position of this hitobject.
1068
+ */
1069
+ get stackedEndPosition() {
1070
+ return this.evaluateStackedPosition(this.endPosition);
1071
+ }
1043
1072
  /**
1044
1073
  * The hitobject type (circle, slider, or spinner).
1045
1074
  */
@@ -1089,6 +1118,7 @@ class HitObject {
1089
1118
  this.hitWindow = null;
1090
1119
  this._stackHeight = 0;
1091
1120
  this._scale = 1;
1121
+ this._stackOffsetMultiplier = 0;
1092
1122
  /**
1093
1123
  * The time at which the approach circle of this hitobject should appear before this hitobject starts.
1094
1124
  */
@@ -1127,9 +1157,11 @@ class HitObject {
1127
1157
  switch (mode) {
1128
1158
  case exports.Modes.droid:
1129
1159
  this.scale = CircleSizeCalculator.droidCSToDroidScale(difficulty.cs);
1160
+ this.stackOffsetMultiplier = -4;
1130
1161
  break;
1131
1162
  case exports.Modes.osu:
1132
1163
  this.scale = CircleSizeCalculator.standardCSToStandardScale(difficulty.cs, true);
1164
+ this.stackOffsetMultiplier = -6.4;
1133
1165
  break;
1134
1166
  }
1135
1167
  }
@@ -1164,35 +1196,6 @@ class HitObject {
1164
1196
  }
1165
1197
  }
1166
1198
  }
1167
- /**
1168
- * Evaluates the stack offset vector of the hitobject.
1169
- *
1170
- * This is used to calculate offset for stacked positions.
1171
- *
1172
- * @param mode The gamemode to evaluate for.
1173
- * @returns The stack offset with respect to the gamemode.
1174
- */
1175
- getStackOffset(mode) {
1176
- return new Vector2(this.stackHeight * this.scale * (mode === exports.Modes.droid ? -4 : -6.4));
1177
- }
1178
- /**
1179
- * Evaluates the stacked position of the hitobject.
1180
- *
1181
- * @param mode The gamemode to evaluate for.
1182
- * @returns The stacked position with respect to the gamemode.
1183
- */
1184
- getStackedPosition(mode) {
1185
- return this.evaluateStackedPosition(this.position, mode);
1186
- }
1187
- /**
1188
- * Evaluates the stacked end position of the hitobject.
1189
- *
1190
- * @param mode The gamemode to evaluate for.
1191
- * @returns The stacked end position with respect to the gamemode.
1192
- */
1193
- getStackedEndPosition(mode) {
1194
- return this.evaluateStackedPosition(this.endPosition, mode);
1195
- }
1196
1199
  /**
1197
1200
  * Creates a hit sample info based on the sample setting of the first `BankHitSampleInfo.HIT_NORMAL` sample in the `samples` array.
1198
1201
  * If no sample is available, sane default settings will be used instead.
@@ -1232,14 +1235,13 @@ class HitObject {
1232
1235
  * Evaluates the stacked position of the specified position.
1233
1236
  *
1234
1237
  * @param position The position to evaluate.
1235
- * @param mode The gamemode to evaluate for.
1236
1238
  * @returns The stacked position.
1237
1239
  */
1238
- evaluateStackedPosition(position, mode) {
1239
- if (this.type & exports.ObjectTypes.spinner) {
1240
+ evaluateStackedPosition(position) {
1241
+ if ((this.type & exports.ObjectTypes.spinner) > 0 || this.stackHeight === 0) {
1240
1242
  return position;
1241
1243
  }
1242
- return position.add(this.getStackOffset(mode));
1244
+ return position.add(this.stackOffset);
1243
1245
  }
1244
1246
  }
1245
1247
  /**
@@ -1263,10 +1265,64 @@ HitObject.preemptMin = 450;
1263
1265
  */
1264
1266
  HitObject.controlPointLeniency = 1;
1265
1267
 
1268
+ /**
1269
+ * Types of easing.
1270
+ *
1271
+ * See {@link http://easings.net/ this} page for more samples.
1272
+ */
1273
+ exports.Easing = void 0;
1274
+ (function (Easing) {
1275
+ Easing[Easing["none"] = 0] = "none";
1276
+ Easing[Easing["out"] = 1] = "out";
1277
+ Easing[Easing["in"] = 2] = "in";
1278
+ Easing[Easing["inQuad"] = 3] = "inQuad";
1279
+ Easing[Easing["outQuad"] = 4] = "outQuad";
1280
+ Easing[Easing["inOutQuad"] = 5] = "inOutQuad";
1281
+ Easing[Easing["inCubic"] = 6] = "inCubic";
1282
+ Easing[Easing["outCubic"] = 7] = "outCubic";
1283
+ Easing[Easing["inOutCubic"] = 8] = "inOutCubic";
1284
+ Easing[Easing["inQuart"] = 9] = "inQuart";
1285
+ Easing[Easing["outQuart"] = 10] = "outQuart";
1286
+ Easing[Easing["inOutQuart"] = 11] = "inOutQuart";
1287
+ Easing[Easing["inQuint"] = 12] = "inQuint";
1288
+ Easing[Easing["outQuint"] = 13] = "outQuint";
1289
+ Easing[Easing["inOutQuint"] = 14] = "inOutQuint";
1290
+ Easing[Easing["inSine"] = 15] = "inSine";
1291
+ Easing[Easing["outSine"] = 16] = "outSine";
1292
+ Easing[Easing["inOutSine"] = 17] = "inOutSine";
1293
+ Easing[Easing["inExpo"] = 18] = "inExpo";
1294
+ Easing[Easing["outExpo"] = 19] = "outExpo";
1295
+ Easing[Easing["inOutExpo"] = 20] = "inOutExpo";
1296
+ Easing[Easing["inCirc"] = 21] = "inCirc";
1297
+ Easing[Easing["outCirc"] = 22] = "outCirc";
1298
+ Easing[Easing["inOutCirc"] = 23] = "inOutCirc";
1299
+ Easing[Easing["inElastic"] = 24] = "inElastic";
1300
+ Easing[Easing["outElastic"] = 25] = "outElastic";
1301
+ Easing[Easing["outElasticHalf"] = 26] = "outElasticHalf";
1302
+ Easing[Easing["outElasticQuarter"] = 27] = "outElasticQuarter";
1303
+ Easing[Easing["inOutElastic"] = 28] = "inOutElastic";
1304
+ Easing[Easing["inBack"] = 29] = "inBack";
1305
+ Easing[Easing["outBack"] = 30] = "outBack";
1306
+ Easing[Easing["inOutBack"] = 31] = "inOutBack";
1307
+ Easing[Easing["inBounce"] = 32] = "inBounce";
1308
+ Easing[Easing["outBounce"] = 33] = "outBounce";
1309
+ Easing[Easing["inOutBounce"] = 34] = "inOutBounce";
1310
+ Easing[Easing["outPow10"] = 35] = "outPow10";
1311
+ })(exports.Easing || (exports.Easing = {}));
1312
+
1266
1313
  /**
1267
1314
  * Represents a `Mod` specific setting.
1268
1315
  */
1269
1316
  class ModSetting {
1317
+ /**
1318
+ * The default value of this `ModSetting`.
1319
+ */
1320
+ get defaultValue() {
1321
+ return this._value;
1322
+ }
1323
+ set defaultValue(value) {
1324
+ this._defaultValue = value;
1325
+ }
1270
1326
  /**
1271
1327
  * The value of this `ModSetting`.
1272
1328
  */
@@ -1296,7 +1352,7 @@ class ModSetting {
1296
1352
  this.valueChangedListeners = new Set();
1297
1353
  this.name = name;
1298
1354
  this.description = description;
1299
- this.defaultValue = defaultValue;
1355
+ this._defaultValue = defaultValue;
1300
1356
  this._value = defaultValue;
1301
1357
  }
1302
1358
  /**
@@ -1344,19 +1400,29 @@ class Mod {
1344
1400
  * `Mod`s that are incompatible with this `Mod`.
1345
1401
  */
1346
1402
  this.incompatibleMods = new Set();
1403
+ this.settingsBacking = null;
1347
1404
  }
1348
1405
  /**
1349
1406
  * `ModSetting`s that are specific to this `Mod`.
1350
1407
  */
1351
1408
  get settings() {
1352
- const settings = [];
1409
+ if (this.settingsBacking !== null) {
1410
+ return this.settingsBacking;
1411
+ }
1412
+ this.settingsBacking = [];
1353
1413
  for (const prop in this) {
1354
1414
  const value = this[prop];
1355
1415
  if (value instanceof ModSetting) {
1356
- settings.push(value);
1416
+ this.settingsBacking.push(value);
1357
1417
  }
1358
1418
  }
1359
- return settings;
1419
+ return this.settingsBacking;
1420
+ }
1421
+ /**
1422
+ * Whether all `ModSetting`s of this `Mod` are set to their default values.
1423
+ */
1424
+ get usesDefaultSettings() {
1425
+ return this.settings.every((s) => s.isDefault);
1360
1426
  }
1361
1427
  /**
1362
1428
  * Serializes this `Mod` to a `SerializedMod`.
@@ -1464,7 +1530,27 @@ class Mod {
1464
1530
  * @returns `true` if the object is the same `Mod`, `false` otherwise.
1465
1531
  */
1466
1532
  equals(other) {
1467
- return this === other || this.acronym === other.acronym;
1533
+ if (this === other) {
1534
+ return true;
1535
+ }
1536
+ if (this.acronym !== other.acronym) {
1537
+ return false;
1538
+ }
1539
+ const settings = this.settings;
1540
+ const otherSettings = other.settings;
1541
+ if (settings.length !== otherSettings.length) {
1542
+ return false;
1543
+ }
1544
+ for (const setting of settings) {
1545
+ const otherSetting = otherSettings.find((s) => s.name === setting.name);
1546
+ if (!otherSetting) {
1547
+ return false;
1548
+ }
1549
+ if (setting.value !== otherSetting.value) {
1550
+ return false;
1551
+ }
1552
+ }
1553
+ return true;
1468
1554
  }
1469
1555
  /**
1470
1556
  * Returns the string representation of this `Mod`.
@@ -1475,329 +1561,105 @@ class Mod {
1475
1561
  }
1476
1562
 
1477
1563
  /**
1478
- * Represents the Relax mod.
1564
+ * Represents a hitobject that can be nested within a slider.
1479
1565
  */
1480
- class ModRelax extends Mod {
1481
- constructor() {
1482
- super();
1483
- this.acronym = "RX";
1484
- this.name = "Relax";
1485
- this.droidRanked = false;
1486
- this.osuRanked = false;
1487
- this.bitwise = 1 << 7;
1488
- this.incompatibleMods.add(ModAuto).add(ModAutopilot);
1489
- }
1490
- get isDroidRelevant() {
1491
- return true;
1492
- }
1493
- calculateDroidScoreMultiplier() {
1494
- return 0.001;
1566
+ class SliderNestedHitObject extends HitObject {
1567
+ constructor(values) {
1568
+ super(values);
1569
+ this.spanIndex = values.spanIndex;
1570
+ this.spanStartTime = values.spanStartTime;
1495
1571
  }
1496
- get isOsuRelevant() {
1497
- return true;
1572
+ toString() {
1573
+ return `Position: [${this._position.x}, ${this._position.y}], span index: ${this.spanIndex}, span start time: ${this.spanStartTime}`;
1498
1574
  }
1499
- get osuScoreMultiplier() {
1500
- return 0;
1575
+ }
1576
+
1577
+ /**
1578
+ * Represents the head of a slider.
1579
+ */
1580
+ class SliderHead extends SliderNestedHitObject {
1581
+ constructor(values) {
1582
+ super(Object.assign(Object.assign({}, values), { spanIndex: 0, spanStartTime: values.startTime }));
1501
1583
  }
1502
1584
  }
1503
1585
 
1504
1586
  /**
1505
- * Represents the Autopilot mod.
1587
+ * An empty `HitWindow` that does not have any hit windows.
1588
+ *
1589
+ * No time values are provided (meaning instantaneous hit or miss).
1506
1590
  */
1507
- class ModAutopilot extends Mod {
1591
+ class EmptyHitWindow extends HitWindow {
1508
1592
  constructor() {
1509
- super();
1510
- this.acronym = "AP";
1511
- this.name = "Autopilot";
1512
- this.droidRanked = false;
1513
- this.osuRanked = false;
1514
- this.bitwise = 1 << 13;
1515
- this.incompatibleMods.add(ModRelax).add(ModAuto);
1516
- }
1517
- get isDroidRelevant() {
1518
- return true;
1593
+ super(0);
1519
1594
  }
1520
- calculateDroidScoreMultiplier() {
1521
- return 0.001;
1595
+ get greatWindow() {
1596
+ return 0;
1522
1597
  }
1523
- get isOsuRelevant() {
1524
- return true;
1598
+ get okWindow() {
1599
+ return 0;
1525
1600
  }
1526
- get osuScoreMultiplier() {
1601
+ get mehWindow() {
1527
1602
  return 0;
1528
1603
  }
1529
1604
  }
1530
1605
 
1531
1606
  /**
1532
- * Represents the Auto mod.
1607
+ * Represents a nested hit object that is at the end of a slider path (either repeat or tail).
1533
1608
  */
1534
- class ModAuto extends Mod {
1535
- constructor() {
1536
- super();
1537
- this.acronym = "AT";
1538
- this.name = "Autoplay";
1539
- this.droidRanked = false;
1540
- this.osuRanked = false;
1541
- this.bitwise = 1 << 11;
1542
- this.incompatibleMods.add(ModAutopilot).add(ModRelax);
1543
- }
1544
- get isDroidRelevant() {
1545
- return true;
1546
- }
1547
- calculateDroidScoreMultiplier() {
1548
- return 1;
1609
+ class SliderEndCircle extends SliderNestedHitObject {
1610
+ constructor(values) {
1611
+ super(values);
1612
+ this.sliderSpanDuration = values.sliderSpanDuration;
1613
+ this.sliderStartTime = values.sliderStartTime;
1549
1614
  }
1550
- get isOsuRelevant() {
1551
- return true;
1615
+ applyDefaults(controlPoints, difficulty, mode) {
1616
+ super.applyDefaults(controlPoints, difficulty, mode);
1617
+ if (this.spanIndex > 0) {
1618
+ // Repeat points after the first span should appear behind the still-visible one.
1619
+ this.timeFadeIn = 0;
1620
+ // The next end circle should appear exactly after the previous circle (on the same end) is hit.
1621
+ this.timePreempt = this.sliderSpanDuration * 2;
1622
+ }
1623
+ else {
1624
+ // The first end circle should fade in with the slider.
1625
+ this.timePreempt += this.startTime - this.sliderStartTime;
1626
+ }
1552
1627
  }
1553
- get osuScoreMultiplier() {
1554
- return 1;
1628
+ createHitWindow() {
1629
+ return new EmptyHitWindow();
1555
1630
  }
1556
1631
  }
1557
1632
 
1558
1633
  /**
1559
- * Represents a `Mod` specific setting that is constrained to a range of values.
1634
+ * Represents a slider repeat.
1560
1635
  */
1561
- class RangeConstrainedModSetting extends ModSetting {
1562
- get value() {
1563
- return super.value;
1564
- }
1565
- set value(value) {
1566
- super.value = this.processValue(value);
1567
- }
1568
- constructor(name, description, defaultValue, min, max, step) {
1569
- super(name, description, defaultValue);
1570
- this.min = min;
1571
- this.max = max;
1572
- this.step = step;
1573
- }
1636
+ class SliderRepeat extends SliderEndCircle {
1574
1637
  }
1575
1638
 
1576
1639
  /**
1577
- * Represents a `Mod` specific setting that is constrained to a number of values.
1640
+ * Represents a slider tick in a slider.
1578
1641
  */
1579
- class NumberModSetting extends RangeConstrainedModSetting {
1580
- constructor(name, description, defaultValue, min, max, step) {
1581
- super(name, description, defaultValue, min, max, step);
1582
- this.displayFormatter = (v) => v.toString();
1583
- if (min > max) {
1584
- throw new RangeError(`The minimum value (${min}) must be less than or equal to the maximum value (${max}).`);
1585
- }
1586
- if (step < 0) {
1587
- throw new RangeError(`The step size (${step}) must be greater than or equal to 0.`);
1642
+ class SliderTick extends SliderNestedHitObject {
1643
+ applyDefaults(controlPoints, difficulty, mode) {
1644
+ super.applyDefaults(controlPoints, difficulty, mode);
1645
+ let offset;
1646
+ if (this.spanIndex > 0) {
1647
+ // Adding 200 to include the offset stable used.
1648
+ // This is so on repeats ticks don't appear too late to be visually processed by the player.
1649
+ offset = 200;
1588
1650
  }
1589
- if (defaultValue < min || defaultValue > max) {
1590
- throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
1651
+ else {
1652
+ offset = this.timePreempt * 0.66;
1591
1653
  }
1654
+ this.timePreempt = (this.startTime - this.spanStartTime) / 2 + offset;
1592
1655
  }
1593
- processValue(value) {
1594
- return MathUtils.clamp(Math.round(value / this.step) * this.step, this.min, this.max);
1656
+ createHitWindow() {
1657
+ return new EmptyHitWindow();
1595
1658
  }
1596
1659
  }
1597
1660
 
1598
1661
  /**
1599
- * Represents a `Mod` specific setting that is constrained to a range of decimal values.
1600
- */
1601
- class DecimalModSetting extends NumberModSetting {
1602
- constructor(name, description, defaultValue, min = -Number.MAX_VALUE, max = Number.MAX_VALUE, step = 0, precision = null) {
1603
- super(name, description, defaultValue, min, max, step);
1604
- this.displayFormatter = (v) => {
1605
- if (this.precision !== null) {
1606
- return v.toFixed(this.precision);
1607
- }
1608
- return super.toDisplayString();
1609
- };
1610
- if (precision !== null && precision < 0) {
1611
- throw new RangeError(`The precision (${precision}) must be greater than or equal to 0.`);
1612
- }
1613
- this.precision = precision;
1614
- }
1615
- processValue(value) {
1616
- const processedValue = super.processValue(value);
1617
- if (this.precision !== null) {
1618
- return parseFloat(processedValue.toFixed(this.precision));
1619
- }
1620
- return processedValue;
1621
- }
1622
- }
1623
-
1624
- /**
1625
- * Represents a `Mod` that adjusts the playback rate of a track.
1626
- */
1627
- class ModRateAdjust extends Mod {
1628
- /**
1629
- * The generic osu!droid score multiplier of this `Mod`.
1630
- */
1631
- get droidScoreMultiplier() {
1632
- return this.trackRateMultiplier.value >= 1
1633
- ? 1 + (this.trackRateMultiplier.value - 1) * 0.24
1634
- : Math.pow(0.3, (1 - this.trackRateMultiplier.value) * 4);
1635
- }
1636
- /**
1637
- * Generic getter to determine if this `ModRateAdjust` is relevant.
1638
- */
1639
- get isRelevant() {
1640
- return this.trackRateMultiplier.value !== 1;
1641
- }
1642
- constructor(trackRateMultiplier = 1) {
1643
- super();
1644
- this.trackRateMultiplier = new DecimalModSetting("Track rate multiplier", "The multiplier for the track's playback rate after applying this mod.", trackRateMultiplier, 0.5, 2, 0.05, 2);
1645
- }
1646
- applyToRate(_, rate) {
1647
- return rate * this.trackRateMultiplier.value;
1648
- }
1649
- equals(other) {
1650
- return (super.equals(other) &&
1651
- other instanceof ModRateAdjust &&
1652
- this.trackRateMultiplier.value === other.trackRateMultiplier.value);
1653
- }
1654
- }
1655
-
1656
- /**
1657
- * Represents the Custom Speed mod.
1658
- *
1659
- * This is a replacement `Mod` for speed modify in osu!droid and custom rates in osu!lazer.
1660
- */
1661
- class ModCustomSpeed extends ModRateAdjust {
1662
- constructor() {
1663
- super(...arguments);
1664
- this.acronym = "CS";
1665
- this.name = "Custom Speed";
1666
- this.droidRanked = true;
1667
- this.osuRanked = false;
1668
- }
1669
- copySettings(mod) {
1670
- var _a, _b;
1671
- super.copySettings(mod);
1672
- this.trackRateMultiplier.value =
1673
- (_b = (_a = mod.settings) === null || _a === void 0 ? void 0 : _a.rateMultiplier) !== null && _b !== void 0 ? _b : this.trackRateMultiplier.value;
1674
- }
1675
- get isDroidRelevant() {
1676
- return this.isRelevant;
1677
- }
1678
- calculateDroidScoreMultiplier() {
1679
- return this.droidScoreMultiplier;
1680
- }
1681
- get isOsuRelevant() {
1682
- return this.isRelevant;
1683
- }
1684
- get osuScoreMultiplier() {
1685
- // Round to the nearest multiple of 0.1.
1686
- let value = Math.trunc(this.trackRateMultiplier.value * 10) / 10;
1687
- // Offset back to 0.
1688
- --value;
1689
- return this.trackRateMultiplier.value >= 1
1690
- ? 1 + value / 5
1691
- : 0.6 + value;
1692
- }
1693
- serializeSettings() {
1694
- return { rateMultiplier: this.trackRateMultiplier.value };
1695
- }
1696
- toString() {
1697
- return `${super.toString()} (${this.trackRateMultiplier.toDisplayString()}x)`;
1698
- }
1699
- }
1700
-
1701
- /**
1702
- * Represents a hitobject that can be nested within a slider.
1703
- */
1704
- class SliderNestedHitObject extends HitObject {
1705
- constructor(values) {
1706
- super(values);
1707
- this.spanIndex = values.spanIndex;
1708
- this.spanStartTime = values.spanStartTime;
1709
- }
1710
- toString() {
1711
- return `Position: [${this._position.x}, ${this._position.y}], span index: ${this.spanIndex}, span start time: ${this.spanStartTime}`;
1712
- }
1713
- }
1714
-
1715
- /**
1716
- * Represents the head of a slider.
1717
- */
1718
- class SliderHead extends SliderNestedHitObject {
1719
- constructor(values) {
1720
- super(Object.assign(Object.assign({}, values), { spanIndex: 0, spanStartTime: values.startTime }));
1721
- }
1722
- }
1723
-
1724
- /**
1725
- * An empty `HitWindow` that does not have any hit windows.
1726
- *
1727
- * No time values are provided (meaning instantaneous hit or miss).
1728
- */
1729
- class EmptyHitWindow extends HitWindow {
1730
- constructor() {
1731
- super(0);
1732
- }
1733
- get greatWindow() {
1734
- return 0;
1735
- }
1736
- get okWindow() {
1737
- return 0;
1738
- }
1739
- get mehWindow() {
1740
- return 0;
1741
- }
1742
- }
1743
-
1744
- /**
1745
- * Represents a nested hit object that is at the end of a slider path (either repeat or tail).
1746
- */
1747
- class SliderEndCircle extends SliderNestedHitObject {
1748
- constructor(values) {
1749
- super(values);
1750
- this.sliderSpanDuration = values.sliderSpanDuration;
1751
- this.sliderStartTime = values.sliderStartTime;
1752
- }
1753
- applyDefaults(controlPoints, difficulty, mode) {
1754
- super.applyDefaults(controlPoints, difficulty, mode);
1755
- if (this.spanIndex > 0) {
1756
- // Repeat points after the first span should appear behind the still-visible one.
1757
- this.timeFadeIn = 0;
1758
- // The next end circle should appear exactly after the previous circle (on the same end) is hit.
1759
- this.timePreempt = this.sliderSpanDuration * 2;
1760
- }
1761
- else {
1762
- // The first end circle should fade in with the slider.
1763
- this.timePreempt += this.startTime - this.sliderStartTime;
1764
- }
1765
- }
1766
- createHitWindow() {
1767
- return new EmptyHitWindow();
1768
- }
1769
- }
1770
-
1771
- /**
1772
- * Represents a slider repeat.
1773
- */
1774
- class SliderRepeat extends SliderEndCircle {
1775
- }
1776
-
1777
- /**
1778
- * Represents a slider tick in a slider.
1779
- */
1780
- class SliderTick extends SliderNestedHitObject {
1781
- applyDefaults(controlPoints, difficulty, mode) {
1782
- super.applyDefaults(controlPoints, difficulty, mode);
1783
- let offset;
1784
- if (this.spanIndex > 0) {
1785
- // Adding 200 to include the offset stable used.
1786
- // This is so on repeats ticks don't appear too late to be visually processed by the player.
1787
- offset = 200;
1788
- }
1789
- else {
1790
- offset = this.timePreempt * 0.66;
1791
- }
1792
- this.timePreempt = (this.startTime - this.spanStartTime) / 2 + offset;
1793
- }
1794
- createHitWindow() {
1795
- return new EmptyHitWindow();
1796
- }
1797
- }
1798
-
1799
- /**
1800
- * Represents the tail of a slider.
1662
+ * Represents the tail of a slider.
1801
1663
  */
1802
1664
  class SliderTail extends SliderEndCircle {
1803
1665
  }
@@ -2297,16 +2159,654 @@ class Slider extends HitObject {
2297
2159
  default:
2298
2160
  v.samples.push(...sampleList);
2299
2161
  }
2300
- });
2162
+ });
2163
+ }
2164
+ toString() {
2165
+ 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}`;
2166
+ }
2167
+ }
2168
+ Slider.baseNormalSlideSample = new BankHitSampleInfo("sliderslide");
2169
+ Slider.baseWhistleSlideSample = new BankHitSampleInfo("sliderwhistle");
2170
+ Slider.baseTickSample = new BankHitSampleInfo("slidertick");
2171
+ Slider.legacyLastTickOffset = 36;
2172
+
2173
+ /**
2174
+ * Represents the Freeze Frame mod.
2175
+ */
2176
+ class ModFreezeFrame extends Mod {
2177
+ constructor() {
2178
+ super();
2179
+ this.name = "Freeze Frame";
2180
+ this.acronym = "FR";
2181
+ this.lastNewComboTime = 0;
2182
+ this.incompatibleMods.add(ModApproachDifferent);
2183
+ }
2184
+ get droidRanked() {
2185
+ return false;
2186
+ }
2187
+ get isDroidRelevant() {
2188
+ return true;
2189
+ }
2190
+ calculateDroidScoreMultiplier() {
2191
+ return 1;
2192
+ }
2193
+ get osuRanked() {
2194
+ return false;
2195
+ }
2196
+ get isOsuRelevant() {
2197
+ return true;
2198
+ }
2199
+ get osuScoreMultiplier() {
2200
+ return 1;
2201
+ }
2202
+ applyToBeatmap(beatmap) {
2203
+ this.lastNewComboTime = 0;
2204
+ for (const hitObject of beatmap.hitObjects) {
2205
+ if (hitObject.isNewCombo) {
2206
+ this.lastNewComboTime = hitObject.startTime;
2207
+ }
2208
+ this.applyFadeInAdjustment(hitObject);
2209
+ }
2210
+ }
2211
+ applyFadeInAdjustment(hitObject) {
2212
+ hitObject.timePreempt += hitObject.startTime - this.lastNewComboTime;
2213
+ if (hitObject instanceof Slider) {
2214
+ // Freezing slider ticks doesn't play well with snaking sliders, and slider repeats will not
2215
+ // layer correctly if its preempt is changed.
2216
+ this.applyFadeInAdjustment(hitObject.head);
2217
+ this.applyFadeInAdjustment(hitObject.tail);
2218
+ }
2219
+ }
2220
+ }
2221
+
2222
+ /**
2223
+ * Represents the Traceable mod.
2224
+ */
2225
+ class ModTraceable extends Mod {
2226
+ constructor() {
2227
+ super();
2228
+ this.acronym = "TC";
2229
+ this.name = "Traceable";
2230
+ this.droidRanked = false;
2231
+ this.osuRanked = false;
2232
+ this.incompatibleMods.add(ModHidden);
2233
+ }
2234
+ get isDroidRelevant() {
2235
+ return true;
2236
+ }
2237
+ calculateDroidScoreMultiplier() {
2238
+ return 1.06;
2239
+ }
2240
+ get isOsuRelevant() {
2241
+ return true;
2242
+ }
2243
+ get osuScoreMultiplier() {
2244
+ return 1;
2245
+ }
2246
+ }
2247
+
2248
+ /**
2249
+ * Represents a `Mod` specific setting that is constrained to a boolean value.
2250
+ */
2251
+ class BooleanModSetting extends ModSetting {
2252
+ constructor() {
2253
+ super(...arguments);
2254
+ this.displayFormatter = (v) => v ? "Enabled" : "Disabled";
2255
+ }
2256
+ }
2257
+
2258
+ /**
2259
+ * Represents the Hidden mod.
2260
+ */
2261
+ class ModHidden extends Mod {
2262
+ get droidRanked() {
2263
+ return this.usesDefaultSettings;
2264
+ }
2265
+ get osuRanked() {
2266
+ return this.usesDefaultSettings;
2267
+ }
2268
+ constructor() {
2269
+ super();
2270
+ this.acronym = "HD";
2271
+ this.name = "Hidden";
2272
+ this.bitwise = 1 << 3;
2273
+ /**
2274
+ * Whether to only fade approach circles.
2275
+ *
2276
+ * The main object body will not fade when enabled.
2277
+ */
2278
+ this.onlyFadeApproachCircles = new BooleanModSetting("Only fade approach circles", "The main object body will not fade when enabled.", false);
2279
+ this.incompatibleMods.add(ModTraceable).add(ModApproachDifferent);
2280
+ }
2281
+ get isDroidRelevant() {
2282
+ return true;
2283
+ }
2284
+ calculateDroidScoreMultiplier() {
2285
+ return 1.06;
2286
+ }
2287
+ get isOsuRelevant() {
2288
+ return true;
2289
+ }
2290
+ get osuScoreMultiplier() {
2291
+ return 1.06;
2292
+ }
2293
+ copySettings(mod) {
2294
+ var _a, _b;
2295
+ super.copySettings(mod);
2296
+ this.onlyFadeApproachCircles.value =
2297
+ (_b = (_a = mod.settings) === null || _a === void 0 ? void 0 : _a.onlyFadeApproachCircles) !== null && _b !== void 0 ? _b : this.onlyFadeApproachCircles.value;
2298
+ }
2299
+ applyToBeatmap(beatmap) {
2300
+ const applyFadeInAdjustment = (hitObject) => {
2301
+ hitObject.timeFadeIn =
2302
+ hitObject.timePreempt * ModHidden.fadeInDurationMultiplier;
2303
+ if (hitObject instanceof Slider) {
2304
+ hitObject.nestedHitObjects.forEach(applyFadeInAdjustment);
2305
+ }
2306
+ };
2307
+ beatmap.hitObjects.objects.forEach(applyFadeInAdjustment);
2308
+ }
2309
+ serializeSettings() {
2310
+ return this.onlyFadeApproachCircles.value
2311
+ ? { onlyFadeApproachCircles: this.onlyFadeApproachCircles.value }
2312
+ : null;
2313
+ }
2314
+ toString() {
2315
+ if (!this.onlyFadeApproachCircles.value) {
2316
+ return super.toString();
2317
+ }
2318
+ return `${super.toString()} (approach circles only)`;
2319
+ }
2320
+ }
2321
+ ModHidden.fadeInDurationMultiplier = 0.4;
2322
+ ModHidden.fadeOutDurationMultiplier = 0.3;
2323
+
2324
+ /**
2325
+ * Represents a `Mod` specific setting that is constrained to a range of values.
2326
+ */
2327
+ class RangeConstrainedModSetting extends ModSetting {
2328
+ /**
2329
+ * The minimum value of this `RangeConstrainedModSetting`.
2330
+ */
2331
+ get min() {
2332
+ return this._min;
2333
+ }
2334
+ set min(value) {
2335
+ this._min = value;
2336
+ this.value = this.processValue(this.value);
2337
+ }
2338
+ /**
2339
+ * The maximum value of this `RangeConstrainedModSetting`.
2340
+ */
2341
+ get max() {
2342
+ return this._max;
2343
+ }
2344
+ set max(value) {
2345
+ this._max = value;
2346
+ this.value = this.processValue(this.value);
2347
+ }
2348
+ /**
2349
+ * The step size of this `RangeConstrainedModSetting`.
2350
+ */
2351
+ get step() {
2352
+ return this._step;
2353
+ }
2354
+ set step(value) {
2355
+ this._step = value;
2356
+ this.value = this.processValue(this.value);
2357
+ }
2358
+ get value() {
2359
+ return super.value;
2360
+ }
2361
+ set value(value) {
2362
+ super.value = this.processValue(value);
2363
+ }
2364
+ constructor(name, description, defaultValue, min, max, step) {
2365
+ super(name, description, defaultValue);
2366
+ this._min = min;
2367
+ this._max = max;
2368
+ this._step = step;
2369
+ }
2370
+ }
2371
+
2372
+ /**
2373
+ * Represents a `Mod` specific setting that is constrained to a number of values.
2374
+ */
2375
+ class NumberModSetting extends RangeConstrainedModSetting {
2376
+ constructor(name, description, defaultValue, min, max, step) {
2377
+ super(name, description, defaultValue, min, max, step);
2378
+ this.displayFormatter = (v) => v.toString();
2379
+ if (min > max) {
2380
+ throw new RangeError(`The minimum value (${min}) must be less than or equal to the maximum value (${max}).`);
2381
+ }
2382
+ if (step < 0) {
2383
+ throw new RangeError(`The step size (${step}) must be greater than or equal to 0.`);
2384
+ }
2385
+ if (defaultValue < min || defaultValue > max) {
2386
+ throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
2387
+ }
2388
+ }
2389
+ processValue(value) {
2390
+ return MathUtils.clamp(Math.round(value / this.step) * this.step, this.min, this.max);
2391
+ }
2392
+ }
2393
+
2394
+ /**
2395
+ * Represents a `Mod` specific setting that is constrained to a range of decimal values.
2396
+ */
2397
+ class DecimalModSetting extends NumberModSetting {
2398
+ /**
2399
+ * The number of decimal places to round the value to.
2400
+ *
2401
+ * When set to `null`, the value will not be rounded.
2402
+ */
2403
+ get precision() {
2404
+ return this._precision;
2405
+ }
2406
+ set precision(value) {
2407
+ if (value !== null && value < 0) {
2408
+ throw new RangeError(`The precision (${value}) must be greater than or equal to 0.`);
2409
+ }
2410
+ this._precision = value;
2411
+ if (value !== null) {
2412
+ this.value = this.processValue(this.value);
2413
+ }
2414
+ }
2415
+ constructor(name, description, defaultValue, min = -Number.MAX_VALUE, max = Number.MAX_VALUE, step = 0, precision = null) {
2416
+ super(name, description, defaultValue, min, max, step);
2417
+ this.displayFormatter = (v) => {
2418
+ if (this.precision !== null) {
2419
+ return v.toFixed(this.precision);
2420
+ }
2421
+ return super.toDisplayString();
2422
+ };
2423
+ if (precision !== null && precision < 0) {
2424
+ throw new RangeError(`The precision (${precision}) must be greater than or equal to 0.`);
2425
+ }
2426
+ this._precision = precision;
2427
+ }
2428
+ processValue(value) {
2429
+ const processedValue = super.processValue(value);
2430
+ if (this.precision !== null) {
2431
+ return parseFloat(processedValue.toFixed(this.precision));
2432
+ }
2433
+ return processedValue;
2434
+ }
2435
+ }
2436
+
2437
+ /**
2438
+ * Represents the Approach Different mod.
2439
+ */
2440
+ class ModApproachDifferent extends Mod {
2441
+ /**
2442
+ * The {@link Easing} to apply to the approach circle animation.
2443
+ */
2444
+ get easing() {
2445
+ switch (this.style.value) {
2446
+ case exports.AnimationStyle.linear:
2447
+ return exports.Easing.none;
2448
+ case exports.AnimationStyle.gravity:
2449
+ return exports.Easing.inBack;
2450
+ case exports.AnimationStyle.inOut1:
2451
+ return exports.Easing.inOutCubic;
2452
+ case exports.AnimationStyle.inOut2:
2453
+ return exports.Easing.inOutQuint;
2454
+ case exports.AnimationStyle.accelerate1:
2455
+ return exports.Easing.in;
2456
+ case exports.AnimationStyle.accelerate2:
2457
+ return exports.Easing.inCubic;
2458
+ case exports.AnimationStyle.accelerate3:
2459
+ return exports.Easing.inQuint;
2460
+ case exports.AnimationStyle.decelerate1:
2461
+ return exports.Easing.out;
2462
+ case exports.AnimationStyle.decelerate2:
2463
+ return exports.Easing.outCubic;
2464
+ case exports.AnimationStyle.decelerate3:
2465
+ return exports.Easing.outQuint;
2466
+ }
2467
+ }
2468
+ constructor() {
2469
+ super();
2470
+ this.name = "Approach Different";
2471
+ this.acronym = "AD";
2472
+ /**
2473
+ * The initial size of the approach circle, relative to hit circles.
2474
+ */
2475
+ this.scale = new DecimalModSetting("Initial size", "The initial size of the approach circle, relative to hit circles.", 3, 1.5, 10, 0.1, 1);
2476
+ /**
2477
+ * The animation style of the approach circles.
2478
+ */
2479
+ this.style = new ModSetting("Style", "The animation style of the approach circles.", exports.AnimationStyle.gravity);
2480
+ this.incompatibleMods.add(ModHidden).add(ModFreezeFrame);
2481
+ }
2482
+ get droidRanked() {
2483
+ return false;
2484
+ }
2485
+ get isDroidRelevant() {
2486
+ return true;
2487
+ }
2488
+ calculateDroidScoreMultiplier() {
2489
+ return 1;
2490
+ }
2491
+ get osuRanked() {
2492
+ return false;
2493
+ }
2494
+ get isOsuRelevant() {
2495
+ return true;
2496
+ }
2497
+ get osuScoreMultiplier() {
2498
+ return 1;
2499
+ }
2500
+ copySettings(mod) {
2501
+ super.copySettings(mod);
2502
+ const { settings } = mod;
2503
+ if (typeof (settings === null || settings === void 0 ? void 0 : settings.scale) === "number") {
2504
+ this.scale.value = settings.scale;
2505
+ }
2506
+ if (typeof (settings === null || settings === void 0 ? void 0 : settings.style) === "number") {
2507
+ this.style.value = settings.style;
2508
+ }
2509
+ }
2510
+ serializeSettings() {
2511
+ return {
2512
+ scale: this.scale.value,
2513
+ style: this.style.value,
2514
+ };
2515
+ }
2516
+ }
2517
+ exports.AnimationStyle = void 0;
2518
+ (function (AnimationStyle) {
2519
+ AnimationStyle[AnimationStyle["linear"] = 0] = "linear";
2520
+ AnimationStyle[AnimationStyle["gravity"] = 1] = "gravity";
2521
+ AnimationStyle[AnimationStyle["inOut1"] = 2] = "inOut1";
2522
+ AnimationStyle[AnimationStyle["inOut2"] = 3] = "inOut2";
2523
+ AnimationStyle[AnimationStyle["accelerate1"] = 4] = "accelerate1";
2524
+ AnimationStyle[AnimationStyle["accelerate2"] = 5] = "accelerate2";
2525
+ AnimationStyle[AnimationStyle["accelerate3"] = 6] = "accelerate3";
2526
+ AnimationStyle[AnimationStyle["decelerate1"] = 7] = "decelerate1";
2527
+ AnimationStyle[AnimationStyle["decelerate2"] = 8] = "decelerate2";
2528
+ AnimationStyle[AnimationStyle["decelerate3"] = 9] = "decelerate3";
2529
+ })(exports.AnimationStyle || (exports.AnimationStyle = {}));
2530
+
2531
+ /**
2532
+ * Represents the Relax mod.
2533
+ */
2534
+ class ModRelax extends Mod {
2535
+ constructor() {
2536
+ super();
2537
+ this.acronym = "RX";
2538
+ this.name = "Relax";
2539
+ this.droidRanked = false;
2540
+ this.osuRanked = false;
2541
+ this.bitwise = 1 << 7;
2542
+ this.incompatibleMods.add(ModAuto).add(ModAutopilot);
2543
+ }
2544
+ get isDroidRelevant() {
2545
+ return true;
2546
+ }
2547
+ calculateDroidScoreMultiplier() {
2548
+ return 0.001;
2549
+ }
2550
+ get isOsuRelevant() {
2551
+ return true;
2552
+ }
2553
+ get osuScoreMultiplier() {
2554
+ return 0;
2555
+ }
2556
+ }
2557
+
2558
+ /**
2559
+ * Represents the Autopilot mod.
2560
+ */
2561
+ class ModAutopilot extends Mod {
2562
+ constructor() {
2563
+ super();
2564
+ this.acronym = "AP";
2565
+ this.name = "Autopilot";
2566
+ this.droidRanked = false;
2567
+ this.osuRanked = false;
2568
+ this.bitwise = 1 << 13;
2569
+ this.incompatibleMods.add(ModRelax).add(ModAuto);
2570
+ }
2571
+ get isDroidRelevant() {
2572
+ return true;
2573
+ }
2574
+ calculateDroidScoreMultiplier() {
2575
+ return 0.001;
2576
+ }
2577
+ get isOsuRelevant() {
2578
+ return true;
2579
+ }
2580
+ get osuScoreMultiplier() {
2581
+ return 0;
2582
+ }
2583
+ }
2584
+
2585
+ /**
2586
+ * Represents the Auto mod.
2587
+ */
2588
+ class ModAuto extends Mod {
2589
+ constructor() {
2590
+ super();
2591
+ this.acronym = "AT";
2592
+ this.name = "Autoplay";
2593
+ this.droidRanked = false;
2594
+ this.osuRanked = false;
2595
+ this.bitwise = 1 << 11;
2596
+ this.incompatibleMods.add(ModAutopilot).add(ModRelax);
2597
+ }
2598
+ get isDroidRelevant() {
2599
+ return true;
2600
+ }
2601
+ calculateDroidScoreMultiplier() {
2602
+ return 1;
2603
+ }
2604
+ get isOsuRelevant() {
2605
+ return true;
2606
+ }
2607
+ get osuScoreMultiplier() {
2608
+ return 1;
2609
+ }
2610
+ }
2611
+
2612
+ /**
2613
+ * Represents a `Mod` that adjusts the playback rate of a track.
2614
+ */
2615
+ class ModRateAdjust extends Mod {
2616
+ /**
2617
+ * The generic osu!droid score multiplier of this `Mod`.
2618
+ */
2619
+ get droidScoreMultiplier() {
2620
+ return this.trackRateMultiplier.value >= 1
2621
+ ? 1 + (this.trackRateMultiplier.value - 1) * 0.24
2622
+ : Math.pow(0.3, (1 - this.trackRateMultiplier.value) * 4);
2623
+ }
2624
+ /**
2625
+ * Generic getter to determine if this `ModRateAdjust` is relevant.
2626
+ */
2627
+ get isRelevant() {
2628
+ return this.trackRateMultiplier.value !== 1;
2629
+ }
2630
+ constructor(trackRateMultiplier = 1) {
2631
+ super();
2632
+ this.trackRateMultiplier = new DecimalModSetting("Track rate multiplier", "The multiplier for the track's playback rate after applying this mod.", trackRateMultiplier, 0.5, 2, 0.05, 2);
2633
+ }
2634
+ applyToRate(_, rate) {
2635
+ return rate * this.trackRateMultiplier.value;
2636
+ }
2637
+ }
2638
+
2639
+ /**
2640
+ * Represents the Custom Speed mod.
2641
+ *
2642
+ * This is a replacement `Mod` for speed modify in osu!droid and custom rates in osu!lazer.
2643
+ */
2644
+ class ModCustomSpeed extends ModRateAdjust {
2645
+ constructor() {
2646
+ super(...arguments);
2647
+ this.acronym = "CS";
2648
+ this.name = "Custom Speed";
2649
+ this.droidRanked = true;
2650
+ this.osuRanked = false;
2651
+ }
2652
+ copySettings(mod) {
2653
+ var _a, _b;
2654
+ super.copySettings(mod);
2655
+ this.trackRateMultiplier.value =
2656
+ (_b = (_a = mod.settings) === null || _a === void 0 ? void 0 : _a.rateMultiplier) !== null && _b !== void 0 ? _b : this.trackRateMultiplier.value;
2657
+ }
2658
+ get isDroidRelevant() {
2659
+ return this.isRelevant;
2660
+ }
2661
+ calculateDroidScoreMultiplier() {
2662
+ return this.droidScoreMultiplier;
2663
+ }
2664
+ get isOsuRelevant() {
2665
+ return this.isRelevant;
2666
+ }
2667
+ get osuScoreMultiplier() {
2668
+ // Round to the nearest multiple of 0.1.
2669
+ let value = Math.trunc(this.trackRateMultiplier.value * 10) / 10;
2670
+ // Offset back to 0.
2671
+ --value;
2672
+ return this.trackRateMultiplier.value >= 1
2673
+ ? 1 + value / 5
2674
+ : 0.6 + value;
2675
+ }
2676
+ serializeSettings() {
2677
+ return { rateMultiplier: this.trackRateMultiplier.value };
2678
+ }
2679
+ toString() {
2680
+ return `${super.toString()} (${this.trackRateMultiplier.toDisplayString()}x)`;
2681
+ }
2682
+ }
2683
+
2684
+ /**
2685
+ * Represents a `Mod` that adjusts the size of `HitObject`s during their fade in animation.
2686
+ */
2687
+ class ModObjectScaleTween extends Mod {
2688
+ constructor() {
2689
+ super();
2690
+ /**
2691
+ * The final size multiplier applied to all [HitObject]s.
2692
+ */
2693
+ this.endScale = 1;
2694
+ this.incompatibleMods.add(ModObjectScaleTween).add(ModTraceable);
2695
+ }
2696
+ copySettings(mod) {
2697
+ var _a;
2698
+ super.copySettings(mod);
2699
+ const { settings } = mod;
2700
+ this.startScale.value =
2701
+ (_a = settings === null || settings === void 0 ? void 0 : settings.startScale) !== null && _a !== void 0 ? _a : this.startScale.value;
2702
+ }
2703
+ serializeSettings() {
2704
+ return { startScale: this.startScale.value };
2705
+ }
2706
+ }
2707
+
2708
+ /**
2709
+ * Represents the Deflate mod.
2710
+ */
2711
+ class ModDeflate extends ModObjectScaleTween {
2712
+ constructor() {
2713
+ super(...arguments);
2714
+ this.name = "Deflate";
2715
+ this.acronym = "DF";
2716
+ this.startScale = new DecimalModSetting("Start scale", "The initial size multiplier applied to all hit objects.", 2, 1, 25, 0.1, 1);
2717
+ }
2718
+ get droidRanked() {
2719
+ return false;
2720
+ }
2721
+ get isDroidRelevant() {
2722
+ return true;
2723
+ }
2724
+ calculateDroidScoreMultiplier() {
2725
+ return 1;
2726
+ }
2727
+ get osuRanked() {
2728
+ return false;
2729
+ }
2730
+ get isOsuRelevant() {
2731
+ return true;
2732
+ }
2733
+ get osuScoreMultiplier() {
2734
+ return 1;
2735
+ }
2736
+ }
2737
+
2738
+ /**
2739
+ * Represents a circle in a beatmap.
2740
+ *
2741
+ * All we need from circles is their position. All positions
2742
+ * stored in the objects are in playfield coordinates (512*384
2743
+ * rectangle).
2744
+ */
2745
+ class Circle extends HitObject {
2746
+ constructor(values) {
2747
+ super(values);
2748
+ }
2749
+ toString() {
2750
+ return `Position: [${this._position.x}, ${this._position.y}]`;
2751
+ }
2752
+ }
2753
+
2754
+ /**
2755
+ * Represents the Replay V6 mod.
2756
+ *
2757
+ * Some behavior of beatmap parsing was changed in replay version 7. More specifically, object stacking
2758
+ * behavior now matches osu!stable and osu!lazer.
2759
+ *
2760
+ * This `Mod` is meant to reapply the stacking behavior prior to replay version 7 to a `Beatmap` that
2761
+ * was played in replays recorded in version 6 and older for replayability and difficulty calculation.
2762
+ */
2763
+ class ModReplayV6 extends Mod {
2764
+ constructor() {
2765
+ super(...arguments);
2766
+ this.name = "Replay V6";
2767
+ this.acronym = "RV6";
2768
+ this.userPlayable = false;
2769
+ this.droidRanked = false;
2770
+ this.facilitateAdjustment = true;
2771
+ }
2772
+ get isDroidRelevant() {
2773
+ return true;
2774
+ }
2775
+ calculateDroidScoreMultiplier() {
2776
+ return 1;
2777
+ }
2778
+ applyToBeatmap(beatmap) {
2779
+ const { objects } = beatmap.hitObjects;
2780
+ if (objects.length === 0) {
2781
+ return;
2782
+ }
2783
+ // Reset stacking
2784
+ objects.forEach((h) => {
2785
+ h.stackHeight = 0;
2786
+ });
2787
+ for (let i = 0; i < objects.length - 1; ++i) {
2788
+ const current = objects[i];
2789
+ const next = objects[i + 1];
2790
+ this.revertObjectScale(current, beatmap.difficulty);
2791
+ this.revertObjectScale(next, beatmap.difficulty);
2792
+ const convertedScale = CircleSizeCalculator.standardScaleToOldDroidScale(objects[0].scale);
2793
+ if (current instanceof Circle &&
2794
+ next.startTime - current.startTime <
2795
+ 2000 * beatmap.general.stackLeniency &&
2796
+ next.position.getDistance(current.position) <
2797
+ Math.sqrt(convertedScale)) {
2798
+ next.stackHeight = current.stackHeight + 1;
2799
+ }
2800
+ }
2301
2801
  }
2302
- toString() {
2303
- 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}`;
2802
+ revertObjectScale(hitObject, difficulty) {
2803
+ const droidScale = CircleSizeCalculator.droidCSToOldDroidScale(difficulty.cs);
2804
+ const radius = CircleSizeCalculator.oldDroidScaleToStandardRadius(droidScale);
2805
+ const standardCS = CircleSizeCalculator.standardRadiusToStandardCS(radius, true);
2806
+ hitObject.scale = CircleSizeCalculator.standardCSToStandardScale(standardCS, true);
2807
+ hitObject.stackOffsetMultiplier = 4;
2304
2808
  }
2305
2809
  }
2306
- Slider.baseNormalSlideSample = new BankHitSampleInfo("sliderslide");
2307
- Slider.baseWhistleSlideSample = new BankHitSampleInfo("sliderwhistle");
2308
- Slider.baseTickSample = new BankHitSampleInfo("slidertick");
2309
- Slider.legacyLastTickOffset = 36;
2310
2810
 
2311
2811
  /**
2312
2812
  * Represents a `Mod` specific setting that is constrained to a number of values.
@@ -2314,6 +2814,23 @@ Slider.legacyLastTickOffset = 36;
2314
2814
  * The value can be `null`, which is treated as a special case.
2315
2815
  */
2316
2816
  class NullableDecimalModSetting extends RangeConstrainedModSetting {
2817
+ /**
2818
+ * The number of decimal places to round the value to.
2819
+ *
2820
+ * When set to `null`, the value will not be rounded.
2821
+ */
2822
+ get precision() {
2823
+ return this._precision;
2824
+ }
2825
+ set precision(value) {
2826
+ if (value !== null && value < 0) {
2827
+ throw new RangeError(`The precision (${value}) must be greater than or equal to 0.`);
2828
+ }
2829
+ this._precision = value;
2830
+ if (value !== null) {
2831
+ this.value = this.processValue(this.value);
2832
+ }
2833
+ }
2317
2834
  constructor(name, description, defaultValue, min = -Number.MAX_VALUE, max = Number.MAX_VALUE, step = 0, precision = null) {
2318
2835
  super(name, description, defaultValue, min, max, step);
2319
2836
  this.displayFormatter = (v) => {
@@ -2335,7 +2852,7 @@ class NullableDecimalModSetting extends RangeConstrainedModSetting {
2335
2852
  (defaultValue < min || defaultValue > max)) {
2336
2853
  throw new RangeError(`The default value (${defaultValue}) must be between the minimum (${min}) and maximum (${max}) values.`);
2337
2854
  }
2338
- this.precision = precision;
2855
+ this._precision = precision;
2339
2856
  }
2340
2857
  processValue(value) {
2341
2858
  if (value === null) {
@@ -2426,18 +2943,19 @@ class ModDifficultyAdjust extends Mod {
2426
2943
  difficulty.ar = (_b = this.ar.value) !== null && _b !== void 0 ? _b : difficulty.ar;
2427
2944
  difficulty.od = (_c = this.od.value) !== null && _c !== void 0 ? _c : difficulty.od;
2428
2945
  difficulty.hp = (_d = this.hp.value) !== null && _d !== void 0 ? _d : difficulty.hp;
2429
- // Special case for force AR, where the AR value is kept constant with respect to game time.
2430
- // This makes the player perceive the AR as is under all speed multipliers.
2431
- if (this.ar.value !== null) {
2946
+ // Special case for force AR in replay version 6 and older, where the AR value is kept constant with
2947
+ // respect to game time. This makes the player perceive the AR as is under all speed multipliers.
2948
+ if (this.ar.value !== null && mods.has(ModReplayV6)) {
2432
2949
  const preempt = BeatmapDifficulty.difficultyRange(this.ar.value, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
2433
2950
  const trackRate = this.calculateTrackRate(mods.values());
2434
2951
  difficulty.ar = BeatmapDifficulty.inverseDifficultyRange(preempt * trackRate, HitObject.preemptMax, HitObject.preemptMid, HitObject.preemptMin);
2435
2952
  }
2436
2953
  }
2437
2954
  applyToHitObjectWithMods(_, hitObject, mods) {
2438
- // Special case for force AR, where the AR value is kept constant with respect to game time.
2439
- // This makes the player perceive the fade in animation as is under all speed multipliers.
2440
- if (this.ar.value === null) {
2955
+ // Special case for force AR in replay version 6 and older, where the AR value is kept constant with
2956
+ // respect to game time. This makes the player perceive the fade in animation as is under all speed
2957
+ // multipliers.
2958
+ if (this.ar.value === null || !mods.has(ModReplayV6)) {
2441
2959
  return;
2442
2960
  }
2443
2961
  this.applyFadeAdjustment(hitObject, mods);
@@ -2485,14 +3003,6 @@ class ModDifficultyAdjust extends Mod {
2485
3003
  }
2486
3004
  return rate;
2487
3005
  }
2488
- equals(other) {
2489
- return (super.equals(other) &&
2490
- other instanceof ModDifficultyAdjust &&
2491
- this.cs.value === other.cs.value &&
2492
- this.ar.value === other.ar.value &&
2493
- this.od.value === other.od.value &&
2494
- this.hp.value === other.hp.value);
2495
- }
2496
3006
  toString() {
2497
3007
  if (!this.isRelevant) {
2498
3008
  return super.toString();
@@ -2595,22 +3105,6 @@ class ModDoubleTime extends ModRateAdjust {
2595
3105
  }
2596
3106
  }
2597
3107
 
2598
- /**
2599
- * Represents a circle in a beatmap.
2600
- *
2601
- * All we need from circles is their position. All positions
2602
- * stored in the objects are in playfield coordinates (512*384
2603
- * rectangle).
2604
- */
2605
- class Circle extends HitObject {
2606
- constructor(values) {
2607
- super(values);
2608
- }
2609
- toString() {
2610
- return `Position: [${this._position.x}, ${this._position.y}]`;
2611
- }
2612
- }
2613
-
2614
3108
  var _a$1;
2615
3109
  /**
2616
3110
  * Represents the osu! playfield.
@@ -2648,10 +3142,10 @@ class Spinner extends HitObject {
2648
3142
  this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Spinner.baseSpinnerSpinSample)))));
2649
3143
  this.auxiliarySamples.push(new SequenceHitSampleInfo(samplePoints.map((s) => new TimedHitSampleInfo(s.time, s.applyTo(Spinner.baseSpinnerBonusSample)))));
2650
3144
  }
2651
- getStackedPosition() {
3145
+ get stackedPosition() {
2652
3146
  return this.position;
2653
3147
  }
2654
- getStackedEndPosition() {
3148
+ get stackedEndPosition() {
2655
3149
  return this.position;
2656
3150
  }
2657
3151
  createHitWindow() {
@@ -3867,11 +4361,6 @@ class ModMirror extends Mod {
3867
4361
  serializeSettings() {
3868
4362
  return { flippedAxes: this.flippedAxes.value - 1 };
3869
4363
  }
3870
- equals(other) {
3871
- return (super.equals(other) &&
3872
- other instanceof ModMirror &&
3873
- other.flippedAxes.value === this.flippedAxes.value);
3874
- }
3875
4364
  toString() {
3876
4365
  const settings = [];
3877
4366
  if (this.flippedAxes.value === exports.Axes.x ||
@@ -3886,62 +4375,6 @@ class ModMirror extends Mod {
3886
4375
  }
3887
4376
  }
3888
4377
 
3889
- /**
3890
- * Represents the Replay V6 mod.
3891
- *
3892
- * Some behavior of beatmap parsing was changed in replay version 7. More specifically, object stacking
3893
- * behavior now matches osu!stable and osu!lazer.
3894
- *
3895
- * This `Mod` is meant to reapply the stacking behavior prior to replay version 7 to a `Beatmap` that
3896
- * was played in replays recorded in version 6 and older for replayability and difficulty calculation.
3897
- */
3898
- class ModReplayV6 extends Mod {
3899
- constructor() {
3900
- super(...arguments);
3901
- this.name = "Replay V6";
3902
- this.acronym = "RV6";
3903
- this.userPlayable = false;
3904
- this.droidRanked = false;
3905
- this.facilitateAdjustment = true;
3906
- }
3907
- get isDroidRelevant() {
3908
- return true;
3909
- }
3910
- calculateDroidScoreMultiplier() {
3911
- return 1;
3912
- }
3913
- applyToBeatmap(beatmap) {
3914
- const { objects } = beatmap.hitObjects;
3915
- if (objects.length === 0) {
3916
- return;
3917
- }
3918
- // Reset stacking
3919
- objects.forEach((h) => {
3920
- h.stackHeight = 0;
3921
- });
3922
- for (let i = 0; i < objects.length - 1; ++i) {
3923
- const current = objects[i];
3924
- const next = objects[i + 1];
3925
- this.revertObjectScale(current, beatmap.difficulty);
3926
- this.revertObjectScale(next, beatmap.difficulty);
3927
- const convertedScale = CircleSizeCalculator.standardScaleToOldDroidScale(objects[0].scale);
3928
- if (current instanceof Circle &&
3929
- next.startTime - current.startTime <
3930
- 2000 * beatmap.general.stackLeniency &&
3931
- next.position.getDistance(current.position) <
3932
- Math.sqrt(convertedScale)) {
3933
- next.stackHeight = current.stackHeight + 1;
3934
- }
3935
- }
3936
- }
3937
- revertObjectScale(hitObject, difficulty) {
3938
- const droidScale = CircleSizeCalculator.droidCSToOldDroidScale(difficulty.cs);
3939
- const radius = CircleSizeCalculator.oldDroidScaleToStandardRadius(droidScale);
3940
- const standardCS = CircleSizeCalculator.standardRadiusToStandardCS(radius, true);
3941
- hitObject.scale = CircleSizeCalculator.standardCSToStandardScale(standardCS, true);
3942
- }
3943
- }
3944
-
3945
4378
  /**
3946
4379
  * Represents the HardRock mod.
3947
4380
  */
@@ -4068,11 +4501,6 @@ class ModFlashlight extends Mod {
4068
4501
  serializeSettings() {
4069
4502
  return { areaFollowDelay: this.followDelay.value };
4070
4503
  }
4071
- equals(other) {
4072
- return (super.equals(other) &&
4073
- other instanceof ModFlashlight &&
4074
- other.followDelay.value === this.followDelay.value);
4075
- }
4076
4504
  toString() {
4077
4505
  if (this.followDelay.value === ModFlashlight.defaultFollowDelay) {
4078
4506
  return super.toString();
@@ -4086,22 +4514,26 @@ class ModFlashlight extends Mod {
4086
4514
  ModFlashlight.defaultFollowDelay = 0.12;
4087
4515
 
4088
4516
  /**
4089
- * Represents the Traceable mod.
4517
+ * Represents the Grow mod.
4090
4518
  */
4091
- class ModTraceable extends Mod {
4519
+ class ModGrow extends ModObjectScaleTween {
4092
4520
  constructor() {
4093
- super();
4094
- this.acronym = "TC";
4095
- this.name = "Traceable";
4096
- this.droidRanked = false;
4097
- this.osuRanked = false;
4098
- this.incompatibleMods.add(ModHidden);
4521
+ super(...arguments);
4522
+ this.name = "Grow";
4523
+ this.acronym = "GR";
4524
+ this.startScale = new DecimalModSetting("Start scale", "The initial size multiplier applied to all hit objects.", 0.5, 0, 0.99, 0.01, 2);
4525
+ }
4526
+ get droidRanked() {
4527
+ return false;
4099
4528
  }
4100
4529
  get isDroidRelevant() {
4101
4530
  return true;
4102
4531
  }
4103
4532
  calculateDroidScoreMultiplier() {
4104
- return 1.06;
4533
+ return 1;
4534
+ }
4535
+ get osuRanked() {
4536
+ return false;
4105
4537
  }
4106
4538
  get isOsuRelevant() {
4107
4539
  return true;
@@ -4112,82 +4544,92 @@ class ModTraceable extends Mod {
4112
4544
  }
4113
4545
 
4114
4546
  /**
4115
- * Represents a `Mod` specific setting that is constrained to a boolean value.
4547
+ * Represents a `Mod` specific setting that is constrained to a range of integer values.
4116
4548
  */
4117
- class BooleanModSetting extends ModSetting {
4118
- constructor() {
4119
- super(...arguments);
4120
- this.displayFormatter = (v) => v ? "Enabled" : "Disabled";
4549
+ class IntegerModSetting extends NumberModSetting {
4550
+ constructor(name, description, defaultValue, min = -2147483648, max = 2147483647) {
4551
+ super(name, description, defaultValue, min, max, 1);
4121
4552
  }
4122
4553
  }
4123
4554
 
4124
4555
  /**
4125
- * Represents the Hidden mod.
4556
+ * Represents the Muted mod.
4126
4557
  */
4127
- class ModHidden extends Mod {
4128
- constructor() {
4129
- super();
4130
- this.acronym = "HD";
4131
- this.name = "Hidden";
4132
- this.droidRanked = true;
4133
- this.osuRanked = true;
4134
- this.bitwise = 1 << 3;
4135
- /**
4136
- * Whether to only fade approach circles.
4137
- *
4138
- * The main object body will not fade when enabled.
4139
- */
4140
- this.onlyFadeApproachCircles = new BooleanModSetting("Only fade approach circles", "The main object body will not fade when enabled.", false);
4141
- this.incompatibleMods.add(ModTraceable);
4558
+ class ModMuted extends Mod {
4559
+ get droidRanked() {
4560
+ return false;
4142
4561
  }
4143
4562
  get isDroidRelevant() {
4144
4563
  return true;
4145
4564
  }
4146
4565
  calculateDroidScoreMultiplier() {
4147
- return 1.06;
4566
+ return 1;
4567
+ }
4568
+ get osuRanked() {
4569
+ return true;
4148
4570
  }
4149
4571
  get isOsuRelevant() {
4150
4572
  return true;
4151
4573
  }
4152
4574
  get osuScoreMultiplier() {
4153
- return 1.06;
4575
+ return 1;
4576
+ }
4577
+ constructor() {
4578
+ super();
4579
+ this.name = "Muted";
4580
+ this.acronym = "MU";
4581
+ /**
4582
+ * Increase volume as combo builds.
4583
+ */
4584
+ this.inverseMuting = new BooleanModSetting("Start muted", "Increase volume as combo builds.", false);
4585
+ /**
4586
+ * Add a metronome beat to help the player keep track of the rhythm.
4587
+ */
4588
+ this.enableMetronome = new BooleanModSetting("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.", true);
4589
+ /**
4590
+ * The combo count at which point the track reaches its final volume.
4591
+ */
4592
+ this.muteComboCount = new IntegerModSetting("Final volume at combo", "The combo count at which point the track reaches its final volume.", 100, 0, 500);
4593
+ /**
4594
+ * Hit sounds are also muted alongside the track.
4595
+ */
4596
+ this.affectsHitSounds = new BooleanModSetting("Mute hit sounds", "Hit sounds are also muted alongside the track.", true);
4597
+ this.inverseMuting.bindValueChanged((_, newValue) => {
4598
+ this.muteComboCount.min = newValue ? 1 : 0;
4599
+ }, true);
4600
+ }
4601
+ /**
4602
+ * Obtains the volume at a given combo.
4603
+ *
4604
+ * @param combo The combo.
4605
+ * @return The volume at `combo`, where 0 is muted and 1 is full volume.
4606
+ */
4607
+ volumeAt(combo) {
4608
+ const volume = MathUtils.clamp(combo / Math.max(1, this.muteComboCount.value), 0, 1);
4609
+ return this.inverseMuting.value ? volume : 1 - volume;
4154
4610
  }
4155
4611
  copySettings(mod) {
4156
- var _a, _b;
4612
+ var _a, _b, _c, _d;
4157
4613
  super.copySettings(mod);
4158
- this.onlyFadeApproachCircles.value =
4159
- (_b = (_a = mod.settings) === null || _a === void 0 ? void 0 : _a.onlyFadeApproachCircles) !== null && _b !== void 0 ? _b : this.onlyFadeApproachCircles.value;
4160
- }
4161
- applyToBeatmap(beatmap) {
4162
- const applyFadeInAdjustment = (hitObject) => {
4163
- hitObject.timeFadeIn =
4164
- hitObject.timePreempt * ModHidden.fadeInDurationMultiplier;
4165
- if (hitObject instanceof Slider) {
4166
- hitObject.nestedHitObjects.forEach(applyFadeInAdjustment);
4167
- }
4168
- };
4169
- beatmap.hitObjects.objects.forEach(applyFadeInAdjustment);
4614
+ const { settings } = mod;
4615
+ this.inverseMuting.value =
4616
+ (_a = settings === null || settings === void 0 ? void 0 : settings.inverseMuting) !== null && _a !== void 0 ? _a : this.inverseMuting.value;
4617
+ this.enableMetronome.value =
4618
+ (_b = settings === null || settings === void 0 ? void 0 : settings.enableMetronome) !== null && _b !== void 0 ? _b : this.enableMetronome.value;
4619
+ this.muteComboCount.value =
4620
+ (_c = settings === null || settings === void 0 ? void 0 : settings.muteComboCount) !== null && _c !== void 0 ? _c : this.muteComboCount.value;
4621
+ this.affectsHitSounds.value =
4622
+ (_d = settings === null || settings === void 0 ? void 0 : settings.affectsHitSounds) !== null && _d !== void 0 ? _d : this.affectsHitSounds.value;
4170
4623
  }
4171
4624
  serializeSettings() {
4172
- return this.onlyFadeApproachCircles.value
4173
- ? { onlyFadeApproachCircles: this.onlyFadeApproachCircles.value }
4174
- : null;
4175
- }
4176
- equals(other) {
4177
- return (super.equals(other) &&
4178
- other instanceof ModHidden &&
4179
- other.onlyFadeApproachCircles.value ===
4180
- this.onlyFadeApproachCircles.value);
4181
- }
4182
- toString() {
4183
- if (!this.onlyFadeApproachCircles.value) {
4184
- return super.toString();
4185
- }
4186
- return `${super.toString()} (approach circles only)`;
4625
+ return {
4626
+ inverseMuting: this.inverseMuting.value,
4627
+ enableMetronome: this.enableMetronome.value,
4628
+ muteComboCount: this.muteComboCount.value,
4629
+ affectsHitSounds: this.affectsHitSounds.value,
4630
+ };
4187
4631
  }
4188
4632
  }
4189
- ModHidden.fadeInDurationMultiplier = 0.4;
4190
- ModHidden.fadeOutDurationMultiplier = 0.3;
4191
4633
 
4192
4634
  /**
4193
4635
  * Represents the SuddenDeath mod.
@@ -4596,12 +5038,6 @@ class ModRandom extends Mod {
4596
5038
  1 && positionInfos[i - 1].hitObject.isNewCombo;
4597
5039
  return previousObjectStartedCombo && this.random.nextDouble() < 0.6;
4598
5040
  }
4599
- equals(other) {
4600
- return (super.equals(other) &&
4601
- other instanceof ModRandom &&
4602
- other.seed.value === this.seed.value &&
4603
- other.angleSharpness.value === this.angleSharpness.value);
4604
- }
4605
5041
  toString() {
4606
5042
  const settings = [];
4607
5043
  if (this.seed.value !== null) {
@@ -4785,51 +5221,6 @@ class ModTouchDevice extends Mod {
4785
5221
  }
4786
5222
  }
4787
5223
 
4788
- /**
4789
- * Types of easing.
4790
- *
4791
- * See {@link http://easings.net/ this} page for more samples.
4792
- */
4793
- exports.Easing = void 0;
4794
- (function (Easing) {
4795
- Easing[Easing["none"] = 0] = "none";
4796
- Easing[Easing["out"] = 1] = "out";
4797
- Easing[Easing["in"] = 2] = "in";
4798
- Easing[Easing["inQuad"] = 3] = "inQuad";
4799
- Easing[Easing["outQuad"] = 4] = "outQuad";
4800
- Easing[Easing["inOutQuad"] = 5] = "inOutQuad";
4801
- Easing[Easing["inCubic"] = 6] = "inCubic";
4802
- Easing[Easing["outCubic"] = 7] = "outCubic";
4803
- Easing[Easing["inOutCubic"] = 8] = "inOutCubic";
4804
- Easing[Easing["inQuart"] = 9] = "inQuart";
4805
- Easing[Easing["outQuart"] = 10] = "outQuart";
4806
- Easing[Easing["inOutQuart"] = 11] = "inOutQuart";
4807
- Easing[Easing["inQuint"] = 12] = "inQuint";
4808
- Easing[Easing["outQuint"] = 13] = "outQuint";
4809
- Easing[Easing["inOutQuint"] = 14] = "inOutQuint";
4810
- Easing[Easing["inSine"] = 15] = "inSine";
4811
- Easing[Easing["outSine"] = 16] = "outSine";
4812
- Easing[Easing["inOutSine"] = 17] = "inOutSine";
4813
- Easing[Easing["inExpo"] = 18] = "inExpo";
4814
- Easing[Easing["outExpo"] = 19] = "outExpo";
4815
- Easing[Easing["inOutExpo"] = 20] = "inOutExpo";
4816
- Easing[Easing["inCirc"] = 21] = "inCirc";
4817
- Easing[Easing["outCirc"] = 22] = "outCirc";
4818
- Easing[Easing["inOutCirc"] = 23] = "inOutCirc";
4819
- Easing[Easing["inElastic"] = 24] = "inElastic";
4820
- Easing[Easing["outElastic"] = 25] = "outElastic";
4821
- Easing[Easing["outElasticHalf"] = 26] = "outElasticHalf";
4822
- Easing[Easing["outElasticQuarter"] = 27] = "outElasticQuarter";
4823
- Easing[Easing["inOutElastic"] = 28] = "inOutElastic";
4824
- Easing[Easing["inBack"] = 29] = "inBack";
4825
- Easing[Easing["outBack"] = 30] = "outBack";
4826
- Easing[Easing["inOutBack"] = 31] = "inOutBack";
4827
- Easing[Easing["inBounce"] = 32] = "inBounce";
4828
- Easing[Easing["outBounce"] = 33] = "outBounce";
4829
- Easing[Easing["inOutBounce"] = 34] = "inOutBounce";
4830
- Easing[Easing["outPow10"] = 35] = "outPow10";
4831
- })(exports.Easing || (exports.Easing = {}));
4832
-
4833
5224
  /**
4834
5225
  * Holds interpolation methods for numbers and vectors.
4835
5226
  */
@@ -5041,12 +5432,6 @@ class ModTimeRamp extends Mod {
5041
5432
  finalRate: this.finalRate.value,
5042
5433
  };
5043
5434
  }
5044
- equals(other) {
5045
- return (super.equals(other) &&
5046
- other instanceof ModTimeRamp &&
5047
- other.initialRate.value === this.initialRate.value &&
5048
- other.finalRate.value === this.finalRate.value);
5049
- }
5050
5435
  toString() {
5051
5436
  return `${super.toString()} (${this.initialRate.toDisplayString()}x - ${this.finalRate.toDisplayString()}x)`;
5052
5437
  }
@@ -5344,6 +5729,8 @@ ModUtil.allMods = (() => {
5344
5729
  ModEasy,
5345
5730
  ModNoFail,
5346
5731
  ModHidden,
5732
+ ModApproachDifferent,
5733
+ ModFreezeFrame,
5347
5734
  ModTraceable,
5348
5735
  ModDoubleTime,
5349
5736
  ModNightCore,
@@ -5353,6 +5740,8 @@ ModUtil.allMods = (() => {
5353
5740
  ModWindUp,
5354
5741
  ModHardRock,
5355
5742
  ModMirror,
5743
+ ModGrow,
5744
+ ModDeflate,
5356
5745
  ModDifficultyAdjust,
5357
5746
  ModFlashlight,
5358
5747
  ModSuddenDeath,
@@ -5360,6 +5749,7 @@ ModUtil.allMods = (() => {
5360
5749
  ModPrecise,
5361
5750
  ModRandom,
5362
5751
  ModReallyEasy,
5752
+ ModMuted,
5363
5753
  ModSynesthesia,
5364
5754
  ModReplayV6,
5365
5755
  ModScoreV2,
@@ -5387,15 +5777,17 @@ class ModMap extends Map {
5387
5777
  return this.size === 0;
5388
5778
  }
5389
5779
  constructor(iterable) {
5780
+ // We are not passing `iterable` here to preserve mod-specific settings.
5781
+ super();
5390
5782
  if (Array.isArray(iterable)) {
5391
5783
  for (const [key, value] of iterable) {
5392
5784
  // Ensure the mod type corresponds to the mod instance.
5393
5785
  if (key !== value.constructor) {
5394
5786
  throw new TypeError(`Key ${key.name} does not match value ${value.constructor.name}`);
5395
5787
  }
5788
+ this.set(value);
5396
5789
  }
5397
5790
  }
5398
- super(iterable);
5399
5791
  }
5400
5792
  has(keyOrValue) {
5401
5793
  const key = keyOrValue instanceof Mod
@@ -5586,6 +5978,9 @@ class BeatmapHitObjects {
5586
5978
  this._sliders = 0;
5587
5979
  this._spinners = 0;
5588
5980
  }
5981
+ [Symbol.iterator]() {
5982
+ return this.objects[Symbol.iterator]();
5983
+ }
5589
5984
  /**
5590
5985
  * Finds the insertion index of a hitobject in a given time.
5591
5986
  *
@@ -5936,7 +6331,7 @@ class PlayableBeatmap {
5936
6331
  this.hitObjects = baseBeatmap.hitObjects;
5937
6332
  this.maxCombo = baseBeatmap.maxCombo;
5938
6333
  this.mods = mods;
5939
- this.speedMultiplier = ModUtil.calculateRateWithMods(this.mods.values());
6334
+ this.speedMultiplier = ModUtil.calculateRateWithMods(this.mods.values(), Number.POSITIVE_INFINITY);
5940
6335
  }
5941
6336
  }
5942
6337
 
@@ -6151,6 +6546,9 @@ class ControlPointManager {
6151
6546
  // l will be the first control point with time > this._points[l].time, but we want the one before it
6152
6547
  return this._points[l - 1];
6153
6548
  }
6549
+ [Symbol.iterator]() {
6550
+ return this._points[Symbol.iterator]();
6551
+ }
6154
6552
  /**
6155
6553
  * Finds the insertion index of a control point in a given time.
6156
6554
  *
@@ -8844,8 +9242,8 @@ class BeatmapDecoder extends Decoder {
8844
9242
  * @param parseStoryboard Whether to parse the beatmap's storyboard.
8845
9243
  */
8846
9244
  decode(str, mode = exports.Modes.osu, parseStoryboard = true) {
8847
- this.finalResult.mode = mode;
8848
9245
  super.decode(str);
9246
+ this.finalResult.mode = mode;
8849
9247
  if (parseStoryboard) {
8850
9248
  const eventsDecoder = (this.decoders[BeatmapSection.events]);
8851
9249
  if (eventsDecoder.storyboardLines.length > 0) {
@@ -10949,15 +11347,6 @@ ErrorFunction.ervInvImpGd = [
10949
11347
  0.3999688121938621e-6, 0.1618092908879045e-8, 0.2315586083102596e-11,
10950
11348
  ];
10951
11349
 
10952
- /**
10953
- * Represents a `Mod` specific setting that is constrained to a range of integer values.
10954
- */
10955
- class IntegerModSetting extends NumberModSetting {
10956
- constructor(name, description, defaultValue, min = -2147483648, max = 2147483647) {
10957
- super(name, description, defaultValue, min, max, 1);
10958
- }
10959
- }
10960
-
10961
11350
  /**
10962
11351
  * Ranking status of a beatmap.
10963
11352
  */
@@ -11475,20 +11864,26 @@ exports.Interpolation = Interpolation;
11475
11864
  exports.MapInfo = MapInfo;
11476
11865
  exports.MathUtils = MathUtils;
11477
11866
  exports.Mod = Mod;
11867
+ exports.ModApproachDifferent = ModApproachDifferent;
11478
11868
  exports.ModAuto = ModAuto;
11479
11869
  exports.ModAutopilot = ModAutopilot;
11480
11870
  exports.ModCustomSpeed = ModCustomSpeed;
11871
+ exports.ModDeflate = ModDeflate;
11481
11872
  exports.ModDifficultyAdjust = ModDifficultyAdjust;
11482
11873
  exports.ModDoubleTime = ModDoubleTime;
11483
11874
  exports.ModEasy = ModEasy;
11484
11875
  exports.ModFlashlight = ModFlashlight;
11876
+ exports.ModFreezeFrame = ModFreezeFrame;
11877
+ exports.ModGrow = ModGrow;
11485
11878
  exports.ModHalfTime = ModHalfTime;
11486
11879
  exports.ModHardRock = ModHardRock;
11487
11880
  exports.ModHidden = ModHidden;
11488
11881
  exports.ModMap = ModMap;
11489
11882
  exports.ModMirror = ModMirror;
11883
+ exports.ModMuted = ModMuted;
11490
11884
  exports.ModNightCore = ModNightCore;
11491
11885
  exports.ModNoFail = ModNoFail;
11886
+ exports.ModObjectScaleTween = ModObjectScaleTween;
11492
11887
  exports.ModOldNightCore = ModOldNightCore;
11493
11888
  exports.ModPerfect = ModPerfect;
11494
11889
  exports.ModPrecise = ModPrecise;
@@ -11510,6 +11905,7 @@ exports.ModWindDown = ModWindDown;
11510
11905
  exports.ModWindUp = ModWindUp;
11511
11906
  exports.NormalDistribution = NormalDistribution;
11512
11907
  exports.NullableDecimalModSetting = NullableDecimalModSetting;
11908
+ exports.NullableIntegerModSetting = NullableIntegerModSetting;
11513
11909
  exports.NumberModSetting = NumberModSetting;
11514
11910
  exports.OsuAPIRequestBuilder = OsuAPIRequestBuilder;
11515
11911
  exports.OsuHitWindow = OsuHitWindow;