@rian8337/osu-difficulty-calculator 4.0.0-beta.56 → 4.0.0-beta.58

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
@@ -7,7 +7,7 @@ var osuBase = require('@rian8337/osu-base');
7
7
  */
8
8
  class DifficultyAttributes {
9
9
  constructor(cacheableAttributes) {
10
- this.mods = [];
10
+ this.mods = new osuBase.ModMap();
11
11
  this.starRating = 0;
12
12
  this.maxCombo = 0;
13
13
  this.aimDifficulty = 0;
@@ -47,7 +47,7 @@ class DifficultyAttributes {
47
47
  * @returns The cacheable attributes.
48
48
  */
49
49
  toCacheableAttributes() {
50
- return Object.assign(Object.assign({}, this), { mods: osuBase.ModUtil.serializeMods(this.mods) });
50
+ return Object.assign(Object.assign({}, this), { mods: this.mods.serializeMods() });
51
51
  }
52
52
  /**
53
53
  * Returns a string representation of the difficulty attributes.
@@ -79,7 +79,7 @@ class DifficultyCalculator {
79
79
  osuBase.ModAutopilot,
80
80
  ]);
81
81
  }
82
- calculate(beatmap, mods = []) {
82
+ calculate(beatmap, mods) {
83
83
  const playableBeatmap = beatmap instanceof osuBase.PlayableBeatmap
84
84
  ? beatmap
85
85
  : this.createPlayableBeatmap(beatmap, mods);
@@ -92,7 +92,7 @@ class DifficultyCalculator {
92
92
  }
93
93
  return this.createDifficultyAttributes(playableBeatmap, skills, objects);
94
94
  }
95
- calculateStrainPeaks(beatmap, mods = []) {
95
+ calculateStrainPeaks(beatmap, mods) {
96
96
  const playableBeatmap = beatmap instanceof osuBase.PlayableBeatmap
97
97
  ? beatmap
98
98
  : this.createPlayableBeatmap(beatmap, mods);
@@ -283,7 +283,7 @@ class DifficultyHitObject {
283
283
  }
284
284
  const fadeInStartTime = this.object.startTime - this.object.timePreempt;
285
285
  const fadeInDuration = this.object.timeFadeIn;
286
- if (mods.some((m) => m instanceof osuBase.ModHidden)) {
286
+ if (mods.has(osuBase.ModHidden)) {
287
287
  const fadeOutStartTime = fadeInStartTime + fadeInDuration;
288
288
  const fadeOutDuration = this.object.timePreempt * osuBase.ModHidden.fadeOutDurationMultiplier;
289
289
  return Math.min(osuBase.MathUtils.clamp((time - fadeInStartTime) / fadeInDuration, 0, 1), 1 -
@@ -568,8 +568,7 @@ class DroidDifficultyHitObject extends DifficultyHitObject {
568
568
  }
569
569
  opacityAt(time, mods) {
570
570
  // Traceable hides the primary piece of a hit circle (that is, its body), so consider it as fully invisible.
571
- if (this.object instanceof osuBase.Circle &&
572
- mods.some((m) => m instanceof osuBase.ModTraceable)) {
571
+ if (this.object instanceof osuBase.Circle && mods.has(osuBase.ModTraceable)) {
573
572
  return 0;
574
573
  }
575
574
  return super.opacityAt(time, mods);
@@ -1178,10 +1177,10 @@ class DroidFlashlightEvaluator {
1178
1177
  }
1179
1178
  result = Math.pow(smallDistNerf * result, 2);
1180
1179
  // Additional bonus for Hidden due to there being no approach circles.
1181
- if (mods.some((m) => m instanceof osuBase.ModHidden)) {
1180
+ if (mods.has(osuBase.ModHidden)) {
1182
1181
  result *= 1 + this.hiddenBonus;
1183
1182
  }
1184
- else if (mods.some((m) => m instanceof osuBase.ModTraceable)) {
1183
+ else if (mods.has(osuBase.ModTraceable)) {
1185
1184
  // Additional bonus for Traceable due to there being no primary or secondary object pieces.
1186
1185
  if (current.object instanceof osuBase.Circle) {
1187
1186
  // Additional bonus for hit circles due to there being no circle piece, which is the primary piece.
@@ -1465,7 +1464,7 @@ class DroidRhythm extends DroidSkill {
1465
1464
  this.starsPerDouble = 1.75;
1466
1465
  this.currentRhythmStrain = 0;
1467
1466
  this.currentRhythmMultiplier = 1;
1468
- this.useSliderAccuracy = mods.some((m) => m instanceof osuBase.ModScoreV2);
1467
+ this.useSliderAccuracy = mods.has(osuBase.ModScoreV2);
1469
1468
  }
1470
1469
  strainValueAt(current) {
1471
1470
  this.currentRhythmMultiplier =
@@ -1649,10 +1648,10 @@ class DroidVisualEvaluator {
1649
1648
  // Start with base density and give global bonus for Hidden and Traceable.
1650
1649
  // Add density caps for sanity.
1651
1650
  let strain;
1652
- if (mods.some((m) => m instanceof osuBase.ModHidden)) {
1651
+ if (mods.has(osuBase.ModHidden)) {
1653
1652
  strain = Math.min(30, Math.pow(current.noteDensity, 3));
1654
1653
  }
1655
- else if (mods.some((m) => m instanceof osuBase.ModTraceable)) {
1654
+ else if (mods.has(osuBase.ModTraceable)) {
1656
1655
  // Give more bonus for hit circles due to there being no circle piece.
1657
1656
  if (current.object instanceof osuBase.Circle) {
1658
1657
  strain = Math.min(25, Math.pow(current.noteDensity, 2.5));
@@ -1812,7 +1811,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1812
1811
  }
1813
1812
  createDifficultyAttributes(beatmap, skills, objects) {
1814
1813
  const attributes = new ExtendedDroidDifficultyAttributes();
1815
- attributes.mods = beatmap.mods.slice();
1814
+ attributes.mods = beatmap.mods;
1816
1815
  attributes.maxCombo = beatmap.maxCombo;
1817
1816
  attributes.clockRate = beatmap.speedMultiplier;
1818
1817
  attributes.hitCircleCount = beatmap.hitObjects.circles;
@@ -1823,14 +1822,14 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1823
1822
  this.populateRhythmAttributes(attributes, skills);
1824
1823
  this.populateFlashlightAttributes(attributes, skills);
1825
1824
  this.populateVisualAttributes(attributes, skills);
1826
- if (attributes.mods.some((m) => m instanceof osuBase.ModRelax)) {
1825
+ if (beatmap.mods.has(osuBase.ModRelax)) {
1827
1826
  attributes.aimDifficulty *= 0.9;
1828
1827
  attributes.tapDifficulty = 0;
1829
1828
  attributes.rhythmDifficulty = 0;
1830
1829
  attributes.flashlightDifficulty *= 0.7;
1831
1830
  attributes.visualDifficulty = 0;
1832
1831
  }
1833
- else if (attributes.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
1832
+ else if (beatmap.mods.has(osuBase.ModAutopilot)) {
1834
1833
  attributes.aimDifficulty = 0;
1835
1834
  attributes.flashlightDifficulty *= 0.3;
1836
1835
  attributes.visualDifficulty *= 0.8;
@@ -1855,7 +1854,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1855
1854
  attributes.starRating = 0;
1856
1855
  }
1857
1856
  let greatWindow;
1858
- if (attributes.mods.some((m) => m instanceof osuBase.ModPrecise)) {
1857
+ if (attributes.mods.has(osuBase.ModPrecise)) {
1859
1858
  greatWindow = new osuBase.PreciseDroidHitWindow(beatmap.difficulty.od)
1860
1859
  .greatWindow;
1861
1860
  }
@@ -1883,11 +1882,11 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1883
1882
  createSkills(beatmap) {
1884
1883
  const { mods } = beatmap;
1885
1884
  const skills = [];
1886
- if (!mods.some((m) => m instanceof osuBase.ModAutopilot)) {
1885
+ if (!mods.has(osuBase.ModAutopilot)) {
1887
1886
  skills.push(new DroidAim(mods, true));
1888
1887
  skills.push(new DroidAim(mods, false));
1889
1888
  }
1890
- if (!mods.some((m) => m instanceof osuBase.ModRelax)) {
1889
+ if (!mods.has(osuBase.ModRelax)) {
1891
1890
  // Tap and visual skills depend on rhythm skill, so we put it first
1892
1891
  skills.push(new DroidRhythm(mods));
1893
1892
  skills.push(new DroidTap(mods, true));
@@ -1895,7 +1894,7 @@ class DroidDifficultyCalculator extends DifficultyCalculator {
1895
1894
  skills.push(new DroidVisual(mods, true));
1896
1895
  skills.push(new DroidVisual(mods, false));
1897
1896
  }
1898
- if (mods.some((m) => m instanceof osuBase.ModFlashlight)) {
1897
+ if (mods.has(osuBase.ModFlashlight)) {
1899
1898
  skills.push(new DroidFlashlight(mods, true));
1900
1899
  skills.push(new DroidFlashlight(mods, false));
1901
1900
  }
@@ -2165,15 +2164,15 @@ class PerformanceCalculator {
2165
2164
  });
2166
2165
  }
2167
2166
  this.effectiveMissCount = this.calculateEffectiveMissCount(combo, maxCombo);
2168
- if (this.mods.some((m) => m instanceof osuBase.ModNoFail)) {
2167
+ if (this.mods.has(osuBase.ModNoFail)) {
2169
2168
  this.finalMultiplier *= Math.max(0.9, 1 - 0.02 * this.effectiveMissCount);
2170
2169
  }
2171
- if (this.mods.some((m) => m instanceof osuBase.ModSpunOut)) {
2170
+ if (this.mods.has(osuBase.ModSpunOut)) {
2172
2171
  this.finalMultiplier *=
2173
2172
  1 -
2174
2173
  Math.pow(this.difficultyAttributes.spinnerCount / this.totalHits, 0.85);
2175
2174
  }
2176
- if (this.mods.some((m) => m instanceof osuBase.ModRelax)) {
2175
+ if (this.mods.has(osuBase.ModRelax)) {
2177
2176
  // Graph: https://www.desmos.com/calculator/bc9eybdthb
2178
2177
  // We use OD13.3 as maximum since it's the value at which great hit window becomes 0.
2179
2178
  const n100Multiplier = Math.max(0, this.difficultyAttributes.overallDifficulty > 0
@@ -2514,12 +2513,11 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2514
2513
  * Calculates the accuracy performance value of the beatmap.
2515
2514
  */
2516
2515
  calculateAccuracyValue() {
2517
- if (this.mods.some((m) => m instanceof osuBase.ModRelax) ||
2518
- this.totalSuccessfulHits === 0) {
2516
+ if (this.mods.has(osuBase.ModRelax) || this.totalSuccessfulHits === 0) {
2519
2517
  return 0;
2520
2518
  }
2521
2519
  let accuracyValue = 650 * Math.exp(-0.1 * this._deviation);
2522
- const ncircles = this.mods.some((m) => m instanceof osuBase.ModScoreV2)
2520
+ const ncircles = this.mods.has(osuBase.ModScoreV2)
2523
2521
  ? this.totalHits - this.difficultyAttributes.spinnerCount
2524
2522
  : this.difficultyAttributes.hitCircleCount;
2525
2523
  // Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
@@ -2531,7 +2529,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2531
2529
  Math.exp(-(this.difficultyAttributes.rhythmDifficulty - 1) / 2));
2532
2530
  // Penalize accuracy pp after the first miss.
2533
2531
  accuracyValue *= Math.pow(0.97, Math.max(0, this.effectiveMissCount - 1));
2534
- if (this.mods.some((m) => m instanceof osuBase.ModFlashlight)) {
2532
+ if (this.mods.has(osuBase.ModFlashlight)) {
2535
2533
  accuracyValue *= 1.02;
2536
2534
  }
2537
2535
  return accuracyValue;
@@ -2540,7 +2538,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2540
2538
  * Calculates the flashlight performance value of the beatmap.
2541
2539
  */
2542
2540
  calculateFlashlightValue() {
2543
- if (!this.mods.some((m) => m instanceof osuBase.ModFlashlight)) {
2541
+ if (!this.mods.has(osuBase.ModFlashlight)) {
2544
2542
  return 0;
2545
2543
  }
2546
2544
  let flashlightValue = Math.pow(this.difficultyAttributes.flashlightDifficulty, 1.6) * 25;
@@ -2763,7 +2761,7 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2763
2761
  }
2764
2762
  getConvertedHitWindow() {
2765
2763
  const hitWindow300 = new osuBase.OsuHitWindow(this.difficultyAttributes.overallDifficulty).greatWindow;
2766
- if (this.mods.some((m) => m instanceof osuBase.ModPrecise)) {
2764
+ if (this.mods.has(osuBase.ModPrecise)) {
2767
2765
  return new osuBase.PreciseDroidHitWindow(osuBase.PreciseDroidHitWindow.greatWindowToOD(hitWindow300 * this.difficultyAttributes.clockRate));
2768
2766
  }
2769
2767
  else {
@@ -2786,37 +2784,6 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
2786
2784
  }
2787
2785
  }
2788
2786
 
2789
- /**
2790
- * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
2791
- * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
2792
- */
2793
- class OsuSkill extends StrainSkill {
2794
- difficultyValue() {
2795
- const strains = this.strainPeaks.slice().sort((a, b) => b - a);
2796
- if (this.reducedSectionCount > 0) {
2797
- // We are reducing the highest strains first to account for extreme difficulty spikes.
2798
- for (let i = 0; i < Math.min(strains.length, this.reducedSectionCount); ++i) {
2799
- const scale = Math.log10(osuBase.Interpolation.lerp(1, 10, osuBase.MathUtils.clamp(i / this.reducedSectionCount, 0, 1)));
2800
- strains[i] *= osuBase.Interpolation.lerp(this.reducedSectionBaseline, 1, scale);
2801
- }
2802
- strains.sort((a, b) => b - a);
2803
- }
2804
- // Difficulty is the weighted sum of the highest strains from every section.
2805
- // We're sorting from highest to lowest strain.
2806
- this.difficulty = 0;
2807
- let weight = 1;
2808
- for (const strain of strains) {
2809
- const addition = strain * weight;
2810
- if (this.difficulty + addition === this.difficulty) {
2811
- break;
2812
- }
2813
- this.difficulty += addition;
2814
- weight *= this.decayWeight;
2815
- }
2816
- return this.difficulty;
2817
- }
2818
- }
2819
-
2820
2787
  /**
2821
2788
  * Represents an osu!standard hit object with difficulty calculation values.
2822
2789
  */
@@ -2983,6 +2950,37 @@ OsuAimEvaluator.sliderMultiplier = 1.35;
2983
2950
  OsuAimEvaluator.velocityChangeMultiplier = 0.75;
2984
2951
  OsuAimEvaluator.wiggleMultiplier = 1.02;
2985
2952
 
2953
+ /**
2954
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
2955
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
2956
+ */
2957
+ class OsuSkill extends StrainSkill {
2958
+ difficultyValue() {
2959
+ const strains = this.strainPeaks.slice().sort((a, b) => b - a);
2960
+ if (this.reducedSectionCount > 0) {
2961
+ // We are reducing the highest strains first to account for extreme difficulty spikes.
2962
+ for (let i = 0; i < Math.min(strains.length, this.reducedSectionCount); ++i) {
2963
+ const scale = Math.log10(osuBase.Interpolation.lerp(1, 10, osuBase.MathUtils.clamp(i / this.reducedSectionCount, 0, 1)));
2964
+ strains[i] *= osuBase.Interpolation.lerp(this.reducedSectionBaseline, 1, scale);
2965
+ }
2966
+ strains.sort((a, b) => b - a);
2967
+ }
2968
+ // Difficulty is the weighted sum of the highest strains from every section.
2969
+ // We're sorting from highest to lowest strain.
2970
+ this.difficulty = 0;
2971
+ let weight = 1;
2972
+ for (const strain of strains) {
2973
+ const addition = strain * weight;
2974
+ if (this.difficulty + addition === this.difficulty) {
2975
+ break;
2976
+ }
2977
+ this.difficulty += addition;
2978
+ weight *= this.decayWeight;
2979
+ }
2980
+ return this.difficulty;
2981
+ }
2982
+ }
2983
+
2986
2984
  /**
2987
2985
  * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
2988
2986
  */
@@ -3124,7 +3122,7 @@ class OsuFlashlightEvaluator {
3124
3122
  }
3125
3123
  result = Math.pow(smallDistNerf * result, 2);
3126
3124
  // Additional bonus for Hidden due to there being no approach circles.
3127
- if (mods.some((m) => m instanceof osuBase.ModHidden)) {
3125
+ if (mods.has(osuBase.ModHidden)) {
3128
3126
  result *= 1 + this.hiddenBonus;
3129
3127
  }
3130
3128
  // Nerf patterns with repeated angles.
@@ -3373,7 +3371,7 @@ class OsuSpeedEvaluator {
3373
3371
  // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
3374
3372
  let distanceBonus = Math.pow(distance / this.SINGLE_SPACING_THRESHOLD, 3.95) *
3375
3373
  this.DISTANCE_MULTIPLIER;
3376
- if (mods.some((m) => m instanceof osuBase.ModAutopilot)) {
3374
+ if (mods.has(osuBase.ModAutopilot)) {
3377
3375
  distanceBonus = 0;
3378
3376
  }
3379
3377
  // Base difficulty with all bonuses
@@ -3463,7 +3461,7 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
3463
3461
  }
3464
3462
  createDifficultyAttributes(beatmap, skills) {
3465
3463
  const attributes = new OsuDifficultyAttributes();
3466
- attributes.mods = beatmap.mods.slice();
3464
+ attributes.mods = beatmap.mods;
3467
3465
  attributes.maxCombo = beatmap.maxCombo;
3468
3466
  attributes.clockRate = beatmap.speedMultiplier;
3469
3467
  attributes.hitCircleCount = beatmap.hitObjects.circles;
@@ -3472,12 +3470,12 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
3472
3470
  this.populateAimAttributes(attributes, skills);
3473
3471
  this.populateSpeedAttributes(attributes, skills);
3474
3472
  this.populateFlashlightAttributes(attributes, skills);
3475
- if (attributes.mods.some((m) => m instanceof osuBase.ModRelax)) {
3473
+ if (attributes.mods.has(osuBase.ModRelax)) {
3476
3474
  attributes.aimDifficulty *= 0.9;
3477
3475
  attributes.speedDifficulty = 0;
3478
3476
  attributes.flashlightDifficulty *= 0.7;
3479
3477
  }
3480
- else if (attributes.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
3478
+ else if (attributes.mods.has(osuBase.ModAutopilot)) {
3481
3479
  attributes.aimDifficulty = 0;
3482
3480
  attributes.speedDifficulty *= 0.5;
3483
3481
  attributes.flashlightDifficulty *= 0.4;
@@ -3524,14 +3522,14 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
3524
3522
  createSkills(beatmap) {
3525
3523
  const { mods } = beatmap;
3526
3524
  const skills = [];
3527
- if (!mods.some((m) => m instanceof osuBase.ModAutopilot)) {
3525
+ if (!mods.has(osuBase.ModAutopilot)) {
3528
3526
  skills.push(new OsuAim(mods, true));
3529
3527
  skills.push(new OsuAim(mods, false));
3530
3528
  }
3531
- if (!mods.some((m) => m instanceof osuBase.ModRelax)) {
3529
+ if (!mods.has(osuBase.ModRelax)) {
3532
3530
  skills.push(new OsuSpeed(mods));
3533
3531
  }
3534
- if (mods.some((m) => m instanceof osuBase.ModFlashlight)) {
3532
+ if (mods.has(osuBase.ModFlashlight)) {
3535
3533
  skills.push(new OsuFlashlight(mods));
3536
3534
  }
3537
3535
  return skills;
@@ -3632,7 +3630,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3632
3630
  * Calculates the aim performance value of the beatmap.
3633
3631
  */
3634
3632
  calculateAimValue() {
3635
- if (this.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
3633
+ if (this.mods.has(osuBase.ModAutopilot)) {
3636
3634
  return 0;
3637
3635
  }
3638
3636
  let aimValue = this.baseValue(this.difficultyAttributes.aimDifficulty);
@@ -3652,7 +3650,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3652
3650
  }
3653
3651
  aimValue *= this.calculateStrainBasedMissPenalty(this.difficultyAttributes.aimDifficultStrainCount);
3654
3652
  const calculatedAR = this.difficultyAttributes.approachRate;
3655
- if (!this.mods.some((m) => m instanceof osuBase.ModRelax)) {
3653
+ if (!this.mods.has(osuBase.ModRelax)) {
3656
3654
  // AR scaling
3657
3655
  let arFactor = 0;
3658
3656
  if (calculatedAR > 10.33) {
@@ -3665,7 +3663,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3665
3663
  aimValue *= 1 + arFactor * lengthBonus;
3666
3664
  }
3667
3665
  // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
3668
- if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3666
+ if (this.mods.has(osuBase.ModHidden) || this.mods.has(osuBase.ModTraceable)) {
3669
3667
  aimValue *= 1 + 0.04 * (12 - calculatedAR);
3670
3668
  }
3671
3669
  // Scale the aim value with slider factor to nerf very likely dropped sliderends.
@@ -3681,7 +3679,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3681
3679
  * Calculates the speed performance value of the beatmap.
3682
3680
  */
3683
3681
  calculateSpeedValue() {
3684
- if (this.mods.some((m) => m instanceof osuBase.ModRelax) ||
3682
+ if (this.mods.has(osuBase.ModRelax) ||
3685
3683
  this.speedDeviation === Number.POSITIVE_INFINITY) {
3686
3684
  return 0;
3687
3685
  }
@@ -3695,12 +3693,11 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3695
3693
  speedValue *= this.calculateStrainBasedMissPenalty(this.difficultyAttributes.speedDifficultStrainCount);
3696
3694
  // AR scaling
3697
3695
  const calculatedAR = this.difficultyAttributes.approachRate;
3698
- if (calculatedAR > 10.33 &&
3699
- !this.mods.some((m) => m instanceof osuBase.ModAutopilot)) {
3696
+ if (calculatedAR > 10.33 && !this.mods.has(osuBase.ModAutopilot)) {
3700
3697
  // Buff for longer maps with high AR.
3701
3698
  speedValue *= 1 + 0.3 * (calculatedAR - 10.33) * lengthBonus;
3702
3699
  }
3703
- if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3700
+ if (this.mods.has(osuBase.ModHidden) || this.mods.has(osuBase.ModTraceable)) {
3704
3701
  speedValue *= 1 + 0.04 * (12 - calculatedAR);
3705
3702
  }
3706
3703
  // Calculate accuracy assuming the worst case scenario.
@@ -3732,10 +3729,10 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3732
3729
  * Calculates the accuracy performance value of the beatmap.
3733
3730
  */
3734
3731
  calculateAccuracyValue() {
3735
- if (this.mods.some((m) => m instanceof osuBase.ModRelax)) {
3732
+ if (this.mods.has(osuBase.ModRelax)) {
3736
3733
  return 0;
3737
3734
  }
3738
- const ncircles = this.mods.some((m) => m instanceof osuBase.ModScoreV2)
3735
+ const ncircles = this.mods.has(osuBase.ModScoreV2)
3739
3736
  ? this.totalHits - this.difficultyAttributes.spinnerCount
3740
3737
  : this.difficultyAttributes.hitCircleCount;
3741
3738
  if (ncircles === 0) {
@@ -3750,10 +3747,10 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3750
3747
  2.83;
3751
3748
  // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
3752
3749
  accuracyValue *= Math.min(1.15, Math.pow(ncircles / 1000, 0.3));
3753
- if (this.mods.some((m) => m instanceof osuBase.ModHidden || m instanceof osuBase.ModTraceable)) {
3750
+ if (this.mods.has(osuBase.ModHidden) || this.mods.has(osuBase.ModTraceable)) {
3754
3751
  accuracyValue *= 1.08;
3755
3752
  }
3756
- if (this.mods.some((m) => m instanceof osuBase.ModFlashlight)) {
3753
+ if (this.mods.has(osuBase.ModFlashlight)) {
3757
3754
  accuracyValue *= 1.02;
3758
3755
  }
3759
3756
  return accuracyValue;
@@ -3762,7 +3759,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
3762
3759
  * Calculates the flashlight performance value of the beatmap.
3763
3760
  */
3764
3761
  calculateFlashlightValue() {
3765
- if (!this.mods.some((m) => m instanceof osuBase.ModFlashlight)) {
3762
+ if (!this.mods.has(osuBase.ModFlashlight)) {
3766
3763
  return 0;
3767
3764
  }
3768
3765
  let flashlightValue = Math.pow(this.difficultyAttributes.flashlightDifficulty, 2) * 25;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rian8337/osu-difficulty-calculator",
3
- "version": "4.0.0-beta.56",
3
+ "version": "4.0.0-beta.58",
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.56"
36
+ "@rian8337/osu-base": "^4.0.0-beta.58"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "330ca82968253d60fd3cb8ed0d769718028a8688"
41
+ "gitHead": "4a5f36a02a668f15fa22b558eeeeb13237105212"
42
42
  }
@@ -1,4 +1,4 @@
1
- import { Mod, SerializedMod, PlaceableHitObject, Modes, PlayableBeatmap, Beatmap, DroidPlayableBeatmap, Accuracy, OsuPlayableBeatmap } from '@rian8337/osu-base';
1
+ import { ModMap, SerializedMod, PlaceableHitObject, Modes, PlayableBeatmap, Mod, Beatmap, DroidPlayableBeatmap, Accuracy, OsuPlayableBeatmap } from '@rian8337/osu-base';
2
2
 
3
3
  /**
4
4
  * Holds data that can be used to calculate performance points.
@@ -7,7 +7,7 @@ interface IDifficultyAttributes {
7
7
  /**
8
8
  * The mods which were applied to the beatmap.
9
9
  */
10
- mods: Mod[];
10
+ mods: ModMap;
11
11
  /**
12
12
  * The combined star rating of all skills.
13
13
  */
@@ -73,7 +73,7 @@ interface IDifficultyAttributes {
73
73
  /**
74
74
  * Represents difficulty attributes that can be cached.
75
75
  */
76
- type CacheableDifficultyAttributes<T extends IDifficultyAttributes> = Omit<T, "mods"> & {
76
+ type CacheableDifficultyAttributes<T extends IDifficultyAttributes> = Omit<T, "mods" | "toCacheableAttributes"> & {
77
77
  /**
78
78
  * The mods which were applied to the beatmap.
79
79
  */
@@ -84,7 +84,7 @@ type CacheableDifficultyAttributes<T extends IDifficultyAttributes> = Omit<T, "m
84
84
  * Holds data that can be used to calculate performance points.
85
85
  */
86
86
  declare abstract class DifficultyAttributes implements IDifficultyAttributes {
87
- mods: Mod[];
87
+ mods: ModMap;
88
88
  starRating: number;
89
89
  maxCombo: number;
90
90
  aimDifficulty: number;
@@ -104,7 +104,7 @@ declare abstract class DifficultyAttributes implements IDifficultyAttributes {
104
104
  *
105
105
  * @returns The cacheable attributes.
106
106
  */
107
- toCacheableAttributes(): CacheableDifficultyAttributes<IDifficultyAttributes>;
107
+ toCacheableAttributes(): CacheableDifficultyAttributes<this>;
108
108
  /**
109
109
  * Returns a string representation of the difficulty attributes.
110
110
  */
@@ -262,7 +262,7 @@ declare abstract class DifficultyHitObject {
262
262
  * @param mods The mods used.
263
263
  * @returns The opacity of the hitobject at the given time.
264
264
  */
265
- opacityAt(time: number, mods: readonly Mod[]): number;
265
+ opacityAt(time: number, mods: ModMap): number;
266
266
  /**
267
267
  * How possible is it to doubletap this object together with the next one and get perfect
268
268
  * judgement in range from 0 to 1.
@@ -285,8 +285,8 @@ declare abstract class Skill {
285
285
  /**
286
286
  * The mods that this skill processes.
287
287
  */
288
- protected readonly mods: readonly Mod[];
289
- constructor(mods: readonly Mod[]);
288
+ protected readonly mods: ModMap;
289
+ constructor(mods: ModMap);
290
290
  /**
291
291
  * Processes a hitobject.
292
292
  *
@@ -436,7 +436,7 @@ declare abstract class DifficultyCalculator<TBeatmap extends PlayableBeatmap, TH
436
436
  * @param mods The `Mod`s to apply to the beatmap. Defaults to No Mod.
437
437
  * @returns A `DifficultyAttributes` object describing the difficulty of the `Beatmap`.
438
438
  */
439
- calculate(beatmap: Beatmap, mods?: Mod[]): TAttributes;
439
+ calculate(beatmap: Beatmap, mods?: ModMap): TAttributes;
440
440
  /**
441
441
  * Obtains the strain peaks of a `PlayableBeatmap`.
442
442
  *
@@ -451,7 +451,7 @@ declare abstract class DifficultyCalculator<TBeatmap extends PlayableBeatmap, TH
451
451
  * @param mods The `Mod`s to apply to the beatmap. Defaults to No Mod.
452
452
  * @returns The strain peaks of the `Beatmap`.
453
453
  */
454
- calculateStrainPeaks(beatmap: Beatmap, mods: Mod[]): StrainPeaks;
454
+ calculateStrainPeaks(beatmap: Beatmap, mods?: ModMap): StrainPeaks;
455
455
  /**
456
456
  * Creates the `Skill`s to calculate the difficulty of a `PlayableBeatmap`.
457
457
  *
@@ -488,7 +488,7 @@ declare abstract class DifficultyCalculator<TBeatmap extends PlayableBeatmap, TH
488
488
  * @param mods The `Mod`s to apply to the `Beatmap`.
489
489
  * @returns The `PlayableBeatmap`.
490
490
  */
491
- protected abstract createPlayableBeatmap(beatmap: Beatmap, mods: Mod[]): TBeatmap;
491
+ protected abstract createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): TBeatmap;
492
492
  /**
493
493
  * Calculates the base rating of a `Skill`.
494
494
  *
@@ -524,27 +524,6 @@ interface DifficultSlider {
524
524
  readonly difficultyRating: number;
525
525
  }
526
526
 
527
- /**
528
- * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
529
- * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
530
- */
531
- declare abstract class DroidSkill extends StrainSkill {
532
- /**
533
- * The bonus multiplier that is given for a sequence of notes of equal difficulty.
534
- */
535
- protected abstract readonly starsPerDouble: number;
536
- process(current: DifficultyHitObject): void;
537
- difficultyValue(): number;
538
- /**
539
- * Gets the strain of a hitobject.
540
- *
541
- * @param current The hitobject to get the strain from.
542
- * @returns The strain of the hitobject.
543
- */
544
- protected abstract getObjectStrain(current: DifficultyHitObject): number;
545
- protected calculateCurrentSectionStart(current: DifficultyHitObject): number;
546
- }
547
-
548
527
  /**
549
528
  * Represents an osu!droid hit object with difficulty calculation values.
550
529
  */
@@ -608,7 +587,7 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
608
587
  */
609
588
  constructor(object: PlaceableHitObject, lastObject: PlaceableHitObject | null, lastLastObject: PlaceableHitObject | null, difficultyHitObjects: readonly DifficultyHitObject[], clockRate: number, index: number);
610
589
  computeProperties(clockRate: number, hitObjects: readonly PlaceableHitObject[]): void;
611
- opacityAt(time: number, mods: readonly Mod[]): number;
590
+ opacityAt(time: number, mods: ModMap): number;
612
591
  previous(backwardsIndex: number): this | null;
613
592
  next(forwardsIndex: number): this | null;
614
593
  /**
@@ -628,6 +607,27 @@ declare class DroidDifficultyHitObject extends DifficultyHitObject {
628
607
  private applyToOverlappingFactor;
629
608
  }
630
609
 
610
+ /**
611
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
612
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
613
+ */
614
+ declare abstract class DroidSkill extends StrainSkill {
615
+ /**
616
+ * The bonus multiplier that is given for a sequence of notes of equal difficulty.
617
+ */
618
+ protected abstract readonly starsPerDouble: number;
619
+ process(current: DifficultyHitObject): void;
620
+ difficultyValue(): number;
621
+ /**
622
+ * Gets the strain of a hitobject.
623
+ *
624
+ * @param current The hitobject to get the strain from.
625
+ * @returns The strain of the hitobject.
626
+ */
627
+ protected abstract getObjectStrain(current: DifficultyHitObject): number;
628
+ protected calculateCurrentSectionStart(current: DifficultyHitObject): number;
629
+ }
630
+
631
631
  /**
632
632
  * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
633
633
  */
@@ -640,7 +640,7 @@ declare class DroidAim extends DroidSkill {
640
640
  private currentAimStrain;
641
641
  private readonly sliderStrains;
642
642
  readonly withSliders: boolean;
643
- constructor(mods: readonly Mod[], withSliders: boolean);
643
+ constructor(mods: ModMap, withSliders: boolean);
644
644
  /**
645
645
  * Obtains the amount of sliders that are considered difficult in terms of relative strain.
646
646
  */
@@ -834,7 +834,7 @@ declare class DroidDifficultyCalculator extends DifficultyCalculator<DroidPlayab
834
834
  constructor();
835
835
  retainDifficultyAdjustmentMods(mods: Mod[]): Mod[];
836
836
  protected createDifficultyAttributes(beatmap: DroidPlayableBeatmap, skills: Skill[], objects: DroidDifficultyHitObject[]): ExtendedDroidDifficultyAttributes;
837
- protected createPlayableBeatmap(beatmap: Beatmap, mods: Mod[]): DroidPlayableBeatmap;
837
+ protected createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): DroidPlayableBeatmap;
838
838
  protected createDifficultyHitObjects(beatmap: DroidPlayableBeatmap): DroidDifficultyHitObject[];
839
839
  protected createSkills(beatmap: DroidPlayableBeatmap): DroidSkill[];
840
840
  protected createStrainPeakSkills(beatmap: DroidPlayableBeatmap): StrainSkill[];
@@ -856,7 +856,7 @@ declare class DroidFlashlight extends DroidSkill {
856
856
  private readonly skillMultiplier;
857
857
  private currentFlashlightStrain;
858
858
  readonly withSliders: boolean;
859
- constructor(mods: readonly Mod[], withSliders: boolean);
859
+ constructor(mods: ModMap, withSliders: boolean);
860
860
  protected strainValueAt(current: DroidDifficultyHitObject): number;
861
861
  protected calculateInitialStrain(time: number, current: DifficultyHitObject): number;
862
862
  protected getObjectStrain(): number;
@@ -888,7 +888,7 @@ declare abstract class DroidFlashlightEvaluator {
888
888
  * @param mods The mods used.
889
889
  * @param withSliders Whether to take slider difficulty into account.
890
890
  */
891
- static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: readonly Mod[], withSliders: boolean): number;
891
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: ModMap, withSliders: boolean): number;
892
892
  }
893
893
 
894
894
  /**
@@ -944,7 +944,7 @@ declare abstract class PerformanceCalculator<T extends IDifficultyAttributes> {
944
944
  /**
945
945
  * The mods that were used.
946
946
  */
947
- protected readonly mods: Mod[];
947
+ protected readonly mods: ModMap;
948
948
  /**
949
949
  * The global multiplier to be applied to the final performance value.
950
950
  *
@@ -1199,7 +1199,7 @@ declare class DroidRhythm extends DroidSkill {
1199
1199
  private readonly useSliderAccuracy;
1200
1200
  private currentRhythmStrain;
1201
1201
  private currentRhythmMultiplier;
1202
- constructor(mods: readonly Mod[]);
1202
+ constructor(mods: ModMap);
1203
1203
  protected strainValueAt(current: DroidDifficultyHitObject): number;
1204
1204
  protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
1205
1205
  protected getObjectStrain(): number;
@@ -1242,7 +1242,7 @@ declare class DroidTap extends DroidSkill {
1242
1242
  get objectDeltaTimes(): readonly number[];
1243
1243
  readonly considerCheesability: boolean;
1244
1244
  private readonly strainTimeCap?;
1245
- constructor(mods: readonly Mod[], considerCheesability: boolean, strainTimeCap?: number);
1245
+ constructor(mods: ModMap, considerCheesability: boolean, strainTimeCap?: number);
1246
1246
  /**
1247
1247
  * The amount of notes that are relevant to the difficulty.
1248
1248
  */
@@ -1293,7 +1293,7 @@ declare class DroidVisual extends DroidSkill {
1293
1293
  private currentRhythmMultiplier;
1294
1294
  private readonly skillMultiplier;
1295
1295
  readonly withSliders: boolean;
1296
- constructor(mods: readonly Mod[], withSliders: boolean);
1296
+ constructor(mods: ModMap, withSliders: boolean);
1297
1297
  protected strainValueAt(current: DroidDifficultyHitObject): number;
1298
1298
  protected calculateInitialStrain(time: number, current: DroidDifficultyHitObject): number;
1299
1299
  protected getObjectStrain(): number;
@@ -1319,7 +1319,7 @@ declare abstract class DroidVisualEvaluator {
1319
1319
  * @param mods The mods used.
1320
1320
  * @param withSliders Whether to take slider difficulty into account.
1321
1321
  */
1322
- static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: readonly Mod[], withSliders: boolean): number;
1322
+ static evaluateDifficultyOf(current: DroidDifficultyHitObject, mods: ModMap, withSliders: boolean): number;
1323
1323
  }
1324
1324
 
1325
1325
  /**
@@ -1342,18 +1342,6 @@ interface IOsuDifficultyAttributes extends IDifficultyAttributes {
1342
1342
  speedDifficultStrainCount: number;
1343
1343
  }
1344
1344
 
1345
- /**
1346
- * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
1347
- * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
1348
- */
1349
- declare abstract class OsuSkill extends StrainSkill {
1350
- /**
1351
- * The weight by which each strain value decays.
1352
- */
1353
- protected abstract readonly decayWeight: number;
1354
- difficultyValue(): number;
1355
- }
1356
-
1357
1345
  /**
1358
1346
  * Represents an osu!standard hit object with difficulty calculation values.
1359
1347
  */
@@ -1371,6 +1359,18 @@ declare class OsuDifficultyHitObject extends DifficultyHitObject {
1371
1359
  protected get scalingFactor(): number;
1372
1360
  }
1373
1361
 
1362
+ /**
1363
+ * Used to processes strain values of difficulty hitobjects, keep track of strain levels caused by the processed objects
1364
+ * and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
1365
+ */
1366
+ declare abstract class OsuSkill extends StrainSkill {
1367
+ /**
1368
+ * The weight by which each strain value decays.
1369
+ */
1370
+ protected abstract readonly decayWeight: number;
1371
+ difficultyValue(): number;
1372
+ }
1373
+
1374
1374
  /**
1375
1375
  * Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
1376
1376
  */
@@ -1383,7 +1383,7 @@ declare class OsuAim extends OsuSkill {
1383
1383
  private readonly skillMultiplier;
1384
1384
  private readonly sliderStrains;
1385
1385
  readonly withSliders: boolean;
1386
- constructor(mods: readonly Mod[], withSliders: boolean);
1386
+ constructor(mods: ModMap, withSliders: boolean);
1387
1387
  /**
1388
1388
  * Obtains the amount of sliders that are considered difficult in terms of relative strain.
1389
1389
  */
@@ -1440,7 +1440,7 @@ declare class OsuDifficultyCalculator extends DifficultyCalculator<OsuPlayableBe
1440
1440
  constructor();
1441
1441
  retainDifficultyAdjustmentMods(mods: Mod[]): Mod[];
1442
1442
  protected createDifficultyAttributes(beatmap: OsuPlayableBeatmap, skills: Skill[]): OsuDifficultyAttributes;
1443
- protected createPlayableBeatmap(beatmap: Beatmap, mods: Mod[]): OsuPlayableBeatmap;
1443
+ protected createPlayableBeatmap(beatmap: Beatmap, mods?: ModMap): OsuPlayableBeatmap;
1444
1444
  protected createDifficultyHitObjects(beatmap: OsuPlayableBeatmap): OsuDifficultyHitObject[];
1445
1445
  protected createSkills(beatmap: OsuPlayableBeatmap): OsuSkill[];
1446
1446
  protected createStrainPeakSkills(beatmap: OsuPlayableBeatmap): StrainSkill[];
@@ -1486,7 +1486,7 @@ declare abstract class OsuFlashlightEvaluator {
1486
1486
  * @param current The current object.
1487
1487
  * @param mods The mods used.
1488
1488
  */
1489
- static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: readonly Mod[]): number;
1489
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: ModMap): number;
1490
1490
  }
1491
1491
 
1492
1492
  /**
@@ -1622,7 +1622,7 @@ declare abstract class OsuSpeedEvaluator {
1622
1622
  * @param current The current object.
1623
1623
  * @param mods The mods applied.
1624
1624
  */
1625
- static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: readonly Mod[]): number;
1625
+ static evaluateDifficultyOf(current: OsuDifficultyHitObject, mods: ModMap): number;
1626
1626
  }
1627
1627
 
1628
1628
  export { type CacheableDifficultyAttributes, type DifficultSlider, DifficultyAttributes, DifficultyCalculator, DifficultyHitObject, DroidAim, DroidAimEvaluator, DroidDifficultyAttributes, DroidDifficultyCalculator, DroidDifficultyHitObject, DroidFlashlight, DroidFlashlightEvaluator, DroidPerformanceCalculator, DroidRhythm, DroidRhythmEvaluator, DroidTap, DroidTapEvaluator, DroidVisual, DroidVisualEvaluator, ExtendedDroidDifficultyAttributes, type HighStrainSection, type IDifficultyAttributes, type IDroidDifficultyAttributes, type IExtendedDroidDifficultyAttributes, type IOsuDifficultyAttributes, OsuAim, OsuAimEvaluator, OsuDifficultyAttributes, OsuDifficultyCalculator, OsuDifficultyHitObject, OsuFlashlight, OsuFlashlightEvaluator, OsuPerformanceCalculator, OsuRhythmEvaluator, OsuSpeed, OsuSpeedEvaluator, type PerformanceCalculationOptions, PerformanceCalculator, type StrainPeaks };