@rian8337/osu-difficulty-calculator 4.0.0-beta.73 → 4.0.0-beta.75

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
@@ -2064,6 +2064,12 @@ DroidDifficultyCalculator.threeFingerStrainThreshold = 175;
2064
2064
  * The base class of performance calculators.
2065
2065
  */
2066
2066
  class PerformanceCalculator {
2067
+ /**
2068
+ * Whether this score uses classic slider accuracy.
2069
+ */
2070
+ get usingClassicSliderAccuracy() {
2071
+ return this._usingClassicSliderAccuracy;
2072
+ }
2067
2073
  /**
2068
2074
  * @param difficultyAttributes The difficulty attributes to calculate.
2069
2075
  */
@@ -2080,6 +2086,17 @@ class PerformanceCalculator {
2080
2086
  * The amount of misses that are filtered out from sliderbreaks.
2081
2087
  */
2082
2088
  this.effectiveMissCount = 0;
2089
+ /**
2090
+ * The amount of slider ends dropped in the score.
2091
+ */
2092
+ this.sliderEndsDropped = 0;
2093
+ /**
2094
+ * The amount of slider ticks missed in the score.
2095
+ *
2096
+ * This is used to calculate the slider accuracy.
2097
+ */
2098
+ this.sliderTicksMissed = 0;
2099
+ this._usingClassicSliderAccuracy = false;
2083
2100
  /**
2084
2101
  * Nerf factor used for nerfing beatmaps with very likely dropped sliderends.
2085
2102
  */
@@ -2160,7 +2177,20 @@ class PerformanceCalculator {
2160
2177
  }
2161
2178
  const maxCombo = this.difficultyAttributes.maxCombo;
2162
2179
  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;
2180
+ let combo = (_b = options === null || options === void 0 ? void 0 : options.combo) !== null && _b !== void 0 ? _b : maxCombo - miss;
2181
+ if ((options === null || options === void 0 ? void 0 : options.sliderEndsDropped) !== undefined &&
2182
+ (options === null || options === void 0 ? void 0 : options.sliderTicksMissed) !== undefined) {
2183
+ this._usingClassicSliderAccuracy = false;
2184
+ this.sliderEndsDropped = options.sliderEndsDropped;
2185
+ this.sliderTicksMissed = options.sliderTicksMissed;
2186
+ }
2187
+ else {
2188
+ this._usingClassicSliderAccuracy = true;
2189
+ this.sliderEndsDropped = 0;
2190
+ this.sliderTicksMissed = 0;
2191
+ }
2192
+ // Ensure that combo is within possible bounds.
2193
+ combo = osuBase.MathUtils.clamp(combo, 0, maxCombo - miss - this.sliderEndsDropped - this.sliderTicksMissed);
2164
2194
  this.effectiveMissCount = this.calculateEffectiveMissCount(combo, maxCombo);
2165
2195
  if (this.mods.has(osuBase.ModNoFail)) {
2166
2196
  this.finalMultiplier *= Math.max(0.9, 1 - 0.02 * this.effectiveMissCount);
@@ -2189,18 +2219,43 @@ class PerformanceCalculator {
2189
2219
  this.computedAccuracy.n100 * n100Multiplier +
2190
2220
  this.computedAccuracy.n50 * n50Multiplier, this.totalHits);
2191
2221
  }
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);
2198
- this.sliderNerfFactor =
2199
- (1 - this.difficultyAttributes.sliderFactor) *
2200
- Math.pow(1 -
2201
- estimateSliderEndsDropped /
2202
- estimateDifficultSliders, 3) +
2203
- this.difficultyAttributes.sliderFactor;
2222
+ const { sliderCount, sliderFactor, aimDifficultSliderCount } = this.difficultyAttributes;
2223
+ if (sliderCount > 0) {
2224
+ if (this.mode === osuBase.Modes.droid) {
2225
+ // We assume 15% of sliders in a beatmap are difficult since there's no way to tell from the performance calculator.
2226
+ const estimateDifficultSliders = sliderCount * 0.15;
2227
+ const estimateSliderEndsDropped = osuBase.MathUtils.clamp(Math.min(this.computedAccuracy.n100 +
2228
+ this.computedAccuracy.n50 +
2229
+ this.computedAccuracy.nmiss, maxCombo - combo), 0, estimateDifficultSliders);
2230
+ this.sliderNerfFactor =
2231
+ (1 - sliderFactor) *
2232
+ Math.pow(1 -
2233
+ estimateSliderEndsDropped /
2234
+ estimateDifficultSliders, 3) +
2235
+ sliderFactor;
2236
+ }
2237
+ else {
2238
+ let estimateImproperlyFollowedDifficultSliders;
2239
+ if (this.usingClassicSliderAccuracy) {
2240
+ // When the score is considered classic (regardless if it was made on old client or not),
2241
+ // we consider all missing combo to be dropped difficult sliders
2242
+ estimateImproperlyFollowedDifficultSliders =
2243
+ osuBase.MathUtils.clamp(Math.min(this.totalImperfectHits, maxCombo - combo), 0, aimDifficultSliderCount);
2244
+ }
2245
+ else {
2246
+ // We add tick misses here since they too mean that the player didn't follow the slider
2247
+ // properly. However aren't adding misses here because missing slider heads has a harsh
2248
+ // penalty by itself and doesn't mean that the rest of the slider wasn't followed properly.
2249
+ estimateImproperlyFollowedDifficultSliders =
2250
+ osuBase.MathUtils.clamp(this.sliderEndsDropped + this.sliderTicksMissed, 0, aimDifficultSliderCount);
2251
+ }
2252
+ this.sliderNerfFactor =
2253
+ (1 - sliderFactor) *
2254
+ Math.pow(1 -
2255
+ estimateImproperlyFollowedDifficultSliders /
2256
+ aimDifficultSliderCount, 3) +
2257
+ sliderFactor;
2258
+ }
2204
2259
  }
2205
2260
  }
2206
2261
  /**
@@ -2223,18 +2278,31 @@ class PerformanceCalculator {
2223
2278
  * Calculates the amount of misses + sliderbreaks from combo.
2224
2279
  */
2225
2280
  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);
2281
+ let missCount = this.computedAccuracy.nmiss;
2282
+ const { sliderCount } = this.difficultyAttributes;
2283
+ if (sliderCount > 0) {
2284
+ if (this.usingClassicSliderAccuracy || this.mode === osuBase.Modes.droid) {
2285
+ // Consider that full combo is maximum combo minus dropped slider tails since
2286
+ // they don't contribute to combo but also don't break it.
2287
+ // In classic scores, we can't know the amount of dropped sliders so we estimate
2288
+ // to 10% of all sliders in the beatmap.
2289
+ const fullComboThreshold = maxCombo - 0.1 * sliderCount;
2290
+ if (combo < fullComboThreshold) {
2291
+ missCount = fullComboThreshold / Math.max(1, combo);
2292
+ }
2293
+ // In classic scores, there can't be more misses than a sum of all non-perfect judgements.
2294
+ missCount = Math.min(missCount, this.totalImperfectHits);
2295
+ }
2296
+ else {
2297
+ const fullComboThreshold = maxCombo - this.sliderEndsDropped;
2298
+ if (combo < fullComboThreshold) {
2299
+ missCount = fullComboThreshold / Math.max(1, combo);
2300
+ }
2301
+ // Combine regular misses with tick misses, since tick misses break combo as well.
2302
+ missCount = Math.min(missCount, this.sliderTicksMissed + this.computedAccuracy.nmiss);
2235
2303
  }
2236
2304
  }
2237
- return Math.max(this.computedAccuracy.nmiss, comboBasedMissCount);
2305
+ return osuBase.MathUtils.clamp(missCount, this.computedAccuracy.nmiss, this.totalHits);
2238
2306
  }
2239
2307
  /**
2240
2308
  * Determines whether an attribute is a cacheable attribute.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.73",
3
+ "version": "4.0.0-beta.75",
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.73"
36
+ "@rian8337/osu-base": "^4.0.0-beta.74"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "e68f26caa3ddc53bbbfa3ab59a6773d0f445e136"
41
+ "gitHead": "1e8a43d98546e5071bebe45b3ca40dd496aa7b21"
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
  */