@rian8337/osu-difficulty-calculator 4.0.0-beta.86 → 4.0.0-beta.88

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
@@ -409,7 +409,7 @@ class DifficultyHitObject {
409
409
  this.lazyEndPosition = this.object.stackedPosition;
410
410
  // Stop here if the slider has too short duration, allowing the player to essentially
411
411
  // complete the slider without movement, making travel distance and time irrelevant.
412
- if (osuBase.Precision.almostEqualsNumber(this.object.startTime, this.object.endTime)) {
412
+ if (osuBase.Precision.almostEquals(this.object.startTime, this.object.endTime)) {
413
413
  return;
414
414
  }
415
415
  }
@@ -1919,15 +1919,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1919
1919
  attributes.hitCircleCount = beatmap.hitObjects.circles;
1920
1920
  attributes.sliderCount = beatmap.hitObjects.sliders;
1921
1921
  attributes.spinnerCount = beatmap.hitObjects.spinners;
1922
- let greatWindow;
1923
- if (attributes.mods.has(osuBase.ModPrecise)) {
1924
- greatWindow = new osuBase.PreciseDroidHitWindow(beatmap.difficulty.od)
1925
- .greatWindow;
1926
- }
1927
- else {
1928
- greatWindow = new osuBase.DroidHitWindow(beatmap.difficulty.od).greatWindow;
1929
- }
1930
- attributes.overallDifficulty = osuBase.OsuHitWindow.greatWindowToOD(greatWindow / attributes.clockRate);
1922
+ attributes.overallDifficulty = beatmap.difficulty.od;
1931
1923
  this.populateAimAttributes(attributes, skills, objects);
1932
1924
  this.populateTapAttributes(attributes, skills, objects);
1933
1925
  this.populateRhythmAttributes(attributes, skills);
@@ -2162,8 +2154,16 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
2162
2154
  attributes.readingDifficulty *= 0.4;
2163
2155
  }
2164
2156
  // Consider accuracy difficulty.
2165
- const ratingMultiplier = 0.75 +
2166
- Math.pow(Math.max(0, attributes.overallDifficulty), 2.2) / 800;
2157
+ let greatWindow;
2158
+ if (attributes.mods.has(osuBase.ModPrecise)) {
2159
+ greatWindow = new osuBase.PreciseDroidHitWindow(attributes.overallDifficulty).greatWindow;
2160
+ }
2161
+ else {
2162
+ greatWindow = new osuBase.DroidHitWindow(attributes.overallDifficulty)
2163
+ .greatWindow;
2164
+ }
2165
+ const overallDifficulty = osuBase.UnadjustedOsuHitWindow.greatWindowToOD(greatWindow / attributes.clockRate);
2166
+ const ratingMultiplier = 0.75 + Math.pow(Math.max(0, overallDifficulty), 2.2) / 800;
2167
2167
  attributes.readingDifficulty *= Math.sqrt(ratingMultiplier);
2168
2168
  }
2169
2169
  /**
@@ -2687,7 +2687,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2687
2687
  return Number.POSITIVE_INFINITY;
2688
2688
  }
2689
2689
  const { clockRate } = this.difficultyAttributes;
2690
- const hitWindow = this.getConvertedHitWindow();
2690
+ const hitWindow = this.getHitWindow();
2691
2691
  const hitWindow300 = hitWindow.greatWindow / clockRate;
2692
2692
  const hitWindow100 = hitWindow.okWindow / clockRate;
2693
2693
  const hitWindow50 = hitWindow.mehWindow / clockRate;
@@ -2756,7 +2756,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2756
2756
  return Number.POSITIVE_INFINITY;
2757
2757
  }
2758
2758
  const { clockRate, speedNoteCount } = this.difficultyAttributes;
2759
- const hitWindow = this.getConvertedHitWindow();
2759
+ const hitWindow = this.getHitWindow();
2760
2760
  const hitWindow300 = hitWindow.greatWindow / clockRate;
2761
2761
  const hitWindow100 = hitWindow.okWindow / clockRate;
2762
2762
  const hitWindow50 = hitWindow.mehWindow / clockRate;
@@ -2842,14 +2842,11 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2842
2842
  const t = 1 - osuBase.Interpolation.reverseLerp(this.tapDeviation, 25, 30);
2843
2843
  return osuBase.Interpolation.lerp(adjustedTapValue, tapValue, t) / tapValue;
2844
2844
  }
2845
- getConvertedHitWindow() {
2846
- const hitWindow300 = new osuBase.OsuHitWindow(this.difficultyAttributes.overallDifficulty).greatWindow;
2847
- if (this.mods.has(osuBase.ModPrecise)) {
2848
- return new osuBase.PreciseDroidHitWindow(osuBase.PreciseDroidHitWindow.greatWindowToOD(hitWindow300 * this.difficultyAttributes.clockRate));
2849
- }
2850
- else {
2851
- return new osuBase.DroidHitWindow(osuBase.DroidHitWindow.greatWindowToOD(hitWindow300 * this.difficultyAttributes.clockRate));
2852
- }
2845
+ getHitWindow() {
2846
+ const { overallDifficulty } = this.difficultyAttributes;
2847
+ return this.mods.has(osuBase.ModPrecise)
2848
+ ? new osuBase.PreciseDroidHitWindow(overallDifficulty)
2849
+ : new osuBase.DroidHitWindow(overallDifficulty);
2853
2850
  }
2854
2851
  toString() {
2855
2852
  return (this.total.toFixed(2) +
@@ -3182,6 +3179,185 @@ class OsuDifficultyAttributes extends DifficultyAttributes {
3182
3179
  }
3183
3180
  }
3184
3181
 
3182
+ class OsuRatingCalculator {
3183
+ constructor(mods, totalHits, approachRate, overallDifficulty, mechanicalDifficultyRating, sliderFactor) {
3184
+ this.mods = mods;
3185
+ this.totalHits = totalHits;
3186
+ this.approachRate = approachRate;
3187
+ this.overallDifficulty = overallDifficulty;
3188
+ this.mechanicalDifficultyRating = mechanicalDifficultyRating;
3189
+ this.sliderFactor = sliderFactor;
3190
+ }
3191
+ computeAimRating(aimDifficultyValue) {
3192
+ if (this.mods.has(osuBase.ModAutopilot)) {
3193
+ return 0;
3194
+ }
3195
+ let aimRating = OsuRatingCalculator.calculateDifficultyRating(aimDifficultyValue);
3196
+ if (this.mods.has(osuBase.ModTouchDevice)) {
3197
+ aimRating = Math.pow(aimRating, 0.8);
3198
+ }
3199
+ if (this.mods.has(osuBase.ModRelax)) {
3200
+ aimRating *= 0.9;
3201
+ }
3202
+ if (this.mods.has(osuBase.ModMagnetised)) {
3203
+ const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3204
+ aimRating *= 1 - magnetisedStrength;
3205
+ }
3206
+ let ratingMultiplier = 1;
3207
+ const approachRateLengthBonus = 0.95 +
3208
+ 0.4 * Math.min(1, this.totalHits / 2000) +
3209
+ (this.totalHits > 2000
3210
+ ? Math.log10(this.totalHits / 2000) * 0.5
3211
+ : 0);
3212
+ let approachRateFactor = 0;
3213
+ if (this.approachRate > 10.33) {
3214
+ approachRateFactor = 0.3 * (this.approachRate - 10.33);
3215
+ }
3216
+ else if (this.approachRate < 8) {
3217
+ approachRateFactor = 0.05 * (8 - this.approachRate);
3218
+ }
3219
+ if (this.mods.has(osuBase.ModRelax)) {
3220
+ approachRateFactor = 0;
3221
+ }
3222
+ // Buff for longer beatmaps with high AR.
3223
+ ratingMultiplier += approachRateFactor * approachRateLengthBonus;
3224
+ if (this.mods.has(osuBase.ModHidden)) {
3225
+ const visibilityFactor = this.calculateAimVisibilityFactor();
3226
+ ratingMultiplier += OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, visibilityFactor, this.sliderFactor);
3227
+ }
3228
+ // It is important to consider accuracy difficulty when scaling with accuracy.
3229
+ ratingMultiplier *=
3230
+ 0.98 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 2500;
3231
+ return aimRating * Math.cbrt(ratingMultiplier);
3232
+ }
3233
+ computeSpeedRating(speedDifficultyValue) {
3234
+ if (this.mods.has(osuBase.ModRelax)) {
3235
+ return 0;
3236
+ }
3237
+ let speedRating = OsuRatingCalculator.calculateDifficultyRating(speedDifficultyValue);
3238
+ if (this.mods.has(osuBase.ModAutopilot)) {
3239
+ speedRating *= 0.5;
3240
+ }
3241
+ if (this.mods.has(osuBase.ModMagnetised)) {
3242
+ // Reduce speed rating because of the distance scaling, with maximum reduction being 0.7.
3243
+ const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3244
+ speedRating *= 1 - magnetisedStrength * 0.3;
3245
+ }
3246
+ let ratingMultiplier = 1;
3247
+ const approachRateLengthBonus = 0.95 +
3248
+ 0.4 * Math.min(1, this.totalHits / 2000) +
3249
+ (this.totalHits > 2000
3250
+ ? Math.log10(this.totalHits / 2000) * 0.5
3251
+ : 0);
3252
+ let approachRateFactor = 0;
3253
+ if (this.approachRate > 10.33) {
3254
+ approachRateFactor = 0.3 * (this.approachRate - 10.33);
3255
+ }
3256
+ if (this.mods.has(osuBase.ModAutopilot)) {
3257
+ approachRateFactor = 0;
3258
+ }
3259
+ // Buff for longer beatmaps with high AR.
3260
+ ratingMultiplier += approachRateFactor * approachRateLengthBonus;
3261
+ if (this.mods.has(osuBase.ModHidden)) {
3262
+ const visibilityFactor = this.calculateSpeedVisibilityFactor();
3263
+ ratingMultiplier += OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, visibilityFactor, this.sliderFactor);
3264
+ }
3265
+ ratingMultiplier *=
3266
+ 0.95 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 750;
3267
+ return speedRating * Math.cbrt(ratingMultiplier);
3268
+ }
3269
+ computeFlashlightRating(flashlightDifficultyValue) {
3270
+ if (!this.mods.has(osuBase.ModFlashlight)) {
3271
+ return 0;
3272
+ }
3273
+ let flashlightRating = OsuRatingCalculator.calculateDifficultyRating(flashlightDifficultyValue);
3274
+ if (this.mods.has(osuBase.ModTouchDevice)) {
3275
+ flashlightRating = Math.pow(flashlightRating, 0.8);
3276
+ }
3277
+ if (this.mods.has(osuBase.ModRelax)) {
3278
+ flashlightRating *= 0.7;
3279
+ }
3280
+ else if (this.mods.has(osuBase.ModAutopilot)) {
3281
+ flashlightRating *= 0.4;
3282
+ }
3283
+ if (this.mods.has(osuBase.ModMagnetised)) {
3284
+ const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3285
+ flashlightRating *= 1 - magnetisedStrength;
3286
+ }
3287
+ if (this.mods.has(osuBase.ModDeflate)) {
3288
+ const deflateInitialScale = this.mods.get(osuBase.ModDeflate).startScale.value;
3289
+ flashlightRating *= osuBase.MathUtils.clamp(osuBase.Interpolation.reverseLerp(deflateInitialScale, 11, 1), 0.1, 1);
3290
+ }
3291
+ let ratingMultiplier = 1;
3292
+ // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
3293
+ ratingMultiplier *=
3294
+ 0.7 +
3295
+ 0.1 * Math.min(1, this.totalHits / 200) +
3296
+ (this.totalHits > 200
3297
+ ? 0.2 * Math.min(1, (this.totalHits - 200) / 200)
3298
+ : 0);
3299
+ // It is important to consider accuracy difficulty when scaling with accuracy.
3300
+ ratingMultiplier *=
3301
+ 0.98 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 2500;
3302
+ return flashlightRating * Math.sqrt(ratingMultiplier);
3303
+ }
3304
+ calculateAimVisibilityFactor() {
3305
+ const approachRateFactorEndpoint = 11.5;
3306
+ const mechanicalDifficultyFactor = osuBase.Interpolation.reverseLerp(this.mechanicalDifficultyRating, 5, 10);
3307
+ const approachRateFactorStartingPoint = osuBase.Interpolation.lerp(9, 10.33, mechanicalDifficultyFactor);
3308
+ return osuBase.Interpolation.reverseLerp(this.approachRate, approachRateFactorEndpoint, approachRateFactorStartingPoint);
3309
+ }
3310
+ calculateSpeedVisibilityFactor() {
3311
+ const approachRateFactorEndpoint = 11.5;
3312
+ const mechanicalDifficultyFactor = osuBase.Interpolation.reverseLerp(this.mechanicalDifficultyRating, 5, 10);
3313
+ const approachRateFactorStartingPoint = osuBase.Interpolation.lerp(10, 10.33, mechanicalDifficultyFactor);
3314
+ return osuBase.Interpolation.reverseLerp(this.approachRate, approachRateFactorEndpoint, approachRateFactorStartingPoint);
3315
+ }
3316
+ /**
3317
+ * Calculates a visibility bonus that is applicable to Hidden and Traceable.
3318
+ *
3319
+ * @param mods The mods applied to the calculation.
3320
+ * @param approachRate The approach rate of the beatmap.
3321
+ * @param visibilityFactor The visibility factor to apply.
3322
+ * @param sliderFactor The slider factor to apply.
3323
+ * @returns The visibility bonus multiplier.
3324
+ */
3325
+ static calculateVisibilityBonus(mods, approachRate, visibilityFactor = 1, sliderFactor = 1) {
3326
+ var _a, _b;
3327
+ const isAlwaysPartiallyVisible = (_b = (_a = mods.get(osuBase.ModHidden)) === null || _a === void 0 ? void 0 : _a.onlyFadeApproachCircles.value) !== null && _b !== void 0 ? _b : mods.has(osuBase.ModTraceable);
3328
+ // Start from normal curve, rewarding lower AR up to AR 7.
3329
+ // Traceable forcefully requires a lower reading bonus for now as it is post-applied in pp, which make
3330
+ // it multiplicative with the regular AR bonuses.
3331
+ // This means it has an advantage over Hidden, so we decrease the multiplier to compensate.
3332
+ // This should be removed once we are able to apply Traceable bonuses in star rating (requires real-time
3333
+ // difficulty calculations being possible).
3334
+ let readingBonus = (isAlwaysPartiallyVisible ? 0.025 : 0.04) *
3335
+ (12 - Math.max(approachRate, 7));
3336
+ readingBonus *= visibilityFactor;
3337
+ // We want to reward slideraim on low AR less.
3338
+ const sliderVisibilityFactor = Math.pow(sliderFactor, 3);
3339
+ // For AR up to 0, reduce reward for very low ARs when object is visible.
3340
+ if (approachRate < 7) {
3341
+ readingBonus +=
3342
+ (isAlwaysPartiallyVisible ? 0.02 : 0.045) *
3343
+ (7 - Math.max(approachRate, 0)) *
3344
+ sliderVisibilityFactor;
3345
+ }
3346
+ // Starting from AR 0, cap values so they won't grow to infinity.
3347
+ if (approachRate < 0) {
3348
+ readingBonus +=
3349
+ (isAlwaysPartiallyVisible ? 0.01 : 0.1) *
3350
+ (1 - Math.pow(1.5, approachRate)) *
3351
+ sliderVisibilityFactor;
3352
+ }
3353
+ return readingBonus;
3354
+ }
3355
+ static calculateDifficultyRating(difficultyValue) {
3356
+ return Math.sqrt(difficultyValue) * this.difficultyMultiplier;
3357
+ }
3358
+ }
3359
+ OsuRatingCalculator.difficultyMultiplier = 0.0675;
3360
+
3185
3361
  /**
3186
3362
  * An evaluator for calculating osu!standard Rhythm skill.
3187
3363
  */
@@ -3459,185 +3635,6 @@ class OsuSpeed extends OsuSkill {
3459
3635
  }
3460
3636
  }
3461
3637
 
3462
- class OsuRatingCalculator {
3463
- constructor(mods, totalHits, approachRate, overallDifficulty, mechanicalDifficultyRating, sliderFactor) {
3464
- this.mods = mods;
3465
- this.totalHits = totalHits;
3466
- this.approachRate = approachRate;
3467
- this.overallDifficulty = overallDifficulty;
3468
- this.mechanicalDifficultyRating = mechanicalDifficultyRating;
3469
- this.sliderFactor = sliderFactor;
3470
- }
3471
- computeAimRating(aimDifficultyValue) {
3472
- if (this.mods.has(osuBase.ModAutopilot)) {
3473
- return 0;
3474
- }
3475
- let aimRating = OsuRatingCalculator.calculateDifficultyRating(aimDifficultyValue);
3476
- if (this.mods.has(osuBase.ModTouchDevice)) {
3477
- aimRating = Math.pow(aimRating, 0.8);
3478
- }
3479
- if (this.mods.has(osuBase.ModRelax)) {
3480
- aimRating *= 0.9;
3481
- }
3482
- if (this.mods.has(osuBase.ModMagnetised)) {
3483
- const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3484
- aimRating *= 1 - magnetisedStrength;
3485
- }
3486
- let ratingMultiplier = 1;
3487
- const approachRateLengthBonus = 0.95 +
3488
- 0.4 * Math.min(1, this.totalHits / 2000) +
3489
- (this.totalHits > 2000
3490
- ? Math.log10(this.totalHits / 2000) * 0.5
3491
- : 0);
3492
- let approachRateFactor = 0;
3493
- if (this.approachRate > 10.33) {
3494
- approachRateFactor = 0.3 * (this.approachRate - 10.33);
3495
- }
3496
- else if (this.approachRate < 8) {
3497
- approachRateFactor = 0.05 * (8 - this.approachRate);
3498
- }
3499
- if (this.mods.has(osuBase.ModRelax)) {
3500
- approachRateFactor = 0;
3501
- }
3502
- // Buff for longer beatmaps with high AR.
3503
- ratingMultiplier += approachRateFactor * approachRateLengthBonus;
3504
- if (this.mods.has(osuBase.ModHidden)) {
3505
- const visibilityFactor = this.calculateAimVisibilityFactor();
3506
- ratingMultiplier += OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, visibilityFactor, this.sliderFactor);
3507
- }
3508
- // It is important to consider accuracy difficulty when scaling with accuracy.
3509
- ratingMultiplier *=
3510
- 0.98 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 2500;
3511
- return aimRating * Math.cbrt(ratingMultiplier);
3512
- }
3513
- computeSpeedRating(speedDifficultyValue) {
3514
- if (this.mods.has(osuBase.ModRelax)) {
3515
- return 0;
3516
- }
3517
- let speedRating = OsuRatingCalculator.calculateDifficultyRating(speedDifficultyValue);
3518
- if (this.mods.has(osuBase.ModAutopilot)) {
3519
- speedRating *= 0.5;
3520
- }
3521
- if (this.mods.has(osuBase.ModMagnetised)) {
3522
- // Reduce speed rating because of the distance scaling, with maximum reduction being 0.7.
3523
- const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3524
- speedRating *= 1 - magnetisedStrength * 0.3;
3525
- }
3526
- let ratingMultiplier = 1;
3527
- const approachRateLengthBonus = 0.95 +
3528
- 0.4 * Math.min(1, this.totalHits / 2000) +
3529
- (this.totalHits > 2000
3530
- ? Math.log10(this.totalHits / 2000) * 0.5
3531
- : 0);
3532
- let approachRateFactor = 0;
3533
- if (this.approachRate > 10.33) {
3534
- approachRateFactor = 0.3 * (this.approachRate - 10.33);
3535
- }
3536
- if (this.mods.has(osuBase.ModAutopilot)) {
3537
- approachRateFactor = 0;
3538
- }
3539
- // Buff for longer beatmaps with high AR.
3540
- ratingMultiplier += approachRateFactor * approachRateLengthBonus;
3541
- if (this.mods.has(osuBase.ModHidden)) {
3542
- const visibilityFactor = this.calculateSpeedVisibilityFactor();
3543
- ratingMultiplier += OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, visibilityFactor, this.sliderFactor);
3544
- }
3545
- ratingMultiplier *=
3546
- 0.95 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 750;
3547
- return speedRating * Math.cbrt(ratingMultiplier);
3548
- }
3549
- computeFlashlightRating(flashlightDifficultyValue) {
3550
- if (!this.mods.has(osuBase.ModFlashlight)) {
3551
- return 0;
3552
- }
3553
- let flashlightRating = OsuRatingCalculator.calculateDifficultyRating(flashlightDifficultyValue);
3554
- if (this.mods.has(osuBase.ModTouchDevice)) {
3555
- flashlightRating = Math.pow(flashlightRating, 0.8);
3556
- }
3557
- if (this.mods.has(osuBase.ModRelax)) {
3558
- flashlightRating *= 0.7;
3559
- }
3560
- else if (this.mods.has(osuBase.ModAutopilot)) {
3561
- flashlightRating *= 0.4;
3562
- }
3563
- if (this.mods.has(osuBase.ModMagnetised)) {
3564
- const magnetisedStrength = this.mods.get(osuBase.ModMagnetised).attractionStrength.value;
3565
- flashlightRating *= 1 - magnetisedStrength;
3566
- }
3567
- if (this.mods.has(osuBase.ModDeflate)) {
3568
- const deflateInitialScale = this.mods.get(osuBase.ModDeflate).startScale.value;
3569
- flashlightRating *= osuBase.MathUtils.clamp(osuBase.Interpolation.reverseLerp(deflateInitialScale, 11, 1), 0.1, 1);
3570
- }
3571
- let ratingMultiplier = 1;
3572
- // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
3573
- ratingMultiplier *=
3574
- 0.7 +
3575
- 0.1 * Math.min(1, this.totalHits / 200) +
3576
- (this.totalHits > 200
3577
- ? 0.2 * Math.min(1, (this.totalHits - 200) / 200)
3578
- : 0);
3579
- // It is important to consider accuracy difficulty when scaling with accuracy.
3580
- ratingMultiplier *=
3581
- 0.98 + Math.pow(Math.max(0, this.overallDifficulty), 2) / 2500;
3582
- return flashlightRating * Math.sqrt(ratingMultiplier);
3583
- }
3584
- calculateAimVisibilityFactor() {
3585
- const approachRateFactorEndpoint = 11.5;
3586
- const mechanicalDifficultyFactor = osuBase.Interpolation.reverseLerp(this.mechanicalDifficultyRating, 5, 10);
3587
- const approachRateFactorStartingPoint = osuBase.Interpolation.lerp(9, 10.33, mechanicalDifficultyFactor);
3588
- return osuBase.Interpolation.reverseLerp(this.approachRate, approachRateFactorEndpoint, approachRateFactorStartingPoint);
3589
- }
3590
- calculateSpeedVisibilityFactor() {
3591
- const approachRateFactorEndpoint = 11.5;
3592
- const mechanicalDifficultyFactor = osuBase.Interpolation.reverseLerp(this.mechanicalDifficultyRating, 5, 10);
3593
- const approachRateFactorStartingPoint = osuBase.Interpolation.lerp(10, 10.33, mechanicalDifficultyFactor);
3594
- return osuBase.Interpolation.reverseLerp(this.approachRate, approachRateFactorEndpoint, approachRateFactorStartingPoint);
3595
- }
3596
- /**
3597
- * Calculates a visibility bonus that is applicable to Hidden and Traceable.
3598
- *
3599
- * @param mods The mods applied to the calculation.
3600
- * @param approachRate The approach rate of the beatmap.
3601
- * @param visibilityFactor The visibility factor to apply.
3602
- * @param sliderFactor The slider factor to apply.
3603
- * @returns The visibility bonus multiplier.
3604
- */
3605
- static calculateVisibilityBonus(mods, approachRate, visibilityFactor = 1, sliderFactor = 1) {
3606
- var _a, _b;
3607
- const isAlwaysPartiallyVisible = (_b = (_a = mods.get(osuBase.ModHidden)) === null || _a === void 0 ? void 0 : _a.onlyFadeApproachCircles.value) !== null && _b !== void 0 ? _b : mods.has(osuBase.ModTraceable);
3608
- // Start from normal curve, rewarding lower AR up to AR 7.
3609
- // Traceable forcefully requires a lower reading bonus for now as it is post-applied in pp, which make
3610
- // it multiplicative with the regular AR bonuses.
3611
- // This means it has an advantage over Hidden, so we decrease the multiplier to compensate.
3612
- // This should be removed once we are able to apply Traceable bonuses in star rating (requires real-time
3613
- // difficulty calculations being possible).
3614
- let readingBonus = (isAlwaysPartiallyVisible ? 0.025 : 0.04) *
3615
- (12 - Math.max(approachRate, 7));
3616
- readingBonus *= visibilityFactor;
3617
- // We want to reward slideraim on low AR less.
3618
- const sliderVisibilityFactor = Math.pow(sliderFactor, 3);
3619
- // For AR up to 0, reduce reward for very low ARs when object is visible.
3620
- if (approachRate < 7) {
3621
- readingBonus +=
3622
- (isAlwaysPartiallyVisible ? 0.02 : 0.045) *
3623
- (7 - Math.max(approachRate, 0)) *
3624
- sliderVisibilityFactor;
3625
- }
3626
- // Starting from AR 0, cap values so they won't grow to infinity.
3627
- if (approachRate < 0) {
3628
- readingBonus +=
3629
- (isAlwaysPartiallyVisible ? 0.01 : 0.1) *
3630
- (1 - Math.pow(1.5, approachRate)) *
3631
- sliderVisibilityFactor;
3632
- }
3633
- return readingBonus;
3634
- }
3635
- static calculateDifficultyRating(difficultyValue) {
3636
- return Math.sqrt(difficultyValue) * this.difficultyMultiplier;
3637
- }
3638
- }
3639
- OsuRatingCalculator.difficultyMultiplier = 0.0675;
3640
-
3641
3638
  /**
3642
3639
  * A performance points calculator that calculates performance points for osu!standard gamemode.
3643
3640
  */
@@ -3660,6 +3657,11 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3660
3657
  * The flashlight performance value.
3661
3658
  */
3662
3659
  this.flashlight = 0;
3660
+ this.greatWindow = 0;
3661
+ this.okWindow = 0;
3662
+ this.mehWindow = 0;
3663
+ this.approachRate = 0;
3664
+ this.overallDifficulty = 0;
3663
3665
  this._effectiveMissCount = 0;
3664
3666
  this.speedDeviation = 0;
3665
3667
  }
@@ -3672,16 +3674,15 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3672
3674
  calculateValues() {
3673
3675
  this._effectiveMissCount = osuBase.MathUtils.clamp(this.calculateComboBasedEstimatedMissCount(), this.computedAccuracy.nmiss, this.totalHits);
3674
3676
  let { finalMultiplier } = OsuPerformanceCalculator;
3677
+ const { spinnerCount, approachRate: ar, overallDifficulty: od, clockRate, } = this.difficultyAttributes;
3675
3678
  if (this.mods.has(osuBase.ModNoFail)) {
3676
3679
  finalMultiplier *= Math.max(0.9, 1 - 0.02 * this._effectiveMissCount);
3677
3680
  }
3678
3681
  if (this.mods.has(osuBase.ModSpunOut)) {
3679
3682
  finalMultiplier *=
3680
- 1 -
3681
- Math.pow(this.difficultyAttributes.spinnerCount / this.totalHits, 0.85);
3683
+ 1 - Math.pow(spinnerCount / this.totalHits, 0.85);
3682
3684
  }
3683
3685
  if (this.mods.has(osuBase.ModRelax)) {
3684
- const { overallDifficulty: od } = this.difficultyAttributes;
3685
3686
  // Graph: https://www.desmos.com/calculator/vspzsop6td
3686
3687
  // We use OD13.3 as maximum since it's the value at which great hit window becomes 0.
3687
3688
  const n100Multiplier = 0.75 * Math.max(0, od > 0 ? 1 - od / 13.33 : 1);
@@ -3692,6 +3693,14 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3692
3693
  this.computedAccuracy.n100 * n100Multiplier +
3693
3694
  this.computedAccuracy.n50 * n50Multiplier, this.totalHits);
3694
3695
  }
3696
+ const hitWindow = new osuBase.OsuHitWindow(od);
3697
+ this.greatWindow = hitWindow.greatWindow / clockRate;
3698
+ this.okWindow = hitWindow.okWindow / clockRate;
3699
+ this.mehWindow = hitWindow.mehWindow / clockRate;
3700
+ this.approachRate =
3701
+ OsuDifficultyCalculator.calculateRateAdjustedApproachRate(ar, clockRate);
3702
+ this.overallDifficulty =
3703
+ OsuDifficultyCalculator.calculateRateAdjustedOverallDifficulty(od, clockRate);
3695
3704
  this.speedDeviation = this.calculateSpeedDeviation();
3696
3705
  this.aim = this.calculateAimValue();
3697
3706
  this.speed = this.calculateSpeedValue();
@@ -3759,7 +3768,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3759
3768
  else if (this.mods.has(osuBase.ModTraceable)) {
3760
3769
  aimValue *=
3761
3770
  1 +
3762
- OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.difficultyAttributes.approachRate, undefined, this.difficultyAttributes.sliderFactor);
3771
+ OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, undefined, this.difficultyAttributes.sliderFactor);
3763
3772
  }
3764
3773
  // Scale the aim value with accuracy.
3765
3774
  aimValue *= this.computedAccuracy.value();
@@ -3794,7 +3803,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3794
3803
  else if (this.mods.has(osuBase.ModTraceable)) {
3795
3804
  speedValue *=
3796
3805
  1 +
3797
- OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.difficultyAttributes.approachRate, undefined, this.difficultyAttributes.sliderFactor);
3806
+ OsuRatingCalculator.calculateVisibilityBonus(this.mods, this.approachRate, undefined, this.difficultyAttributes.sliderFactor);
3798
3807
  }
3799
3808
  // Calculate accuracy assuming the worst case scenario.
3800
3809
  const countGreat = this.computedAccuracy.n300;
@@ -3812,7 +3821,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3812
3821
  { n300: 0, nobjects: 1 });
3813
3822
  speedValue *= this.calculateSpeedHighDeviationNerf();
3814
3823
  // Scale the speed value with accuracy and OD.
3815
- speedValue *= Math.pow((this.computedAccuracy.value() + relevantAccuracy.value()) / 2, (14.5 - this.difficultyAttributes.overallDifficulty) / 2);
3824
+ speedValue *= Math.pow((this.computedAccuracy.value() + relevantAccuracy.value()) / 2, (14.5 - this.overallDifficulty) / 2);
3816
3825
  return speedValue;
3817
3826
  }
3818
3827
  /**
@@ -3831,7 +3840,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3831
3840
  const realAccuracy = new osuBase.Accuracy(Object.assign(Object.assign({}, this.computedAccuracy), { n300: this.computedAccuracy.n300 - (this.totalHits - ncircles) }));
3832
3841
  // Lots of arbitrary values from testing.
3833
3842
  // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
3834
- let accuracyValue = Math.pow(1.52163, this.difficultyAttributes.overallDifficulty) *
3843
+ let accuracyValue = Math.pow(1.52163, this.overallDifficulty) *
3835
3844
  // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points.
3836
3845
  Math.pow(realAccuracy.n300 < 0 ? 0 : realAccuracy.value(), 24) *
3837
3846
  2.83;
@@ -3845,8 +3854,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3845
3854
  // Decrease bonus for AR > 10.
3846
3855
  accuracyValue *=
3847
3856
  1 +
3848
- 0.08 *
3849
- osuBase.Interpolation.reverseLerp(this.difficultyAttributes.approachRate, 11.5, 10);
3857
+ 0.08 * osuBase.Interpolation.reverseLerp(this.approachRate, 11.5, 10);
3850
3858
  }
3851
3859
  if (this.mods.has(osuBase.ModFlashlight)) {
3852
3860
  accuracyValue *= 1.02;
@@ -3931,14 +3939,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3931
3939
  if (relevantCountGreat + relevantCountOk + relevantCountMeh <= 0) {
3932
3940
  return Number.POSITIVE_INFINITY;
3933
3941
  }
3934
- // Obtain the great, ok, and meh windows.
3935
- const { clockRate, overallDifficulty } = this.difficultyAttributes;
3936
- const hitWindow = new osuBase.OsuHitWindow(osuBase.OsuHitWindow.greatWindowToOD(
3937
- // Convert current OD to non clock rate-adjusted OD.
3938
- new osuBase.OsuHitWindow(overallDifficulty).greatWindow * clockRate));
3939
- const greatWindow = hitWindow.greatWindow / clockRate;
3940
- const okWindow = hitWindow.okWindow / clockRate;
3941
- const mehWindow = hitWindow.mehWindow / clockRate;
3942
+ const { greatWindow, okWindow, mehWindow } = this;
3942
3943
  // The sample proportion of successful hits.
3943
3944
  const n = Math.max(1, relevantCountGreat + relevantCountOk);
3944
3945
  const p = relevantCountGreat / n;
@@ -4334,7 +4335,7 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
4334
4335
  }
4335
4336
  static calculateRateAdjustedOverallDifficulty(overallDifficulty, clockRate) {
4336
4337
  const greatWindow = new osuBase.OsuHitWindow(overallDifficulty).greatWindow / clockRate;
4337
- return osuBase.OsuHitWindow.greatWindowToOD(greatWindow);
4338
+ return (79.5 - greatWindow) / 6;
4338
4339
  }
4339
4340
  }
4340
4341
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.86",
3
+ "version": "4.0.0-beta.88",
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.86"
36
+ "@rian8337/osu-base": "4.0.0-beta.88"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "62e3d012a8d3532fdb4410f6b48badda689b3a06"
41
+ "gitHead": "42c9324c999ed6dfe0804fe74131b870a72dd822"
42
42
  }
@@ -43,7 +43,7 @@ interface IDifficultyAttributes {
43
43
  */
44
44
  clockRate: number;
45
45
  /**
46
- * The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc), based on osu!standard judgement.
46
+ * The perceived overall difficulty **exclusive** of rate-adjusting mods (DT/HT/etc).
47
47
  *
48
48
  * Rate-adjusting mods don't directly affect the overall difficulty value, but have a perceived effect as a result of adjusting audio timing.
49
49
  */
@@ -1143,7 +1143,7 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator<IDroidDif
1143
1143
  * [Graph](https://www.desmos.com/calculator/z5l9ebrwpi)
1144
1144
  */
1145
1145
  private calculateTapHighDeviationNerf;
1146
- private getConvertedHitWindow;
1146
+ private getHitWindow;
1147
1147
  toString(): string;
1148
1148
  }
1149
1149
 
@@ -1316,7 +1316,7 @@ declare abstract class DroidTapEvaluator {
1316
1316
  */
1317
1317
  interface IOsuDifficultyAttributes extends IDifficultyAttributes {
1318
1318
  /**
1319
- * The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
1319
+ * The perceived approach rate **exclusive** of rate-adjusting mods (DT/HT/etc).
1320
1320
  *
1321
1321
  * Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
1322
1322
  */
@@ -1531,6 +1531,11 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator<IOsuDifficu
1531
1531
  */
1532
1532
  get effectiveMissCount(): number;
1533
1533
  static readonly finalMultiplier = 1.14;
1534
+ private greatWindow;
1535
+ private okWindow;
1536
+ private mehWindow;
1537
+ private approachRate;
1538
+ private overallDifficulty;
1534
1539
  private _effectiveMissCount;
1535
1540
  private speedDeviation;
1536
1541
  protected calculateValues(): void;