@rian8337/osu-difficulty-calculator 4.0.0-beta.25 → 4.0.0-beta.27

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
@@ -32,6 +32,12 @@ AimEvaluator.velocityChangeMultiplier = 0.75;
32
32
  * The base of a difficulty calculator.
33
33
  */
34
34
  class DifficultyCalculator {
35
+ /**
36
+ * The difficulty objects of the beatmap.
37
+ */
38
+ get objects() {
39
+ return this._objects;
40
+ }
35
41
  /**
36
42
  * The total star rating of the beatmap.
37
43
  */
@@ -41,13 +47,13 @@ class DifficultyCalculator {
41
47
  /**
42
48
  * Constructs a new instance of the calculator.
43
49
  *
44
- * @param beatmap The beatmap to calculate. This beatmap will be deep-cloned to prevent reference changes.
50
+ * @param beatmap The beatmap to calculate.
45
51
  */
46
52
  constructor(beatmap) {
47
53
  /**
48
54
  * The difficulty objects of the beatmap.
49
55
  */
50
- this.objects = [];
56
+ this._objects = [];
51
57
  /**
52
58
  * The modifications applied.
53
59
  */
@@ -62,13 +68,6 @@ class DifficultyCalculator {
62
68
  flashlight: [],
63
69
  };
64
70
  this.beatmap = beatmap;
65
- this.difficultyStatistics = {
66
- circleSize: beatmap.difficulty.cs,
67
- approachRate: beatmap.difficulty.ar,
68
- overallDifficulty: beatmap.difficulty.od,
69
- healthDrain: beatmap.difficulty.hp,
70
- overallSpeedMultiplier: 1,
71
- };
72
71
  }
73
72
  /**
74
73
  * Calculates the star rating of the specified beatmap.
@@ -93,9 +92,9 @@ class DifficultyCalculator {
93
92
  mods: this.mods,
94
93
  customSpeedMultiplier: options === null || options === void 0 ? void 0 : options.customSpeedMultiplier,
95
94
  });
96
- this.difficultyStatistics = Object.seal(this.computeDifficultyStatistics(options));
97
- this.populateDifficultyAttributes();
98
- this.objects.push(...this.generateDifficultyHitObjects(playableBeatmap));
95
+ const clockRate = this.calculateClockRate(options);
96
+ this.populateDifficultyAttributes(playableBeatmap, clockRate);
97
+ this._objects = this.generateDifficultyHitObjects(playableBeatmap, clockRate);
99
98
  this.calculateAll();
100
99
  return this;
101
100
  }
@@ -112,20 +111,40 @@ class DifficultyCalculator {
112
111
  }
113
112
  }
114
113
  }
114
+ /**
115
+ * Obtains the clock rate of the beatmap.
116
+ *
117
+ * @param options The options to obtain the clock rate with.
118
+ * @returns The clock rate of the beatmap.
119
+ */
120
+ calculateClockRate(options) {
121
+ var _a, _b;
122
+ return (osuBase.ModUtil.calculateRateWithMods((_a = options === null || options === void 0 ? void 0 : options.mods) !== null && _a !== void 0 ? _a : []) *
123
+ ((_b = options === null || options === void 0 ? void 0 : options.customSpeedMultiplier) !== null && _b !== void 0 ? _b : 1));
124
+ }
115
125
  /**
116
126
  * Populates the stored difficulty attributes with necessary data.
127
+ *
128
+ * @param beatmap The beatmap to populate the attributes with.
129
+ * @param clockRate The clock rate of the beatmap.
117
130
  */
118
- populateDifficultyAttributes() {
119
- this.attributes.approachRate = this.difficultyStatistics.approachRate;
131
+ populateDifficultyAttributes(beatmap, clockRate) {
120
132
  this.attributes.hitCircleCount = this.beatmap.hitObjects.circles;
121
133
  this.attributes.maxCombo = this.beatmap.maxCombo;
122
134
  this.attributes.mods = this.mods.slice();
123
- this.attributes.overallDifficulty =
124
- this.difficultyStatistics.overallDifficulty;
125
135
  this.attributes.sliderCount = this.beatmap.hitObjects.sliders;
126
136
  this.attributes.spinnerCount = this.beatmap.hitObjects.spinners;
127
- this.attributes.clockRate =
128
- this.difficultyStatistics.overallSpeedMultiplier;
137
+ this.attributes.clockRate = clockRate;
138
+ let greatWindow;
139
+ switch (this.mode) {
140
+ case osuBase.Modes.droid:
141
+ greatWindow = new osuBase.DroidHitWindow(beatmap.difficulty.od).hitWindowFor300(this.mods.some((m) => m instanceof osuBase.ModPrecise));
142
+ break;
143
+ case osuBase.Modes.osu:
144
+ greatWindow = new osuBase.OsuHitWindow(beatmap.difficulty.od).hitWindowFor300();
145
+ break;
146
+ }
147
+ this.attributes.overallDifficulty = osuBase.OsuHitWindow.hitWindow300ToOD(greatWindow / clockRate);
129
148
  }
130
149
  /**
131
150
  * Calculates the star rating value of a difficulty.
@@ -1057,7 +1076,7 @@ class DroidFlashlight extends DroidSkill {
1057
1076
  this.reducedSectionCount = 0;
1058
1077
  this.reducedSectionBaseline = 1;
1059
1078
  this.starsPerDouble = 1.06;
1060
- this.skillMultiplier = 0.052;
1079
+ this.skillMultiplier = 0.02;
1061
1080
  this.currentFlashlightStrain = 0;
1062
1081
  this.isHidden = mods.some((m) => m instanceof osuBase.ModHidden);
1063
1082
  this.withSliders = withSliders;
@@ -1086,64 +1105,42 @@ class DroidFlashlight extends DroidSkill {
1086
1105
  }
1087
1106
  }
1088
1107
  difficultyValue() {
1089
- return Math.pow(this.strainPeaks.reduce((a, v) => a + v, 0) * this.starsPerDouble, 0.8);
1108
+ return (this.strainPeaks.reduce((a, v) => a + v, 0) * this.starsPerDouble);
1090
1109
  }
1091
1110
  }
1092
1111
 
1093
- /**
1094
- * An evaluator for calculating rhythm skill.
1095
- *
1096
- * This class should be considered an "evaluating" class and not persisted.
1097
- */
1098
- class RhythmEvaluator {
1099
- }
1100
- RhythmEvaluator.rhythmMultiplier = 0.75;
1101
- RhythmEvaluator.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
1102
-
1103
1112
  class Island {
1104
- constructor(firstDelta, epsilon) {
1105
- this.deltas = [];
1106
- if (epsilon === undefined) {
1107
- this.deltaDifferenceEpsilon = firstDelta;
1108
- return;
1113
+ constructor(delta, deltaDifferenceEpsilon) {
1114
+ this.delta = Number.MAX_SAFE_INTEGER;
1115
+ this.deltaCount = 0;
1116
+ if (deltaDifferenceEpsilon === undefined) {
1117
+ this.deltaDifferenceEpsilon = delta;
1118
+ }
1119
+ else {
1120
+ this.deltaDifferenceEpsilon = deltaDifferenceEpsilon;
1121
+ this.addDelta(delta);
1109
1122
  }
1110
- this.deltaDifferenceEpsilon = epsilon;
1111
- this.addDelta(firstDelta);
1112
1123
  }
1113
1124
  addDelta(delta) {
1114
- // Convert to integer
1115
- delta = Math.trunc(delta);
1116
- const existingDelta = this.deltas.find((v) => Math.abs(v - delta) >= this.deltaDifferenceEpsilon);
1117
- this.deltas.push(existingDelta !== null && existingDelta !== void 0 ? existingDelta : delta);
1118
- }
1119
- get averageDelta() {
1120
- return this.deltas.length > 0
1121
- ? Math.max(this.deltas.reduce((a, b) => a + b) / this.deltas.length, DifficultyHitObject.minDeltaTime)
1122
- : 0;
1125
+ if (this.delta === Number.MAX_SAFE_INTEGER) {
1126
+ this.delta = Math.max(Math.trunc(delta), DifficultyHitObject.minDeltaTime);
1127
+ }
1128
+ ++this.deltaCount;
1123
1129
  }
1124
1130
  isSimilarPolarity(other) {
1125
- // Consider islands to be of similar polarity only if they're having the same
1126
- // average delta (we don't want to consider 3 singletaps similar to a triple)
1127
- return (Math.abs(this.averageDelta - other.averageDelta) <
1128
- this.deltaDifferenceEpsilon &&
1129
- this.deltas.length % 2 === other.deltas.length % 2);
1131
+ // TODO: consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
1132
+ // naively adding delta check here breaks _a lot_ of maps because of the flawed ratio calculation
1133
+ return this.deltaCount % 2 == other.deltaCount % 2;
1130
1134
  }
1131
1135
  equals(other) {
1132
- if (this.deltas.length !== other.deltas.length) {
1133
- return false;
1134
- }
1135
- for (let i = 0; i < this.deltas.length; ++i) {
1136
- if (this.deltas[i] !== other.deltas[i]) {
1137
- return false;
1138
- }
1139
- }
1140
- return true;
1136
+ return (Math.abs(this.delta - other.delta) < this.deltaDifferenceEpsilon &&
1137
+ this.deltaCount === other.deltaCount);
1141
1138
  }
1142
1139
  }
1143
1140
  /**
1144
1141
  * An evaluator for calculating osu!droid Rhythm skill.
1145
1142
  */
1146
- class DroidRhythmEvaluator extends RhythmEvaluator {
1143
+ class DroidRhythmEvaluator {
1147
1144
  /**
1148
1145
  * Calculates a rhythm multiplier for the difficulty of the tap associated
1149
1146
  * with historic data of the current object.
@@ -1151,9 +1148,7 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
1151
1148
  * @param current The current object.
1152
1149
  */
1153
1150
  static evaluateDifficultyOf(current) {
1154
- if (current.object instanceof osuBase.Spinner ||
1155
- // Exclude overlapping objects that can be tapped at once.
1156
- current.isOverlapping(false)) {
1151
+ if (current.object instanceof osuBase.Spinner) {
1157
1152
  return 1;
1158
1153
  }
1159
1154
  const deltaDifferenceEpsilon = current.fullGreatWindow * 0.3;
@@ -1183,47 +1178,50 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
1183
1178
  ++rhythmStart;
1184
1179
  }
1185
1180
  for (let i = rhythmStart; i > 0; --i) {
1186
- // Scale note 0 to 1 from history to now.
1187
- let currentHistoricalDecay = (this.historyTimeMax -
1188
- (current.startTime - validPrevious[i - 1].startTime)) /
1189
- this.historyTimeMax;
1190
- // Either we're limited by time or limited by object count.
1191
- currentHistoricalDecay = Math.min(currentHistoricalDecay, (validPrevious.length - i) / validPrevious.length);
1192
1181
  const currentObject = validPrevious[i - 1];
1193
1182
  const prevObject = validPrevious[i];
1194
1183
  const lastObject = validPrevious[i + 1];
1184
+ // Scale note 0 to 1 from history to now.
1185
+ const timeDecay = (this.historyTimeMax -
1186
+ (current.startTime - currentObject.startTime)) /
1187
+ this.historyTimeMax;
1188
+ const noteDecay = (validPrevious.length - i) / validPrevious.length;
1189
+ // Either we're limited by time or limited by object count.
1190
+ const currentHistoricalDecay = Math.min(timeDecay, noteDecay);
1195
1191
  const currentDelta = currentObject.strainTime;
1196
1192
  const prevDelta = prevObject.strainTime;
1197
1193
  const lastDelta = lastObject.strainTime;
1194
+ // Calculate how much current delta difference deserves a rhythm bonus
1195
+ // This function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e. 100 and 200)
1196
+ const deltaDifferenceRatio = Math.min(prevDelta, currentDelta) /
1197
+ Math.max(prevDelta, currentDelta);
1198
1198
  const currentRatio = 1 +
1199
- 10 *
1200
- Math.min(0.5, Math.pow(Math.sin(Math.PI /
1201
- (Math.min(prevDelta, currentDelta) /
1202
- Math.max(prevDelta, currentDelta))), 2));
1203
- const windowPenalty = osuBase.MathUtils.clamp((Math.abs(prevDelta - currentDelta) - deltaDifferenceEpsilon) /
1204
- deltaDifferenceEpsilon, 0, 1);
1205
- let effectiveRatio = windowPenalty * currentRatio;
1199
+ this.rhythmRatioMultiplier *
1200
+ Math.min(0.5, Math.pow(Math.sin(Math.PI / deltaDifferenceRatio), 2));
1201
+ // Reduce ratio bonus if delta difference is too big
1202
+ const fraction = Math.max(prevDelta / currentDelta, currentDelta / prevDelta);
1203
+ const fractionMultiplier = osuBase.MathUtils.clamp(2 - fraction / 8, 0, 1);
1204
+ const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
1205
+ let effectiveRatio = windowPenalty * currentRatio * fractionMultiplier;
1206
1206
  if (firstDeltaSwitch) {
1207
- if (Math.abs(prevDelta - currentDelta) <= deltaDifferenceEpsilon) {
1208
- if (island.deltas.length < this.maxIslandSize) {
1209
- // Island is still progressing.
1210
- island.addDelta(currentDelta);
1211
- }
1207
+ if (Math.abs(prevDelta - currentDelta) < deltaDifferenceEpsilon) {
1208
+ // Island is still progressing, count size.
1209
+ island.addDelta(currentDelta);
1212
1210
  }
1213
1211
  else {
1214
1212
  // BPM change is into slider, this is easy acc window.
1215
1213
  if (currentObject.object instanceof osuBase.Slider) {
1216
1214
  effectiveRatio /= 8;
1217
1215
  }
1218
- // BPM change was from a slider, this is typically easier than circle -> circle.
1219
- // Unintentional side effect is that bursts with kicksliders at the ends might
1220
- // have lower difficulty than bursts without sliders.
1216
+ // BPM change was from a slider, this is easier typically than circle -> circle.
1217
+ // Unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty
1218
+ // than bursts without sliders.
1221
1219
  if (prevObject.object instanceof osuBase.Slider) {
1222
- effectiveRatio /= 4;
1220
+ effectiveRatio *= 0.3;
1223
1221
  }
1224
1222
  // Repeated island polarity (2 -> 4, 3 -> 5).
1225
1223
  if (island.isSimilarPolarity(previousIsland)) {
1226
- effectiveRatio *= 0.3;
1224
+ effectiveRatio /= 2;
1227
1225
  }
1228
1226
  // Previous increase happened a note ago.
1229
1227
  // Albeit this is a 1/1 -> 1/2-1/4 type of transition, we don't want to buff this.
@@ -1231,8 +1229,9 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
1231
1229
  prevDelta > currentDelta + deltaDifferenceEpsilon) {
1232
1230
  effectiveRatio /= 8;
1233
1231
  }
1234
- // Singletaps are easier to control.
1235
- if (island.deltas.length === 1) {
1232
+ // Repeated island size (ex: triplet -> triplet).
1233
+ // TODO: remove this nerf since its staying here only for balancing purposes because of the flawed ratio calculation
1234
+ if (previousIsland.deltaCount == island.deltaCount) {
1236
1235
  effectiveRatio /= 2;
1237
1236
  }
1238
1237
  let islandFound = false;
@@ -1249,9 +1248,7 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
1249
1248
  }
1250
1249
  // Repeated island (ex: triplet -> triplet).
1251
1250
  // Graph: https://www.desmos.com/calculator/pj7an56zwf
1252
- effectiveRatio *= Math.min(1 / islandCount, Math.pow(1 / islandCount, 4 /
1253
- (1 +
1254
- Math.exp(10 - 0.165 * island.averageDelta))));
1251
+ effectiveRatio *= Math.min(3 / islandCount, Math.pow(1 / islandCount, 2.75 / (1 + Math.exp(14 - 0.24 * island.delta))));
1255
1252
  break;
1256
1253
  }
1257
1254
  if (!islandFound) {
@@ -1269,28 +1266,35 @@ class DroidRhythmEvaluator extends RhythmEvaluator {
1269
1266
  // If we're speeding up, this stays as is and we keep counting island size.
1270
1267
  firstDeltaSwitch = false;
1271
1268
  }
1272
- island = new Island(Math.trunc(currentDelta), deltaDifferenceEpsilon);
1269
+ island = new Island(currentDelta, deltaDifferenceEpsilon);
1273
1270
  }
1274
1271
  }
1275
- else if (prevDelta > deltaDifferenceEpsilon + currentDelta) {
1276
- // We're speeding up.
1272
+ else if (prevDelta > currentDelta + deltaDifferenceEpsilon) {
1273
+ // We are speeding up.
1277
1274
  // Begin counting island until we change speed again.
1278
1275
  firstDeltaSwitch = true;
1279
- // Reduce ratio if we're starting after a slider.
1276
+ // BPM change is into slider, this is easy acc window.
1277
+ if (currentObject.object instanceof osuBase.Slider) {
1278
+ effectiveRatio *= 0.6;
1279
+ }
1280
+ // BPM change was from a slider, this is easier typically than circle -> circle
1281
+ // Unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty
1282
+ // than bursts without sliders
1280
1283
  if (prevObject.object instanceof osuBase.Slider) {
1281
- effectiveRatio *= 0.3;
1284
+ effectiveRatio *= 0.6;
1282
1285
  }
1283
1286
  startRatio = effectiveRatio;
1284
- island = new Island(Math.trunc(currentDelta), deltaDifferenceEpsilon);
1287
+ island = new Island(currentDelta, deltaDifferenceEpsilon);
1285
1288
  }
1286
1289
  }
1287
- return Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier) / 2;
1290
+ return (Math.sqrt(4 + rhythmComplexitySum * this.rhythmOverallMultiplier) /
1291
+ 2);
1288
1292
  }
1289
1293
  }
1290
- DroidRhythmEvaluator.rhythmMultiplier = 1.2;
1291
- DroidRhythmEvaluator.historyTimeMax = 4000;
1292
- DroidRhythmEvaluator.maxIslandSize = 7;
1293
- DroidRhythmEvaluator.historyObjectsMax = 24;
1294
+ DroidRhythmEvaluator.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
1295
+ DroidRhythmEvaluator.historyObjectsMax = 32;
1296
+ DroidRhythmEvaluator.rhythmOverallMultiplier = 0.95;
1297
+ DroidRhythmEvaluator.rhythmRatioMultiplier = 12;
1294
1298
 
1295
1299
  /**
1296
1300
  * Represents the skill required to properly follow a beatmap's rhythm.
@@ -1492,9 +1496,8 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
1492
1496
  * @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
1493
1497
  * @param clockRate The clock rate of the beatmap.
1494
1498
  * @param greatWindow The great window of the hitobject.
1495
- * @param isForceAR Whether force AR is enabled.
1496
1499
  */
1497
- constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow, isForceAR) {
1500
+ constructor(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow) {
1498
1501
  super(object, lastObject, lastLastObject, difficultyHitObjects, clockRate, greatWindow);
1499
1502
  /**
1500
1503
  * The tap strain generated by the hitobject.
@@ -1538,10 +1541,7 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
1538
1541
  this.radiusBuffThreshold = 70;
1539
1542
  this.mode = osuBase.Modes.droid;
1540
1543
  this.maximumSliderRadius = this.normalizedRadius * 2;
1541
- this.timePreempt = object.timePreempt;
1542
- if (!isForceAR) {
1543
- this.timePreempt /= clockRate;
1544
- }
1544
+ this.timePreempt = object.timePreempt / clockRate;
1545
1545
  }
1546
1546
  computeProperties(clockRate, hitObjects) {
1547
1547
  super.computeProperties(clockRate, hitObjects);
@@ -1553,6 +1553,9 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
1553
1553
  * Keep in mind that "overlapping" in this case is overlapping to the point where both hitobjects
1554
1554
  * can be hit with just a single tap in osu!droid.
1555
1555
  *
1556
+ * In the case of sliders, it is considered overlapping if all nested hitobjects can be hit with
1557
+ * one aim motion.
1558
+ *
1556
1559
  * @param considerDistance Whether to consider the distance between both hitobjects.
1557
1560
  * @returns Whether the hitobject is considered overlapping.
1558
1561
  */
@@ -1560,23 +1563,48 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
1560
1563
  if (this.object instanceof osuBase.Spinner) {
1561
1564
  return false;
1562
1565
  }
1563
- const previous = this.previous(0);
1564
- if (!previous || previous.object instanceof osuBase.Spinner) {
1566
+ const prev = this.previous(0);
1567
+ if (!prev || prev.object instanceof osuBase.Spinner) {
1568
+ return false;
1569
+ }
1570
+ if (this.object.startTime !== prev.object.startTime) {
1565
1571
  return false;
1566
1572
  }
1567
- if (this.deltaTime >= 5) {
1573
+ if (!considerDistance) {
1574
+ return true;
1575
+ }
1576
+ const distanceThreshold = 2 * this.object.radius;
1577
+ const startPosition = this.object.getStackedPosition(osuBase.Modes.droid);
1578
+ const prevStartPosition = prev.object.getStackedPosition(osuBase.Modes.droid);
1579
+ // We need to consider two cases:
1580
+ //
1581
+ // Case 1: Current object is a circle, or previous object is a circle.
1582
+ // In this case, we only need to check if their positions are close enough to be tapped together.
1583
+ //
1584
+ // Case 2: Both objects are sliders.
1585
+ // In this case, we need to check if all nested hitobjects can be hit together.
1586
+ // To start with, check if the starting positions can be tapped together.
1587
+ if (startPosition.getDistance(prevStartPosition) > distanceThreshold) {
1568
1588
  return false;
1569
1589
  }
1570
- if (considerDistance) {
1571
- const endPosition = this.object.getStackedPosition(osuBase.Modes.droid);
1572
- let distance = previous.object
1573
- .getStackedEndPosition(osuBase.Modes.droid)
1574
- .getDistance(endPosition);
1575
- if (previous.object instanceof osuBase.Slider &&
1576
- previous.object.lazyEndPosition) {
1577
- distance = Math.min(distance, previous.object.lazyEndPosition.getDistance(endPosition));
1590
+ if (this.object instanceof osuBase.Circle || prev.object instanceof osuBase.Circle) {
1591
+ return true;
1592
+ }
1593
+ // Check if all nested hitobjects can be hit together.
1594
+ for (let i = 1; i < this.object.nestedHitObjects.length; ++i) {
1595
+ const position = this.object.nestedHitObjects[i].getStackedPosition(osuBase.Modes.droid);
1596
+ const prevPosition = prevStartPosition.add(prev.object.curvePositionAt(i / (this.object.nestedHitObjects.length - 1)));
1597
+ if (position.getDistance(prevPosition) > distanceThreshold) {
1598
+ return false;
1599
+ }
1600
+ }
1601
+ // Do the same for the previous slider as well.
1602
+ for (let i = 1; i < prev.object.nestedHitObjects.length; ++i) {
1603
+ const prevPosition = prev.object.nestedHitObjects[i].getStackedPosition(osuBase.Modes.droid);
1604
+ const position = startPosition.add(this.object.curvePositionAt(i / (prev.object.nestedHitObjects.length - 1)));
1605
+ if (prevPosition.getDistance(position) > distanceThreshold) {
1606
+ return false;
1578
1607
  }
1579
- return distance <= 2 * this.object.radius;
1580
1608
  }
1581
1609
  return true;
1582
1610
  }
@@ -1660,7 +1688,6 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1660
1688
  speedNoteCount: 0,
1661
1689
  sliderFactor: 0,
1662
1690
  clockRate: 1,
1663
- approachRate: 0,
1664
1691
  overallDifficulty: 0,
1665
1692
  hitCircleCount: 0,
1666
1693
  sliderCount: 0,
@@ -1710,30 +1737,11 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1710
1737
  return this.attributes.visualDifficulty;
1711
1738
  }
1712
1739
  get cacheableAttributes() {
1713
- return {
1714
- tapDifficulty: this.tap,
1715
- rhythmDifficulty: this.rhythm,
1716
- visualDifficulty: this.visual,
1717
- mods: osuBase.ModUtil.modsToOsuString(this.attributes.mods),
1718
- starRating: this.total,
1719
- maxCombo: this.attributes.maxCombo,
1720
- aimDifficulty: this.aim,
1721
- flashlightDifficulty: this.flashlight,
1722
- speedNoteCount: this.attributes.speedNoteCount,
1723
- sliderFactor: this.attributes.sliderFactor,
1724
- clockRate: this.attributes.clockRate,
1725
- approachRate: this.attributes.approachRate,
1726
- overallDifficulty: this.attributes.overallDifficulty,
1727
- hitCircleCount: this.attributes.hitCircleCount,
1728
- sliderCount: this.attributes.sliderCount,
1729
- spinnerCount: this.attributes.spinnerCount,
1730
- aimDifficultStrainCount: this.attributes.aimDifficultStrainCount,
1731
- tapDifficultStrainCount: this.attributes.tapDifficultStrainCount,
1732
- flashlightDifficultStrainCount: this.attributes.flashlightDifficultStrainCount,
1733
- visualDifficultStrainCount: this.attributes.visualDifficultStrainCount,
1734
- averageSpeedDeltaTime: this.attributes.averageSpeedDeltaTime,
1735
- vibroFactor: this.attributes.vibroFactor,
1736
- };
1740
+ return Object.assign(Object.assign({}, this.attributes), { mods: osuBase.ModUtil.modsToOsuString(this.attributes.mods) });
1741
+ }
1742
+ // Override to use DroidDifficultyCalculationOptions
1743
+ calculate(options) {
1744
+ return super.calculate(options);
1737
1745
  }
1738
1746
  /**
1739
1747
  * Calculates the aim star rating of the beatmap and stores it in this instance.
@@ -1842,31 +1850,19 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1842
1850
  this.visual.toFixed(2) +
1843
1851
  " visual)");
1844
1852
  }
1845
- generateDifficultyHitObjects(convertedBeatmap) {
1853
+ generateDifficultyHitObjects(beatmap, clockRate) {
1846
1854
  var _a, _b;
1847
1855
  const difficultyObjects = [];
1848
- const { objects } = convertedBeatmap.hitObjects;
1849
- const difficultyAdjustMod = this.mods.find((m) => m instanceof osuBase.ModDifficultyAdjust);
1850
- const greatWindow = new osuBase.OsuHitWindow(this.difficultyStatistics.overallDifficulty).hitWindowFor300();
1856
+ const { objects } = beatmap.hitObjects;
1857
+ const isPrecise = this.mods.some((m) => m instanceof osuBase.ModPrecise);
1858
+ const greatWindow = new osuBase.DroidHitWindow(beatmap.difficulty.od).hitWindowFor300(isPrecise) / clockRate;
1851
1859
  for (let i = 0; i < objects.length; ++i) {
1852
- const difficultyObject = new DroidDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier, greatWindow, (difficultyAdjustMod === null || difficultyAdjustMod === void 0 ? void 0 : difficultyAdjustMod.ar) !== undefined);
1853
- difficultyObject.computeProperties(this.difficultyStatistics.overallSpeedMultiplier, objects);
1860
+ const difficultyObject = new DroidDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, clockRate, greatWindow);
1861
+ difficultyObject.computeProperties(clockRate, objects);
1854
1862
  difficultyObjects.push(difficultyObject);
1855
1863
  }
1856
1864
  return difficultyObjects;
1857
1865
  }
1858
- computeDifficultyStatistics(options) {
1859
- const { difficulty } = this.beatmap;
1860
- return osuBase.calculateDroidDifficultyStatistics({
1861
- circleSize: difficulty.cs,
1862
- approachRate: difficulty.ar,
1863
- overallDifficulty: difficulty.od,
1864
- healthDrain: difficulty.hp,
1865
- mods: this.mods,
1866
- customSpeedMultiplier: options === null || options === void 0 ? void 0 : options.customSpeedMultiplier,
1867
- oldStatistics: options === null || options === void 0 ? void 0 : options.oldStatistics,
1868
- });
1869
- }
1870
1866
  createSkills() {
1871
1867
  return [
1872
1868
  new DroidAim(this.mods, true),
@@ -1883,6 +1879,10 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1883
1879
  new DroidVisual(this.mods, false),
1884
1880
  ];
1885
1881
  }
1882
+ calculateClockRate(options) {
1883
+ var _a, _b;
1884
+ return (osuBase.ModUtil.calculateRateWithMods((_a = options === null || options === void 0 ? void 0 : options.mods) !== null && _a !== void 0 ? _a : [], options === null || options === void 0 ? void 0 : options.oldStatistics) * ((_b = options === null || options === void 0 ? void 0 : options.customSpeedMultiplier) !== null && _b !== void 0 ? _b : 1));
1885
+ }
1886
1886
  /**
1887
1887
  * Called after aim skill calculation.
1888
1888
  *
@@ -3030,7 +3030,7 @@ OsuSpeedEvaluator.SINGLE_SPACING_THRESHOLD = 125;
3030
3030
  /**
3031
3031
  * An evaluator for calculating osu!standard Rhythm skill.
3032
3032
  */
3033
- class OsuRhythmEvaluator extends RhythmEvaluator {
3033
+ class OsuRhythmEvaluator {
3034
3034
  /**
3035
3035
  * Calculates a rhythm multiplier for the difficulty of the tap associated
3036
3036
  * with historic data of the current object.
@@ -3136,6 +3136,8 @@ class OsuRhythmEvaluator extends RhythmEvaluator {
3136
3136
  return Math.sqrt(4 + rhythmComplexitySum * this.rhythmMultiplier) / 2;
3137
3137
  }
3138
3138
  }
3139
+ OsuRhythmEvaluator.rhythmMultiplier = 0.75;
3140
+ OsuRhythmEvaluator.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
3139
3141
 
3140
3142
  /**
3141
3143
  * Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
@@ -3447,29 +3449,19 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
3447
3449
  this.flashlight.toFixed(2) +
3448
3450
  " flashlight)");
3449
3451
  }
3450
- generateDifficultyHitObjects(convertedBeatmap) {
3452
+ generateDifficultyHitObjects(beatmap, clockRate) {
3451
3453
  var _a, _b;
3452
3454
  const difficultyObjects = [];
3453
- const { objects } = convertedBeatmap.hitObjects;
3454
- const greatWindow = new osuBase.OsuHitWindow(this.difficultyStatistics.overallDifficulty).hitWindowFor300();
3455
+ const { objects } = beatmap.hitObjects;
3456
+ const greatWindow = new osuBase.OsuHitWindow(beatmap.difficulty.od).hitWindowFor300() /
3457
+ clockRate;
3455
3458
  for (let i = 0; i < objects.length; ++i) {
3456
- const difficultyObject = new OsuDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, this.difficultyStatistics.overallSpeedMultiplier, greatWindow);
3457
- difficultyObject.computeProperties(this.difficultyStatistics.overallSpeedMultiplier, objects);
3459
+ const difficultyObject = new OsuDifficultyHitObject(objects[i], (_a = objects[i - 1]) !== null && _a !== void 0 ? _a : null, (_b = objects[i - 2]) !== null && _b !== void 0 ? _b : null, difficultyObjects, clockRate, greatWindow);
3460
+ difficultyObject.computeProperties(clockRate, objects);
3458
3461
  difficultyObjects.push(difficultyObject);
3459
3462
  }
3460
3463
  return difficultyObjects;
3461
3464
  }
3462
- computeDifficultyStatistics(options) {
3463
- const { difficulty } = this.beatmap;
3464
- return osuBase.calculateOsuDifficultyStatistics({
3465
- circleSize: difficulty.cs,
3466
- approachRate: difficulty.ar,
3467
- overallDifficulty: difficulty.od,
3468
- healthDrain: difficulty.hp,
3469
- mods: options === null || options === void 0 ? void 0 : options.mods,
3470
- customSpeedMultiplier: options === null || options === void 0 ? void 0 : options.customSpeedMultiplier,
3471
- });
3472
- }
3473
3465
  createSkills() {
3474
3466
  return [
3475
3467
  new OsuAim(this.mods, true),
@@ -3478,6 +3470,11 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
3478
3470
  new OsuFlashlight(this.mods),
3479
3471
  ];
3480
3472
  }
3473
+ populateDifficultyAttributes(beatmap, clockRate) {
3474
+ super.populateDifficultyAttributes(beatmap, clockRate);
3475
+ const preempt = osuBase.BeatmapDifficulty.difficultyRange(beatmap.difficulty.ar, osuBase.HitObject.preemptMax, osuBase.HitObject.preemptMid, osuBase.HitObject.preemptMin) / clockRate;
3476
+ this.attributes.approachRate = osuBase.BeatmapDifficulty.inverseDifficultyRange(preempt, osuBase.HitObject.preemptMax, osuBase.HitObject.preemptMid, osuBase.HitObject.preemptMin);
3477
+ }
3481
3478
  /**
3482
3479
  * Called after aim skill calculation.
3483
3480
  *
@@ -3784,6 +3781,5 @@ exports.OsuRhythmEvaluator = OsuRhythmEvaluator;
3784
3781
  exports.OsuSpeed = OsuSpeed;
3785
3782
  exports.OsuSpeedEvaluator = OsuSpeedEvaluator;
3786
3783
  exports.PerformanceCalculator = PerformanceCalculator;
3787
- exports.RhythmEvaluator = RhythmEvaluator;
3788
3784
  exports.SpeedEvaluator = SpeedEvaluator;
3789
3785
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.25",
3
+ "version": "4.0.0-beta.27",
4
4
  "description": "A module for calculating osu!standard beatmap difficulty and performance value with respect to the current difficulty and performance algorithm.",
5
5
  "keywords": [
6
6
  "osu",
@@ -33,10 +33,10 @@
33
33
  "url": "https://github.com/Rian8337/osu-droid-module/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@rian8337/osu-base": "^4.0.0-beta.25"
36
+ "@rian8337/osu-base": "^4.0.0-beta.27"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "2d6271ea24c787caad1d24c3d20dbeeaf450359e"
41
+ "gitHead": "6714ebe03b132a0f9406a72900af70a9c39a58bc"
42
42
  }
@@ -1,4 +1,4 @@
1
- import { Mod, PlaceableHitObject, Modes, Beatmap, DifficultyStatisticsCalculatorResult, Accuracy } from '@rian8337/osu-base';
1
+ import { Mod, PlaceableHitObject, Modes, Beatmap, Accuracy } from '@rian8337/osu-base';
2
2
 
3
3
  /**
4
4
  * An evaluator for calculating aim skill.
@@ -62,12 +62,6 @@ interface DifficultyAttributes {
62
62
  * The overall clock rate that was applied to the beatmap.
63
63
  */
64
64
  clockRate: number;
65
- /**
66
- * The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
67
- *
68
- * Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
69
- */
70
- approachRate: number;
71
65
  /**
72
66
  * The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc), based on osu!standard judgement.
73
67
  *
@@ -327,7 +321,11 @@ declare abstract class DifficultyCalculator<THitObject extends DifficultyHitObje
327
321
  /**
328
322
  * The difficulty objects of the beatmap.
329
323
  */
330
- readonly objects: THitObject[];
324
+ private _objects;
325
+ /**
326
+ * The difficulty objects of the beatmap.
327
+ */
328
+ get objects(): readonly THitObject[];
331
329
  /**
332
330
  * The modifications applied.
333
331
  */
@@ -336,10 +334,6 @@ declare abstract class DifficultyCalculator<THitObject extends DifficultyHitObje
336
334
  * The total star rating of the beatmap.
337
335
  */
338
336
  get total(): number;
339
- /**
340
- * The difficulty statistics of the beatmap after modifications are applied.
341
- */
342
- difficultyStatistics: DifficultyStatisticsCalculatorResult<number, number, number, number>;
343
337
  /**
344
338
  * The strain peaks of various calculated difficulties.
345
339
  */
@@ -357,7 +351,7 @@ declare abstract class DifficultyCalculator<THitObject extends DifficultyHitObje
357
351
  /**
358
352
  * Constructs a new instance of the calculator.
359
353
  *
360
- * @param beatmap The beatmap to calculate. This beatmap will be deep-cloned to prevent reference changes.
354
+ * @param beatmap The beatmap to calculate.
361
355
  */
362
356
  constructor(beatmap: Beatmap);
363
357
  /**
@@ -379,16 +373,10 @@ declare abstract class DifficultyCalculator<THitObject extends DifficultyHitObje
379
373
  /**
380
374
  * Generates difficulty hitobjects for this calculator.
381
375
  *
382
- * @param convertedBeatmap The beatmap to generate difficulty hitobjects from.
383
- */
384
- protected abstract generateDifficultyHitObjects(convertedBeatmap: Beatmap): THitObject[];
385
- /**
386
- * Computes the difficulty statistics of the original beatmap with respect to the used options.
387
- *
388
- * @param options The options to use for the difficulty statistics calculation.
389
- * @returns The computed difficulty statistics.
376
+ * @param beatmap The beatmap to generate difficulty hitobjects from.
377
+ * @param clockRate The clock rate of the beatmap.
390
378
  */
391
- protected abstract computeDifficultyStatistics(options?: DifficultyCalculationOptions): DifficultyStatisticsCalculatorResult<number, number, number, number>;
379
+ protected abstract generateDifficultyHitObjects(beatmap: Beatmap, clockRate: number): THitObject[];
392
380
  /**
393
381
  * Calculates the skills provided.
394
382
  *
@@ -411,10 +399,20 @@ declare abstract class DifficultyCalculator<THitObject extends DifficultyHitObje
411
399
  * Creates skills to be calculated.
412
400
  */
413
401
  protected abstract createSkills(): Skill[];
402
+ /**
403
+ * Obtains the clock rate of the beatmap.
404
+ *
405
+ * @param options The options to obtain the clock rate with.
406
+ * @returns The clock rate of the beatmap.
407
+ */
408
+ protected calculateClockRate(options?: DifficultyCalculationOptions): number;
414
409
  /**
415
410
  * Populates the stored difficulty attributes with necessary data.
411
+ *
412
+ * @param beatmap The beatmap to populate the attributes with.
413
+ * @param clockRate The clock rate of the beatmap.
416
414
  */
417
- protected populateDifficultyAttributes(): void;
415
+ protected populateDifficultyAttributes(beatmap: Beatmap, clockRate: number): void;
418
416
  /**
419
417
  * Calculates the star rating value of a difficulty.
420
418
  *
@@ -616,9 +614,8 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
616
614
  * @param difficultyHitObjects All difficulty hitobjects in the processed beatmap.
617
615
  * @param clockRate The clock rate of the beatmap.
618
616
  * @param greatWindow The great window of the hitobject.
619
- * @param isForceAR Whether force AR is enabled.
620
617
  */
621
- constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, greatWindow: number, isForceAR: boolean);
618
+ constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, greatWindow: number);
622
619
  computeProperties(clockRate: number, hitObjects: readonly PlaceableHitObject[]): void;
623
620
  /**
624
621
  * Determines whether this hitobject is considered overlapping with the hitobject before it.
@@ -626,6 +623,9 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
626
623
  * Keep in mind that "overlapping" in this case is overlapping to the point where both hitobjects
627
624
  * can be hit with just a single tap in osu!droid.
628
625
  *
626
+ * In the case of sliders, it is considered overlapping if all nested hitobjects can be hit with
627
+ * one aim motion.
628
+ *
629
629
  * @param considerDistance Whether to consider the distance between both hitobjects.
630
630
  * @returns Whether the hitobject is considered overlapping.
631
631
  */
@@ -835,6 +835,7 @@ declare class DroidDifficultyCalculator extends DifficultyCalculator<DroidDiffic
835
835
  get cacheableAttributes(): CacheableDifficultyAttributes<DroidDifficultyAttributes>;
836
836
  protected readonly difficultyMultiplier = 0.18;
837
837
  protected readonly mode = Modes.droid;
838
+ calculate(options?: DroidDifficultyCalculationOptions): this;
838
839
  /**
839
840
  * Calculates the aim star rating of the beatmap and stores it in this instance.
840
841
  */
@@ -858,9 +859,9 @@ declare class DroidDifficultyCalculator extends DifficultyCalculator<DroidDiffic
858
859
  calculateTotal(): void;
859
860
  calculateAll(): void;
860
861
  toString(): string;
861
- protected generateDifficultyHitObjects(convertedBeatmap: Beatmap): DroidDifficultyHitObject[];
862
- protected computeDifficultyStatistics(options?: DroidDifficultyCalculationOptions): DifficultyStatisticsCalculatorResult<number, number, number, number>;
862
+ protected generateDifficultyHitObjects(beatmap: Beatmap, clockRate: number): DroidDifficultyHitObject[];
863
863
  protected createSkills(): DroidSkill[];
864
+ protected calculateClockRate(options?: DroidDifficultyCalculationOptions): number;
864
865
  /**
865
866
  * Called after aim skill calculation.
866
867
  *
@@ -1266,24 +1267,14 @@ declare class DroidRhythm extends DroidSkill {
1266
1267
  protected saveToHitObject(current: DroidDifficultyHitObject): void;
1267
1268
  }
1268
1269
 
1269
- /**
1270
- * An evaluator for calculating rhythm skill.
1271
- *
1272
- * This class should be considered an "evaluating" class and not persisted.
1273
- */
1274
- declare abstract class RhythmEvaluator {
1275
- protected static readonly rhythmMultiplier: number;
1276
- protected static readonly historyTimeMax: number;
1277
- }
1278
-
1279
1270
  /**
1280
1271
  * An evaluator for calculating osu!droid Rhythm skill.
1281
1272
  */
1282
- declare abstract class DroidRhythmEvaluator extends RhythmEvaluator {
1283
- protected static readonly rhythmMultiplier = 1.2;
1284
- protected static readonly historyTimeMax = 4000;
1285
- private static readonly maxIslandSize;
1273
+ declare abstract class DroidRhythmEvaluator {
1274
+ private static readonly historyTimeMax;
1286
1275
  private static readonly historyObjectsMax;
1276
+ private static readonly rhythmOverallMultiplier;
1277
+ private static readonly rhythmRatioMultiplier;
1287
1278
  /**
1288
1279
  * Calculates a rhythm multiplier for the difficulty of the tap associated
1289
1280
  * with historic data of the current object.
@@ -1481,6 +1472,12 @@ declare abstract class OsuAimEvaluator extends AimEvaluator {
1481
1472
  * Holds data that can be used to calculate osu!standard performance points.
1482
1473
  */
1483
1474
  interface OsuDifficultyAttributes extends DifficultyAttributes {
1475
+ /**
1476
+ * The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
1477
+ *
1478
+ * Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
1479
+ */
1480
+ approachRate: number;
1484
1481
  /**
1485
1482
  * The difficulty corresponding to the speed skill.
1486
1483
  */
@@ -1522,9 +1519,9 @@ declare class OsuDifficultyCalculator extends DifficultyCalculator<OsuDifficulty
1522
1519
  calculateTotal(): void;
1523
1520
  calculateAll(): void;
1524
1521
  toString(): string;
1525
- protected generateDifficultyHitObjects(convertedBeatmap: Beatmap): OsuDifficultyHitObject[];
1526
- protected computeDifficultyStatistics(options?: DifficultyCalculationOptions): DifficultyStatisticsCalculatorResult<number, number, number, number>;
1522
+ protected generateDifficultyHitObjects(beatmap: Beatmap, clockRate: number): OsuDifficultyHitObject[];
1527
1523
  protected createSkills(): OsuSkill[];
1524
+ protected populateDifficultyAttributes(beatmap: Beatmap, clockRate: number): void;
1528
1525
  /**
1529
1526
  * Called after aim skill calculation.
1530
1527
  *
@@ -1632,7 +1629,9 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator<OsuDifficul
1632
1629
  /**
1633
1630
  * An evaluator for calculating osu!standard Rhythm skill.
1634
1631
  */
1635
- declare abstract class OsuRhythmEvaluator extends RhythmEvaluator {
1632
+ declare abstract class OsuRhythmEvaluator {
1633
+ private static readonly rhythmMultiplier;
1634
+ private static readonly historyTimeMax;
1636
1635
  /**
1637
1636
  * Calculates a rhythm multiplier for the difficulty of the tap associated
1638
1637
  * with historic data of the current object.
@@ -1686,4 +1685,4 @@ declare abstract class OsuSpeedEvaluator extends SpeedEvaluator {
1686
1685
  static evaluateDifficultyOf(current: OsuDifficultyHitObject): number;
1687
1686
  }
1688
1687
 
1689
- export { AimEvaluator, type CacheableDifficultyAttributes, type DifficultSlider, type DifficultyAttributes, type DifficultyCalculationOptions, DifficultyCalculator, DifficultyHitObject, DroidAim, DroidAimEvaluator, type DroidDifficultyAttributes, type DroidDifficultyCalculationOptions, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, DroidVisual, DroidVisualEvaluator, type ExtendedDroidDifficultyAttributes, FlashlightEvaluator, type HighStrainSection, OsuAim, OsuAimEvaluator, type OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, type PerformanceCalculationOptions, PerformanceCalculator, RhythmEvaluator, SpeedEvaluator, type StrainPeaks };
1688
+ export { AimEvaluator, type CacheableDifficultyAttributes, type DifficultSlider, type DifficultyAttributes, type DifficultyCalculationOptions, DifficultyCalculator, DifficultyHitObject, DroidAim, DroidAimEvaluator, type DroidDifficultyAttributes, type DroidDifficultyCalculationOptions, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, DroidVisual, DroidVisualEvaluator, type ExtendedDroidDifficultyAttributes, FlashlightEvaluator, type HighStrainSection, OsuAim, OsuAimEvaluator, type OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, type PerformanceCalculationOptions, PerformanceCalculator, SpeedEvaluator, type StrainPeaks };