@rian8337/osu-difficulty-calculator 4.0.0-beta.74 → 4.0.0-beta.76

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
@@ -849,15 +849,18 @@ class DroidAimEvaluator {
849
849
  * Calculates the flow aim strain of a hitobject.
850
850
  */
851
851
  static flowAimStrainOf(current) {
852
- var _a, _b;
852
+ var _a;
853
853
  let speedBonus = 1;
854
854
  if (current.strainTime < this.minSpeedBonus) {
855
855
  speedBonus +=
856
856
  0.75 *
857
857
  Math.pow((this.minSpeedBonus - current.strainTime) / 40, 2);
858
858
  }
859
- const travelDistance = (_b = (_a = current.previous(0)) === null || _a === void 0 ? void 0 : _a.travelDistance) !== null && _b !== void 0 ? _b : 0;
860
- const shortDistancePenalty = Math.pow(Math.min(this.singleSpacingThreshold, travelDistance + current.minimumJumpDistance) / this.singleSpacingThreshold, 3.5);
859
+ const prev = current.previous(0);
860
+ // Punish low spacing as it is easier to aim.
861
+ const travelDistance = (_a = prev === null || prev === void 0 ? void 0 : prev.travelDistance) !== null && _a !== void 0 ? _a : 0;
862
+ const distance = travelDistance + current.minimumJumpDistance;
863
+ const shortDistancePenalty = Math.min(1, Math.pow(distance / this.singleSpacingThreshold, 3.5));
861
864
  return (200 * speedBonus * shortDistancePenalty) / current.strainTime;
862
865
  }
863
866
  static calculateWideAngleBonus(angle) {
@@ -872,7 +875,7 @@ DroidAimEvaluator.acuteAngleMultiplier = 2.6;
872
875
  DroidAimEvaluator.sliderMultiplier = 1.35;
873
876
  DroidAimEvaluator.velocityChangeMultiplier = 0.75;
874
877
  DroidAimEvaluator.wiggleMultiplier = 1.02;
875
- DroidAimEvaluator.singleSpacingThreshold = 100;
878
+ DroidAimEvaluator.singleSpacingThreshold = DroidDifficultyHitObject.normalizedDiameter;
876
879
  // 200 1/4 BPM delta time
877
880
  DroidAimEvaluator.minSpeedBonus = 75;
878
881
 
@@ -1028,7 +1031,7 @@ class DroidAim extends DroidSkill {
1028
1031
  this.reducedSectionCount = 10;
1029
1032
  this.reducedSectionBaseline = 0.75;
1030
1033
  this.starsPerDouble = 1.05;
1031
- this.skillMultiplier = 25.6;
1034
+ this.skillMultiplier = 26.5;
1032
1035
  this.currentAimStrain = 0;
1033
1036
  this.sliderStrains = [];
1034
1037
  this.withSliders = withSliders;
@@ -1230,7 +1233,7 @@ class DroidFlashlight extends DroidSkill {
1230
1233
  this.reducedSectionCount = 0;
1231
1234
  this.reducedSectionBaseline = 1;
1232
1235
  this.starsPerDouble = 1.06;
1233
- this.skillMultiplier = 0.02;
1236
+ this.skillMultiplier = 0.023;
1234
1237
  this.currentFlashlightStrain = 0;
1235
1238
  this.withSliders = withSliders;
1236
1239
  }
@@ -1501,7 +1504,6 @@ class DroidTapEvaluator {
1501
1504
  * - and the strain time cap.
1502
1505
  *
1503
1506
  * @param current The current object.
1504
- * @param greatWindow The great hit window of the current object.
1505
1507
  * @param considerCheesability Whether to consider cheesability.
1506
1508
  * @param strainTimeCap The strain time to cap the object's strain time to.
1507
1509
  */
@@ -1511,7 +1513,6 @@ class DroidTapEvaluator {
1511
1513
  current.isOverlapping(false)) {
1512
1514
  return 0;
1513
1515
  }
1514
- // Nerf doubletappable doubles.
1515
1516
  const doubletapness = considerCheesability
1516
1517
  ? 1 - current.doubletapness
1517
1518
  : 1;
@@ -1646,7 +1647,7 @@ class DroidVisualEvaluator {
1646
1647
  current.index === 0) {
1647
1648
  return 0;
1648
1649
  }
1649
- // Start with base density and give global bonus for Hidden and Traceable.
1650
+ // Start with base density and give global bonus for Hidden.
1650
1651
  // Add density caps for sanity.
1651
1652
  let strain;
1652
1653
  if (mods.has(osuBase.ModHidden)) {
@@ -1737,7 +1738,7 @@ class DroidVisual extends DroidSkill {
1737
1738
  this.strainDecayBase = 0.1;
1738
1739
  this.currentVisualStrain = 0;
1739
1740
  this.currentRhythmMultiplier = 1;
1740
- this.skillMultiplier = 10;
1741
+ this.skillMultiplier = 11.2;
1741
1742
  this.withSliders = withSliders;
1742
1743
  }
1743
1744
  strainValueAt(current) {
@@ -2064,6 +2065,12 @@ DroidDifficultyCalculator.threeFingerStrainThreshold = 175;
2064
2065
  * The base class of performance calculators.
2065
2066
  */
2066
2067
  class PerformanceCalculator {
2068
+ /**
2069
+ * Whether this score uses classic slider accuracy.
2070
+ */
2071
+ get usingClassicSliderAccuracy() {
2072
+ return this._usingClassicSliderAccuracy;
2073
+ }
2067
2074
  /**
2068
2075
  * @param difficultyAttributes The difficulty attributes to calculate.
2069
2076
  */
@@ -2080,6 +2087,17 @@ class PerformanceCalculator {
2080
2087
  * The amount of misses that are filtered out from sliderbreaks.
2081
2088
  */
2082
2089
  this.effectiveMissCount = 0;
2090
+ /**
2091
+ * The amount of slider ends dropped in the score.
2092
+ */
2093
+ this.sliderEndsDropped = 0;
2094
+ /**
2095
+ * The amount of slider ticks missed in the score.
2096
+ *
2097
+ * This is used to calculate the slider accuracy.
2098
+ */
2099
+ this.sliderTicksMissed = 0;
2100
+ this._usingClassicSliderAccuracy = false;
2083
2101
  /**
2084
2102
  * Nerf factor used for nerfing beatmaps with very likely dropped sliderends.
2085
2103
  */
@@ -2160,7 +2178,20 @@ class PerformanceCalculator {
2160
2178
  }
2161
2179
  const maxCombo = this.difficultyAttributes.maxCombo;
2162
2180
  const miss = this.computedAccuracy.nmiss;
2163
- const combo = (_b = options === null || options === void 0 ? void 0 : options.combo) !== null && _b !== void 0 ? _b : maxCombo - miss;
2181
+ let combo = (_b = options === null || options === void 0 ? void 0 : options.combo) !== null && _b !== void 0 ? _b : maxCombo - miss;
2182
+ if ((options === null || options === void 0 ? void 0 : options.sliderEndsDropped) !== undefined &&
2183
+ (options === null || options === void 0 ? void 0 : options.sliderTicksMissed) !== undefined) {
2184
+ this._usingClassicSliderAccuracy = false;
2185
+ this.sliderEndsDropped = options.sliderEndsDropped;
2186
+ this.sliderTicksMissed = options.sliderTicksMissed;
2187
+ }
2188
+ else {
2189
+ this._usingClassicSliderAccuracy = true;
2190
+ this.sliderEndsDropped = 0;
2191
+ this.sliderTicksMissed = 0;
2192
+ }
2193
+ // Ensure that combo is within possible bounds.
2194
+ combo = osuBase.MathUtils.clamp(combo, 0, maxCombo - miss - this.sliderEndsDropped - this.sliderTicksMissed);
2164
2195
  this.effectiveMissCount = this.calculateEffectiveMissCount(combo, maxCombo);
2165
2196
  if (this.mods.has(osuBase.ModNoFail)) {
2166
2197
  this.finalMultiplier *= Math.max(0.9, 1 - 0.02 * this.effectiveMissCount);
@@ -2189,18 +2220,26 @@ class PerformanceCalculator {
2189
2220
  this.computedAccuracy.n100 * n100Multiplier +
2190
2221
  this.computedAccuracy.n50 * n50Multiplier, this.totalHits);
2191
2222
  }
2192
- if (this.difficultyAttributes.sliderCount > 0) {
2193
- // We assume 15% of sliders in a beatmap are difficult since there's no way to tell from the performance calculator.
2194
- const estimateDifficultSliders = this.difficultyAttributes.sliderCount * 0.15;
2195
- const estimateSliderEndsDropped = osuBase.MathUtils.clamp(Math.min(this.computedAccuracy.n100 +
2196
- this.computedAccuracy.n50 +
2197
- this.computedAccuracy.nmiss, maxCombo - combo), 0, estimateDifficultSliders);
2223
+ const { aimDifficultSliderCount, sliderFactor } = this.difficultyAttributes;
2224
+ if (aimDifficultSliderCount > 0) {
2225
+ let estimateImproperlyFollowedDifficultSliders;
2226
+ if (this.usingClassicSliderAccuracy) {
2227
+ // When the score is considered classic (regardless if it was made on old client or not),
2228
+ // we consider all missing combo to be dropped difficult sliders.
2229
+ estimateImproperlyFollowedDifficultSliders = osuBase.MathUtils.clamp(Math.min(this.totalImperfectHits, maxCombo - combo), 0, aimDifficultSliderCount);
2230
+ }
2231
+ else {
2232
+ // We add tick misses here since they too mean that the player didn't follow the slider
2233
+ // properly. However aren't adding misses here because missing slider heads has a harsh
2234
+ // penalty by itself and doesn't mean that the rest of the slider wasn't followed properly.
2235
+ estimateImproperlyFollowedDifficultSliders = osuBase.MathUtils.clamp(this.sliderEndsDropped + this.sliderTicksMissed, 0, aimDifficultSliderCount);
2236
+ }
2198
2237
  this.sliderNerfFactor =
2199
- (1 - this.difficultyAttributes.sliderFactor) *
2238
+ (1 - sliderFactor) *
2200
2239
  Math.pow(1 -
2201
- estimateSliderEndsDropped /
2202
- estimateDifficultSliders, 3) +
2203
- this.difficultyAttributes.sliderFactor;
2240
+ estimateImproperlyFollowedDifficultSliders /
2241
+ aimDifficultSliderCount, 3) +
2242
+ sliderFactor;
2204
2243
  }
2205
2244
  }
2206
2245
  /**
@@ -2223,18 +2262,31 @@ class PerformanceCalculator {
2223
2262
  * Calculates the amount of misses + sliderbreaks from combo.
2224
2263
  */
2225
2264
  calculateEffectiveMissCount(combo, maxCombo) {
2226
- // Guess the number of misses + slider breaks from combo.
2227
- let comboBasedMissCount = 0;
2228
- if (this.difficultyAttributes.sliderCount > 0) {
2229
- const fullComboThreshold = maxCombo - 0.1 * this.difficultyAttributes.sliderCount;
2230
- if (combo < fullComboThreshold) {
2231
- // Clamp miss count to maximum amount of possible breaks.
2232
- comboBasedMissCount = Math.min(fullComboThreshold / Math.max(1, combo), this.computedAccuracy.n100 +
2233
- this.computedAccuracy.n50 +
2234
- this.computedAccuracy.nmiss);
2265
+ let missCount = this.computedAccuracy.nmiss;
2266
+ const { sliderCount } = this.difficultyAttributes;
2267
+ if (sliderCount > 0) {
2268
+ if (this.usingClassicSliderAccuracy || this.mode === osuBase.Modes.droid) {
2269
+ // Consider that full combo is maximum combo minus dropped slider tails since
2270
+ // they don't contribute to combo but also don't break it.
2271
+ // In classic scores, we can't know the amount of dropped sliders so we estimate
2272
+ // to 10% of all sliders in the beatmap.
2273
+ const fullComboThreshold = maxCombo - 0.1 * sliderCount;
2274
+ if (combo < fullComboThreshold) {
2275
+ missCount = fullComboThreshold / Math.max(1, combo);
2276
+ }
2277
+ // In classic scores, there can't be more misses than a sum of all non-perfect judgements.
2278
+ missCount = Math.min(missCount, this.totalImperfectHits);
2279
+ }
2280
+ else {
2281
+ const fullComboThreshold = maxCombo - this.sliderEndsDropped;
2282
+ if (combo < fullComboThreshold) {
2283
+ missCount = fullComboThreshold / Math.max(1, combo);
2284
+ }
2285
+ // Combine regular misses with tick misses, since tick misses break combo as well.
2286
+ missCount = Math.min(missCount, this.sliderTicksMissed + this.computedAccuracy.nmiss);
2235
2287
  }
2236
2288
  }
2237
- return Math.max(this.computedAccuracy.nmiss, comboBasedMissCount);
2289
+ return osuBase.MathUtils.clamp(missCount, this.computedAccuracy.nmiss, this.totalHits);
2238
2290
  }
2239
2291
  /**
2240
2292
  * Determines whether an attribute is a cacheable attribute.
@@ -2424,7 +2476,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2424
2476
  Math.pow(this.visual, 1.1), 1 / 1.1) * this.finalMultiplier);
2425
2477
  }
2426
2478
  handleOptions(options) {
2427
- var _a, _b, _c, _d, _e;
2479
+ var _a, _b, _c, _d;
2428
2480
  this._tapPenalty = (_a = options === null || options === void 0 ? void 0 : options.tapPenalty) !== null && _a !== void 0 ? _a : 1;
2429
2481
  this._aimSliderCheesePenalty = (_b = options === null || options === void 0 ? void 0 : options.aimSliderCheesePenalty) !== null && _b !== void 0 ? _b : 1;
2430
2482
  this._flashlightSliderCheesePenalty =
@@ -2432,18 +2484,6 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2432
2484
  this._visualSliderCheesePenalty =
2433
2485
  (_d = options === null || options === void 0 ? void 0 : options.visualSliderCheesePenalty) !== null && _d !== void 0 ? _d : 1;
2434
2486
  super.handleOptions(options);
2435
- const { maxCombo, aimDifficultSliderCount, sliderFactor } = this.difficultyAttributes;
2436
- const combo = (_e = options === null || options === void 0 ? void 0 : options.combo) !== null && _e !== void 0 ? _e : maxCombo - this.computedAccuracy.nmiss;
2437
- if (aimDifficultSliderCount > 0) {
2438
- // Consider all missing combo to be dropped difficult sliders.
2439
- const estimateImproperlyFollowedDifficultSliders = osuBase.MathUtils.clamp(Math.min(this.totalImperfectHits, maxCombo - combo), 0, aimDifficultSliderCount);
2440
- this.sliderNerfFactor =
2441
- (1 - sliderFactor) *
2442
- Math.pow(1 -
2443
- estimateImproperlyFollowedDifficultSliders /
2444
- aimDifficultSliderCount, 3) +
2445
- sliderFactor;
2446
- }
2447
2487
  }
2448
2488
  /**
2449
2489
  * Calculates the aim performance value of the beatmap.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.74",
3
+ "version": "4.0.0-beta.76",
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",
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "00abc82040e820ccff8fcad7ee77d7e7d8031717"
41
+ "gitHead": "d843f2cf65e2e44c87654a2be675d6ed925193ea"
42
42
  }
@@ -983,6 +983,21 @@ declare abstract class PerformanceCalculator<T extends IDifficultyAttributes> {
983
983
  * The amount of misses that are filtered out from sliderbreaks.
984
984
  */
985
985
  protected effectiveMissCount: number;
986
+ /**
987
+ * The amount of slider ends dropped in the score.
988
+ */
989
+ protected sliderEndsDropped: number;
990
+ /**
991
+ * The amount of slider ticks missed in the score.
992
+ *
993
+ * This is used to calculate the slider accuracy.
994
+ */
995
+ protected sliderTicksMissed: number;
996
+ private _usingClassicSliderAccuracy;
997
+ /**
998
+ * Whether this score uses classic slider accuracy.
999
+ */
1000
+ protected get usingClassicSliderAccuracy(): boolean;
986
1001
  /**
987
1002
  * Nerf factor used for nerfing beatmaps with very likely dropped sliderends.
988
1003
  */
@@ -1298,7 +1313,6 @@ declare abstract class DroidTapEvaluator {
1298
1313
  * - and the strain time cap.
1299
1314
  *
1300
1315
  * @param current The current object.
1301
- * @param greatWindow The great hit window of the current object.
1302
1316
  * @param considerCheesability Whether to consider cheesability.
1303
1317
  * @param strainTimeCap The strain time to cap the object's strain time to.
1304
1318
  */