@rian8337/osu-difficulty-calculator 4.0.0-beta.30 → 4.0.0-beta.32
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 +174 -152
- package/package.json +3 -3
- package/typings/index.d.ts +46 -50
package/dist/index.js
CHANGED
|
@@ -664,11 +664,19 @@ class StrainSkill extends Skill {
|
|
|
664
664
|
* Strain peaks are stored here.
|
|
665
665
|
*/
|
|
666
666
|
this.strainPeaks = [];
|
|
667
|
+
this._objectStrains = [];
|
|
668
|
+
this.difficulty = 0;
|
|
667
669
|
this.sectionLength = 400;
|
|
668
670
|
this.currentStrain = 0;
|
|
669
671
|
this.currentSectionPeak = 0;
|
|
670
672
|
this.currentSectionEnd = 0;
|
|
671
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* The strains of hitobjects.
|
|
676
|
+
*/
|
|
677
|
+
get objectStrains() {
|
|
678
|
+
return this._objectStrains;
|
|
679
|
+
}
|
|
672
680
|
process(current) {
|
|
673
681
|
// The first object doesn't generate a strain, so we begin with an incremented section end
|
|
674
682
|
if (current.index === 0) {
|
|
@@ -694,6 +702,21 @@ class StrainSkill extends Skill {
|
|
|
694
702
|
saveCurrentPeak() {
|
|
695
703
|
this.strainPeaks.push(this.currentSectionPeak);
|
|
696
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Returns the number of strains weighed against the top strain.
|
|
707
|
+
*
|
|
708
|
+
* The result is scaled by clock rate as it affects the total number of strains.
|
|
709
|
+
*/
|
|
710
|
+
countDifficultStrains() {
|
|
711
|
+
if (this.difficulty === 0) {
|
|
712
|
+
return 0;
|
|
713
|
+
}
|
|
714
|
+
// This is what the top strain is if all strain values were identical.
|
|
715
|
+
const consistentTopStrain = this.difficulty / 10;
|
|
716
|
+
// Use a weighted sum of all strains.
|
|
717
|
+
return this._objectStrains.reduce((total, next) => total +
|
|
718
|
+
1.1 / (1 + Math.exp(-10 * (next / consistentTopStrain - 0.88))), 0);
|
|
719
|
+
}
|
|
697
720
|
/**
|
|
698
721
|
* Calculates strain decay for a specified time frame.
|
|
699
722
|
*
|
|
@@ -730,32 +753,6 @@ class StrainSkill extends Skill {
|
|
|
730
753
|
* and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
|
731
754
|
*/
|
|
732
755
|
class DroidSkill extends StrainSkill {
|
|
733
|
-
constructor() {
|
|
734
|
-
super(...arguments);
|
|
735
|
-
this._objectStrains = [];
|
|
736
|
-
this.difficulty = 0;
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* The strains of hitobjects.
|
|
740
|
-
*/
|
|
741
|
-
get objectStrains() {
|
|
742
|
-
return this._objectStrains;
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Returns the number of strains weighed against the top strain.
|
|
746
|
-
*
|
|
747
|
-
* The result is scaled by clock rate as it affects the total number of strains.
|
|
748
|
-
*/
|
|
749
|
-
countDifficultStrains() {
|
|
750
|
-
if (this.difficulty === 0) {
|
|
751
|
-
return 0;
|
|
752
|
-
}
|
|
753
|
-
// This is what the top strain is if all strain values were identical.
|
|
754
|
-
const consistentTopStrain = this.difficulty / 10;
|
|
755
|
-
// Use a weighted sum of all strains.
|
|
756
|
-
return this._objectStrains.reduce((total, next) => total +
|
|
757
|
-
1.1 / (1 + Math.exp(-10 * (next / consistentTopStrain - 0.88))), 0);
|
|
758
|
-
}
|
|
759
756
|
process(current) {
|
|
760
757
|
super.process(current);
|
|
761
758
|
this._objectStrains.push(this.getObjectStrain(current));
|
|
@@ -1137,6 +1134,7 @@ class Island {
|
|
|
1137
1134
|
this.deltaCount === other.deltaCount);
|
|
1138
1135
|
}
|
|
1139
1136
|
}
|
|
1137
|
+
|
|
1140
1138
|
/**
|
|
1141
1139
|
* An evaluator for calculating osu!droid Rhythm skill.
|
|
1142
1140
|
*/
|
|
@@ -2086,10 +2084,6 @@ class PerformanceCalculator {
|
|
|
2086
2084
|
* The calculated accuracy.
|
|
2087
2085
|
*/
|
|
2088
2086
|
this.computedAccuracy = new osuBase.Accuracy({});
|
|
2089
|
-
/**
|
|
2090
|
-
* Penalty for combo breaks.
|
|
2091
|
-
*/
|
|
2092
|
-
this.comboPenalty = 0;
|
|
2093
2087
|
/**
|
|
2094
2088
|
* The amount of misses that are filtered out from sliderbreaks.
|
|
2095
2089
|
*/
|
|
@@ -2149,7 +2143,6 @@ class PerformanceCalculator {
|
|
|
2149
2143
|
const maxCombo = this.difficultyAttributes.maxCombo;
|
|
2150
2144
|
const miss = this.computedAccuracy.nmiss;
|
|
2151
2145
|
const combo = (_a = options === null || options === void 0 ? void 0 : options.combo) !== null && _a !== void 0 ? _a : maxCombo - miss;
|
|
2152
|
-
this.comboPenalty = Math.min(Math.pow(combo / maxCombo, 0.8), 1);
|
|
2153
2146
|
if ((options === null || options === void 0 ? void 0 : options.accPercent) instanceof osuBase.Accuracy) {
|
|
2154
2147
|
// Copy into new instance to not modify the original
|
|
2155
2148
|
this.computedAccuracy = new osuBase.Accuracy(options.accPercent);
|
|
@@ -2187,7 +2180,7 @@ class PerformanceCalculator {
|
|
|
2187
2180
|
Math.pow(this.difficultyAttributes.overallDifficulty /
|
|
2188
2181
|
13.33, 1.8)
|
|
2189
2182
|
: 1);
|
|
2190
|
-
const n50Multiplier = Math.max(0, this.difficultyAttributes.overallDifficulty > 0
|
|
2183
|
+
const n50Multiplier = Math.max(0, this.difficultyAttributes.overallDifficulty > 0
|
|
2191
2184
|
? 1 -
|
|
2192
2185
|
Math.pow(this.difficultyAttributes.overallDifficulty /
|
|
2193
2186
|
13.33, 5)
|
|
@@ -2212,6 +2205,22 @@ class PerformanceCalculator {
|
|
|
2212
2205
|
this.difficultyAttributes.sliderFactor;
|
|
2213
2206
|
}
|
|
2214
2207
|
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Calculates a strain-based miss penalty.
|
|
2210
|
+
*
|
|
2211
|
+
* Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
|
|
2212
|
+
* so we use the amount of relatively difficult sections to adjust miss penalty
|
|
2213
|
+
* to make it more punishing on maps with lower amount of hard sections.
|
|
2214
|
+
*/
|
|
2215
|
+
calculateStrainBasedMissPenalty(difficultStrainCount) {
|
|
2216
|
+
if (this.effectiveMissCount === 0) {
|
|
2217
|
+
return 1;
|
|
2218
|
+
}
|
|
2219
|
+
return (0.96 /
|
|
2220
|
+
(this.effectiveMissCount /
|
|
2221
|
+
(4 * Math.pow(Math.log(difficultStrainCount), 0.94)) +
|
|
2222
|
+
1));
|
|
2223
|
+
}
|
|
2215
2224
|
/**
|
|
2216
2225
|
* Calculates the amount of misses + sliderbreaks from combo.
|
|
2217
2226
|
*/
|
|
@@ -2555,22 +2564,6 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2555
2564
|
visualValue *= 0.98 + Math.pow(5, 2) / 2500;
|
|
2556
2565
|
return visualValue;
|
|
2557
2566
|
}
|
|
2558
|
-
/**
|
|
2559
|
-
* Calculates a strain-based miss penalty.
|
|
2560
|
-
*
|
|
2561
|
-
* Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
|
|
2562
|
-
* so we use the amount of relatively difficult sections to adjust miss penalty
|
|
2563
|
-
* to make it more punishing on maps with lower amount of hard sections.
|
|
2564
|
-
*/
|
|
2565
|
-
calculateStrainBasedMissPenalty(difficultStrainCount) {
|
|
2566
|
-
if (this.effectiveMissCount === 0) {
|
|
2567
|
-
return 1;
|
|
2568
|
-
}
|
|
2569
|
-
return (0.96 /
|
|
2570
|
-
(this.effectiveMissCount /
|
|
2571
|
-
(4 * Math.pow(Math.log(difficultStrainCount), 0.94)) +
|
|
2572
|
-
1));
|
|
2573
|
-
}
|
|
2574
2567
|
/**
|
|
2575
2568
|
* The object-based proportional miss penalty.
|
|
2576
2569
|
*/
|
|
@@ -2781,17 +2774,8 @@ class DroidPerformanceCalculator extends PerformanceCalculator {
|
|
|
2781
2774
|
* and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
|
2782
2775
|
*/
|
|
2783
2776
|
class OsuSkill extends StrainSkill {
|
|
2784
|
-
constructor() {
|
|
2785
|
-
super(...arguments);
|
|
2786
|
-
/**
|
|
2787
|
-
* The final multiplier to be applied to the final difficulty value after all other calculations.
|
|
2788
|
-
*/
|
|
2789
|
-
this.difficultyMultiplier = OsuSkill.defaultDifficultyMultiplier;
|
|
2790
|
-
}
|
|
2791
2777
|
difficultyValue() {
|
|
2792
|
-
const strains = this.strainPeaks
|
|
2793
|
-
.slice()
|
|
2794
|
-
.sort((a, b) => b - a);
|
|
2778
|
+
const strains = this.strainPeaks.slice().sort((a, b) => b - a);
|
|
2795
2779
|
if (this.reducedSectionCount > 0) {
|
|
2796
2780
|
// We are reducing the highest strains first to account for extreme difficulty spikes.
|
|
2797
2781
|
for (let i = 0; i < Math.min(strains.length, this.reducedSectionCount); ++i) {
|
|
@@ -2802,25 +2786,19 @@ class OsuSkill extends StrainSkill {
|
|
|
2802
2786
|
}
|
|
2803
2787
|
// Difficulty is the weighted sum of the highest strains from every section.
|
|
2804
2788
|
// We're sorting from highest to lowest strain.
|
|
2805
|
-
|
|
2789
|
+
this.difficulty = 0;
|
|
2806
2790
|
let weight = 1;
|
|
2807
2791
|
for (const strain of strains) {
|
|
2808
2792
|
const addition = strain * weight;
|
|
2809
|
-
if (difficulty + addition === difficulty) {
|
|
2793
|
+
if (this.difficulty + addition === this.difficulty) {
|
|
2810
2794
|
break;
|
|
2811
2795
|
}
|
|
2812
|
-
difficulty += addition;
|
|
2796
|
+
this.difficulty += addition;
|
|
2813
2797
|
weight *= this.decayWeight;
|
|
2814
2798
|
}
|
|
2815
|
-
return
|
|
2799
|
+
return this.difficulty;
|
|
2816
2800
|
}
|
|
2817
2801
|
}
|
|
2818
|
-
/**
|
|
2819
|
-
* The default multiplier applied to the final difficulty value after all other calculations.
|
|
2820
|
-
*
|
|
2821
|
-
* May be overridden via {@link difficultyMultiplier}.
|
|
2822
|
-
*/
|
|
2823
|
-
OsuSkill.defaultDifficultyMultiplier = 1.06;
|
|
2824
2802
|
|
|
2825
2803
|
/**
|
|
2826
2804
|
* An evaluator for calculating osu!standard Aim skill.
|
|
@@ -2955,7 +2933,7 @@ class OsuAim extends OsuSkill {
|
|
|
2955
2933
|
this.reducedSectionBaseline = 0.75;
|
|
2956
2934
|
this.decayWeight = 0.9;
|
|
2957
2935
|
this.currentAimStrain = 0;
|
|
2958
|
-
this.skillMultiplier =
|
|
2936
|
+
this.skillMultiplier = 25.18;
|
|
2959
2937
|
this.withSliders = withSliders;
|
|
2960
2938
|
}
|
|
2961
2939
|
strainValueAt(current) {
|
|
@@ -2963,6 +2941,7 @@ class OsuAim extends OsuSkill {
|
|
|
2963
2941
|
this.currentAimStrain +=
|
|
2964
2942
|
OsuAimEvaluator.evaluateDifficultyOf(current, this.withSliders) *
|
|
2965
2943
|
this.skillMultiplier;
|
|
2944
|
+
this._objectStrains.push(this.currentAimStrain);
|
|
2966
2945
|
return this.currentAimStrain;
|
|
2967
2946
|
}
|
|
2968
2947
|
calculateInitialStrain(time, current) {
|
|
@@ -3008,24 +2987,32 @@ class OsuSpeedEvaluator extends SpeedEvaluator {
|
|
|
3008
2987
|
// Cap deltatime to the OD 300 hitwindow.
|
|
3009
2988
|
// 0.93 is derived from making sure 260 BPM 1/4 OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
|
3010
2989
|
strainTime /= osuBase.MathUtils.clamp(strainTime / current.fullGreatWindow / 0.93, 0.92, 1);
|
|
3011
|
-
|
|
2990
|
+
// speedBonus will be 0.0 for BPM < 200
|
|
2991
|
+
let speedBonus = 0;
|
|
2992
|
+
// Add additional scaling bonus for streams/bursts higher than 200bpm
|
|
3012
2993
|
if (strainTime < this.minSpeedBonus) {
|
|
3013
|
-
speedBonus
|
|
2994
|
+
speedBonus =
|
|
3014
2995
|
0.75 * Math.pow((this.minSpeedBonus - strainTime) / 40, 2);
|
|
3015
2996
|
}
|
|
3016
2997
|
const travelDistance = (_a = prev === null || prev === void 0 ? void 0 : prev.travelDistance) !== null && _a !== void 0 ? _a : 0;
|
|
2998
|
+
// Cap distance at spacing threshold
|
|
3017
2999
|
const distance = Math.min(this.SINGLE_SPACING_THRESHOLD, travelDistance + current.minimumJumpDistance);
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3000
|
+
// Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
|
|
3001
|
+
const distanceBonus = Math.pow(distance / this.SINGLE_SPACING_THRESHOLD, 3.95) *
|
|
3002
|
+
this.DISTANCE_MULTIPLIER;
|
|
3003
|
+
// Base difficulty with all bonuses
|
|
3004
|
+
const difficulty = ((1 + speedBonus + distanceBonus) * 1000) / strainTime;
|
|
3005
|
+
// Apply penalty if there's doubletappable doubles
|
|
3006
|
+
return difficulty * doubletapness;
|
|
3023
3007
|
}
|
|
3024
3008
|
}
|
|
3025
3009
|
/**
|
|
3026
3010
|
* Spacing threshold for a single hitobject spacing.
|
|
3011
|
+
*
|
|
3012
|
+
* About 1.25 circles distance between hitobject centers.
|
|
3027
3013
|
*/
|
|
3028
3014
|
OsuSpeedEvaluator.SINGLE_SPACING_THRESHOLD = 125;
|
|
3015
|
+
OsuSpeedEvaluator.DISTANCE_MULTIPLIER = 0.94;
|
|
3029
3016
|
|
|
3030
3017
|
/**
|
|
3031
3018
|
* An evaluator for calculating osu!standard Rhythm skill.
|
|
@@ -3036,20 +3023,21 @@ class OsuRhythmEvaluator {
|
|
|
3036
3023
|
* with historic data of the current object.
|
|
3037
3024
|
*
|
|
3038
3025
|
* @param current The current object.
|
|
3039
|
-
* @param greatWindow The great hit window of the current object.
|
|
3040
3026
|
*/
|
|
3041
3027
|
static evaluateDifficultyOf(current) {
|
|
3042
3028
|
if (current.object instanceof osuBase.Spinner) {
|
|
3043
3029
|
return 0;
|
|
3044
3030
|
}
|
|
3045
|
-
|
|
3031
|
+
const deltaDifferenceEpsilon = current.fullGreatWindow * 0.3;
|
|
3046
3032
|
let rhythmComplexitySum = 0;
|
|
3047
|
-
let
|
|
3033
|
+
let island = new Island(deltaDifferenceEpsilon);
|
|
3034
|
+
let previousIsland = new Island(deltaDifferenceEpsilon);
|
|
3035
|
+
const islandCounts = new Map();
|
|
3048
3036
|
// Store the ratio of the current start of an island to buff for tighter rhythms.
|
|
3049
3037
|
let startRatio = 0;
|
|
3050
3038
|
let firstDeltaSwitch = false;
|
|
3051
|
-
const historicalNoteCount = Math.min(current.index, 32);
|
|
3052
3039
|
let rhythmStart = 0;
|
|
3040
|
+
const historicalNoteCount = Math.min(current.index, this.historyObjectsMax);
|
|
3053
3041
|
while (rhythmStart < historicalNoteCount - 2 &&
|
|
3054
3042
|
current.startTime - current.previous(rhythmStart).startTime <
|
|
3055
3043
|
this.historyTimeMax) {
|
|
@@ -3060,84 +3048,119 @@ class OsuRhythmEvaluator {
|
|
|
3060
3048
|
const prevObject = current.previous(i);
|
|
3061
3049
|
const lastObject = current.previous(i + 1);
|
|
3062
3050
|
// Scale note 0 to 1 from history to now.
|
|
3063
|
-
|
|
3051
|
+
const timeDecay = (this.historyTimeMax -
|
|
3064
3052
|
(current.startTime - currentObject.startTime)) /
|
|
3065
3053
|
this.historyTimeMax;
|
|
3054
|
+
const noteDecay = (historicalNoteCount - i) / historicalNoteCount;
|
|
3066
3055
|
// Either we're limited by time or limited by object count.
|
|
3067
|
-
currentHistoricalDecay = Math.min(
|
|
3056
|
+
const currentHistoricalDecay = Math.min(timeDecay, noteDecay);
|
|
3068
3057
|
const currentDelta = currentObject.strainTime;
|
|
3069
3058
|
const prevDelta = prevObject.strainTime;
|
|
3070
3059
|
const lastDelta = lastObject.strainTime;
|
|
3060
|
+
// Calculate how much current delta difference deserves a rhythm bonus
|
|
3061
|
+
// This function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e. 100 and 200)
|
|
3062
|
+
const deltaDifferenceRatio = Math.min(prevDelta, currentDelta) /
|
|
3063
|
+
Math.max(prevDelta, currentDelta);
|
|
3071
3064
|
const currentRatio = 1 +
|
|
3072
|
-
|
|
3073
|
-
Math.min(0.5, Math.pow(Math.sin(Math.PI /
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
let effectiveRatio = windowPenalty * currentRatio;
|
|
3065
|
+
this.rhythmRatioMultiplier *
|
|
3066
|
+
Math.min(0.5, Math.pow(Math.sin(Math.PI / deltaDifferenceRatio), 2));
|
|
3067
|
+
// Reduce ratio bonus if delta difference is too big
|
|
3068
|
+
const fraction = Math.max(prevDelta / currentDelta, currentDelta / prevDelta);
|
|
3069
|
+
const fractionMultiplier = osuBase.MathUtils.clamp(2 - fraction / 8, 0, 1);
|
|
3070
|
+
const windowPenalty = Math.min(1, Math.max(0, Math.abs(prevDelta - currentDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
|
|
3071
|
+
let effectiveRatio = windowPenalty * currentRatio * fractionMultiplier;
|
|
3080
3072
|
if (firstDeltaSwitch) {
|
|
3081
|
-
if (prevDelta
|
|
3082
|
-
prevDelta * 1.25 >= currentDelta) {
|
|
3073
|
+
if (Math.abs(prevDelta - currentDelta) < deltaDifferenceEpsilon) {
|
|
3083
3074
|
// Island is still progressing, count size.
|
|
3084
|
-
|
|
3085
|
-
++islandSize;
|
|
3086
|
-
}
|
|
3075
|
+
island.addDelta(currentDelta);
|
|
3087
3076
|
}
|
|
3088
3077
|
else {
|
|
3078
|
+
// BPM change is into slider, this is easy acc window.
|
|
3089
3079
|
if (currentObject.object instanceof osuBase.Slider) {
|
|
3090
|
-
// BPM change is into slider, this is easy acc window.
|
|
3091
3080
|
effectiveRatio /= 8;
|
|
3092
3081
|
}
|
|
3082
|
+
// BPM change was from a slider, this is easier typically than circle -> circle.
|
|
3083
|
+
// Unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty
|
|
3084
|
+
// than bursts without sliders.
|
|
3093
3085
|
if (prevObject.object instanceof osuBase.Slider) {
|
|
3094
|
-
|
|
3095
|
-
effectiveRatio /= 4;
|
|
3096
|
-
}
|
|
3097
|
-
if (previousIslandSize === islandSize) {
|
|
3098
|
-
// Repeated island size (ex: triplet -> triplet).
|
|
3099
|
-
effectiveRatio /= 4;
|
|
3086
|
+
effectiveRatio *= 0.3;
|
|
3100
3087
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3088
|
+
// Repeated island polarity (2 -> 4, 3 -> 5).
|
|
3089
|
+
if (island.isSimilarPolarity(previousIsland)) {
|
|
3103
3090
|
effectiveRatio /= 2;
|
|
3104
3091
|
}
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3092
|
+
// Previous increase happened a note ago.
|
|
3093
|
+
// Albeit this is a 1/1 -> 1/2-1/4 type of transition, we don't want to buff this.
|
|
3094
|
+
if (lastDelta > prevDelta + deltaDifferenceEpsilon &&
|
|
3095
|
+
prevDelta > currentDelta + deltaDifferenceEpsilon) {
|
|
3109
3096
|
effectiveRatio /= 8;
|
|
3110
3097
|
}
|
|
3098
|
+
// Repeated island size (ex: triplet -> triplet).
|
|
3099
|
+
// TODO: remove this nerf since its staying here only for balancing purposes because of the flawed ratio calculation
|
|
3100
|
+
if (previousIsland.deltaCount == island.deltaCount) {
|
|
3101
|
+
effectiveRatio /= 2;
|
|
3102
|
+
}
|
|
3103
|
+
let islandFound = false;
|
|
3104
|
+
for (const [currentIsland, count] of islandCounts) {
|
|
3105
|
+
if (!island.equals(currentIsland)) {
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
islandFound = true;
|
|
3109
|
+
let islandCount = count;
|
|
3110
|
+
if (previousIsland.equals(island)) {
|
|
3111
|
+
// Only add island to island counts if they're going one after another.
|
|
3112
|
+
++islandCount;
|
|
3113
|
+
islandCounts.set(currentIsland, islandCount);
|
|
3114
|
+
}
|
|
3115
|
+
// Repeated island (ex: triplet -> triplet).
|
|
3116
|
+
// Graph: https://www.desmos.com/calculator/pj7an56zwf
|
|
3117
|
+
effectiveRatio *= Math.min(3 / islandCount, Math.pow(1 / islandCount, 2.75 / (1 + Math.exp(14 - 0.24 * island.delta))));
|
|
3118
|
+
break;
|
|
3119
|
+
}
|
|
3120
|
+
if (!islandFound) {
|
|
3121
|
+
islandCounts.set(island, 1);
|
|
3122
|
+
}
|
|
3123
|
+
// Scale down the difficulty if the object is doubletappable.
|
|
3124
|
+
effectiveRatio *= 1 - prevObject.doubletapness * 0.75;
|
|
3111
3125
|
rhythmComplexitySum +=
|
|
3112
|
-
|
|
3113
|
-
currentHistoricalDecay
|
|
3114
|
-
Math.sqrt(4 + islandSize)) /
|
|
3115
|
-
2) *
|
|
3116
|
-
Math.sqrt(4 + previousIslandSize)) /
|
|
3117
|
-
2;
|
|
3126
|
+
Math.sqrt(effectiveRatio * startRatio) *
|
|
3127
|
+
currentHistoricalDecay;
|
|
3118
3128
|
startRatio = effectiveRatio;
|
|
3119
|
-
|
|
3120
|
-
if (prevDelta
|
|
3129
|
+
previousIsland = island;
|
|
3130
|
+
if (prevDelta + deltaDifferenceEpsilon < currentDelta) {
|
|
3121
3131
|
// We're slowing down, stop counting.
|
|
3122
3132
|
// If we're speeding up, this stays as is and we keep counting island size.
|
|
3123
3133
|
firstDeltaSwitch = false;
|
|
3124
3134
|
}
|
|
3125
|
-
|
|
3135
|
+
island = new Island(currentDelta, deltaDifferenceEpsilon);
|
|
3126
3136
|
}
|
|
3127
3137
|
}
|
|
3128
|
-
else if (prevDelta >
|
|
3129
|
-
// We
|
|
3138
|
+
else if (prevDelta > currentDelta + deltaDifferenceEpsilon) {
|
|
3139
|
+
// We are speeding up.
|
|
3130
3140
|
// Begin counting island until we change speed again.
|
|
3131
3141
|
firstDeltaSwitch = true;
|
|
3142
|
+
// BPM change is into slider, this is easy acc window.
|
|
3143
|
+
if (currentObject.object instanceof osuBase.Slider) {
|
|
3144
|
+
effectiveRatio *= 0.6;
|
|
3145
|
+
}
|
|
3146
|
+
// BPM change was from a slider, this is easier typically than circle -> circle
|
|
3147
|
+
// Unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty
|
|
3148
|
+
// than bursts without sliders
|
|
3149
|
+
if (prevObject.object instanceof osuBase.Slider) {
|
|
3150
|
+
effectiveRatio *= 0.6;
|
|
3151
|
+
}
|
|
3132
3152
|
startRatio = effectiveRatio;
|
|
3133
|
-
|
|
3153
|
+
island = new Island(currentDelta, deltaDifferenceEpsilon);
|
|
3134
3154
|
}
|
|
3135
3155
|
}
|
|
3136
|
-
return Math.sqrt(4 + rhythmComplexitySum * this.
|
|
3156
|
+
return (Math.sqrt(4 + rhythmComplexitySum * this.rhythmOverallMultiplier) /
|
|
3157
|
+
2);
|
|
3137
3158
|
}
|
|
3138
3159
|
}
|
|
3139
|
-
OsuRhythmEvaluator.rhythmMultiplier = 0.75;
|
|
3140
3160
|
OsuRhythmEvaluator.historyTimeMax = 5000; // 5 seconds of calculateRhythmBonus max.
|
|
3161
|
+
OsuRhythmEvaluator.historyObjectsMax = 32;
|
|
3162
|
+
OsuRhythmEvaluator.rhythmOverallMultiplier = 0.95;
|
|
3163
|
+
OsuRhythmEvaluator.rhythmRatioMultiplier = 12;
|
|
3141
3164
|
|
|
3142
3165
|
/**
|
|
3143
3166
|
* Represents the skill required to press keys or tap with regards to keeping up with the speed at which objects need to be hit.
|
|
@@ -3148,11 +3171,10 @@ class OsuSpeed extends OsuSkill {
|
|
|
3148
3171
|
this.strainDecayBase = 0.3;
|
|
3149
3172
|
this.reducedSectionCount = 5;
|
|
3150
3173
|
this.reducedSectionBaseline = 0.75;
|
|
3151
|
-
this.difficultyMultiplier = 1.04;
|
|
3152
3174
|
this.decayWeight = 0.9;
|
|
3153
3175
|
this.currentSpeedStrain = 0;
|
|
3154
3176
|
this.currentRhythm = 0;
|
|
3155
|
-
this.skillMultiplier =
|
|
3177
|
+
this.skillMultiplier = 1.43;
|
|
3156
3178
|
}
|
|
3157
3179
|
/**
|
|
3158
3180
|
* @param current The hitobject to calculate.
|
|
@@ -3163,7 +3185,9 @@ class OsuSpeed extends OsuSkill {
|
|
|
3163
3185
|
OsuSpeedEvaluator.evaluateDifficultyOf(current) *
|
|
3164
3186
|
this.skillMultiplier;
|
|
3165
3187
|
this.currentRhythm = OsuRhythmEvaluator.evaluateDifficultyOf(current);
|
|
3166
|
-
|
|
3188
|
+
const strain = this.currentSpeedStrain * this.currentRhythm;
|
|
3189
|
+
this._objectStrains.push(strain);
|
|
3190
|
+
return strain;
|
|
3167
3191
|
}
|
|
3168
3192
|
calculateInitialStrain(time, current) {
|
|
3169
3193
|
var _a, _b;
|
|
@@ -3211,7 +3235,7 @@ class OsuFlashlightEvaluator extends FlashlightEvaluator {
|
|
|
3211
3235
|
if (!(currentObject.object instanceof osuBase.Spinner)) {
|
|
3212
3236
|
const jumpDistance = current.object
|
|
3213
3237
|
.getStackedPosition(osuBase.Modes.osu)
|
|
3214
|
-
.subtract(currentObject.object.
|
|
3238
|
+
.subtract(currentObject.object.getStackedEndPosition(osuBase.Modes.osu)).length;
|
|
3215
3239
|
cumulativeStrainTime += last.strainTime;
|
|
3216
3240
|
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
|
|
3217
3241
|
if (i === 0) {
|
|
@@ -3273,9 +3297,12 @@ class OsuFlashlight extends OsuSkill {
|
|
|
3273
3297
|
this.reducedSectionBaseline = 1;
|
|
3274
3298
|
this.decayWeight = 1;
|
|
3275
3299
|
this.currentFlashlightStrain = 0;
|
|
3276
|
-
this.skillMultiplier = 0.
|
|
3300
|
+
this.skillMultiplier = 0.05512;
|
|
3277
3301
|
this.isHidden = mods.some((m) => m instanceof osuBase.ModHidden);
|
|
3278
3302
|
}
|
|
3303
|
+
difficultyValue() {
|
|
3304
|
+
return this.strainPeaks.reduce((a, b) => a + b, 0);
|
|
3305
|
+
}
|
|
3279
3306
|
strainValueAt(current) {
|
|
3280
3307
|
this.currentFlashlightStrain *= this.strainDecay(current.deltaTime);
|
|
3281
3308
|
this.currentFlashlightStrain +=
|
|
@@ -3343,6 +3370,8 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3343
3370
|
hitCircleCount: 0,
|
|
3344
3371
|
sliderCount: 0,
|
|
3345
3372
|
spinnerCount: 0,
|
|
3373
|
+
aimDifficultStrainCount: 0,
|
|
3374
|
+
speedDifficultStrainCount: 0,
|
|
3346
3375
|
};
|
|
3347
3376
|
this.difficultyMultiplier = 0.0675;
|
|
3348
3377
|
this.mode = osuBase.Modes.osu;
|
|
@@ -3411,7 +3440,7 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3411
3440
|
// Document for formula derivation:
|
|
3412
3441
|
// https://docs.google.com/document/d/10DZGYYSsT_yjz2Mtp6yIJld0Rqx4E-vVHupCqiM4TNI/edit
|
|
3413
3442
|
this.attributes.starRating =
|
|
3414
|
-
Math.cbrt(1.
|
|
3443
|
+
Math.cbrt(1.15) *
|
|
3415
3444
|
0.027 *
|
|
3416
3445
|
(Math.cbrt((100000 / Math.pow(2, 1 / 1.1)) * basePerformanceValue) +
|
|
3417
3446
|
4);
|
|
@@ -3496,6 +3525,8 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3496
3525
|
if (this.mods.some((m) => m instanceof osuBase.ModRelax)) {
|
|
3497
3526
|
this.attributes.aimDifficulty *= 0.9;
|
|
3498
3527
|
}
|
|
3528
|
+
this.attributes.aimDifficultStrainCount =
|
|
3529
|
+
aimSkill.countDifficultStrains();
|
|
3499
3530
|
}
|
|
3500
3531
|
/**
|
|
3501
3532
|
* Called after speed skill calculation.
|
|
@@ -3505,6 +3536,8 @@ class OsuDifficultyCalculator extends DifficultyCalculator {
|
|
|
3505
3536
|
postCalculateSpeed(speedSkill) {
|
|
3506
3537
|
this.strainPeaks.speed = speedSkill.strainPeaks;
|
|
3507
3538
|
this.attributes.speedDifficulty = this.starValue(speedSkill.difficultyValue());
|
|
3539
|
+
this.attributes.speedDifficultStrainCount =
|
|
3540
|
+
speedSkill.countDifficultStrains();
|
|
3508
3541
|
}
|
|
3509
3542
|
/**
|
|
3510
3543
|
* Calculates speed-related attributes.
|
|
@@ -3555,8 +3588,9 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
3555
3588
|
* The flashlight performance value.
|
|
3556
3589
|
*/
|
|
3557
3590
|
this.flashlight = 0;
|
|
3558
|
-
this.finalMultiplier = 1.
|
|
3591
|
+
this.finalMultiplier = 1.15;
|
|
3559
3592
|
this.mode = osuBase.Modes.osu;
|
|
3593
|
+
this.comboPenalty = 1;
|
|
3560
3594
|
}
|
|
3561
3595
|
calculateValues() {
|
|
3562
3596
|
this.aim = this.calculateAimValue();
|
|
@@ -3570,6 +3604,14 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
3570
3604
|
Math.pow(this.accuracy, 1.1) +
|
|
3571
3605
|
Math.pow(this.flashlight, 1.1), 1 / 1.1) * this.finalMultiplier);
|
|
3572
3606
|
}
|
|
3607
|
+
handleOptions(options) {
|
|
3608
|
+
var _a;
|
|
3609
|
+
super.handleOptions(options);
|
|
3610
|
+
const maxCombo = this.difficultyAttributes.maxCombo;
|
|
3611
|
+
const miss = this.computedAccuracy.nmiss;
|
|
3612
|
+
const combo = (_a = options === null || options === void 0 ? void 0 : options.combo) !== null && _a !== void 0 ? _a : maxCombo - miss;
|
|
3613
|
+
this.comboPenalty = Math.min(Math.pow(combo / maxCombo, 0.8), 1);
|
|
3614
|
+
}
|
|
3573
3615
|
/**
|
|
3574
3616
|
* Calculates the aim performance value of the beatmap.
|
|
3575
3617
|
*/
|
|
@@ -3581,16 +3623,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
3581
3623
|
lengthBonus += Math.log10(this.totalHits / 2000) * 0.5;
|
|
3582
3624
|
}
|
|
3583
3625
|
aimValue *= lengthBonus;
|
|
3584
|
-
|
|
3585
|
-
// Penalize misses by assessing # of misses relative to the total # of objects.
|
|
3586
|
-
// Default a 3% reduction for any # of misses.
|
|
3587
|
-
aimValue *=
|
|
3588
|
-
0.97 *
|
|
3589
|
-
Math.pow(1 -
|
|
3590
|
-
Math.pow(this.effectiveMissCount / this.totalHits, 0.775), this.effectiveMissCount);
|
|
3591
|
-
}
|
|
3592
|
-
// Combo scaling
|
|
3593
|
-
aimValue *= this.comboPenalty;
|
|
3626
|
+
aimValue *= this.calculateStrainBasedMissPenalty(this.difficultyAttributes.aimDifficultStrainCount);
|
|
3594
3627
|
const calculatedAR = this.difficultyAttributes.approachRate;
|
|
3595
3628
|
if (!this.difficultyAttributes.mods.some((m) => m instanceof osuBase.ModRelax)) {
|
|
3596
3629
|
// AR scaling
|
|
@@ -3631,16 +3664,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
3631
3664
|
lengthBonus += Math.log10(this.totalHits / 2000) * 0.5;
|
|
3632
3665
|
}
|
|
3633
3666
|
speedValue *= lengthBonus;
|
|
3634
|
-
|
|
3635
|
-
// Penalize misses by assessing # of misses relative to the total # of objects.
|
|
3636
|
-
// Default a 3% reduction for any # of misses.
|
|
3637
|
-
speedValue *=
|
|
3638
|
-
0.97 *
|
|
3639
|
-
Math.pow(1 -
|
|
3640
|
-
Math.pow(this.effectiveMissCount / this.totalHits, 0.775), Math.pow(this.effectiveMissCount, 0.875));
|
|
3641
|
-
}
|
|
3642
|
-
// Combo scaling
|
|
3643
|
-
speedValue *= this.comboPenalty;
|
|
3667
|
+
speedValue *= this.calculateStrainBasedMissPenalty(this.difficultyAttributes.speedDifficultStrainCount);
|
|
3644
3668
|
// AR scaling
|
|
3645
3669
|
const calculatedAR = this.difficultyAttributes.approachRate;
|
|
3646
3670
|
if (calculatedAR > 10.33) {
|
|
@@ -3671,9 +3695,7 @@ class OsuPerformanceCalculator extends PerformanceCalculator {
|
|
|
3671
3695
|
750) *
|
|
3672
3696
|
Math.pow((this.computedAccuracy.value() +
|
|
3673
3697
|
relevantAccuracy.value(this.difficultyAttributes.speedNoteCount)) /
|
|
3674
|
-
2, (14.5 -
|
|
3675
|
-
Math.max(this.difficultyAttributes.overallDifficulty, 8)) /
|
|
3676
|
-
2);
|
|
3698
|
+
2, (14.5 - this.difficultyAttributes.overallDifficulty) / 2);
|
|
3677
3699
|
// Scale the speed value with # of 50s to punish doubletapping.
|
|
3678
3700
|
speedValue *= Math.pow(0.99, Math.max(0, this.computedAccuracy.n50 - this.totalHits / 500));
|
|
3679
3701
|
return speedValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rian8337/osu-difficulty-calculator",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.32",
|
|
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.
|
|
36
|
+
"@rian8337/osu-base": "^4.0.0-beta.31"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "dd164dcc63fea94501959236d310eccf5a7cda6a"
|
|
42
42
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ interface DifficultyAttributes {
|
|
|
80
80
|
* The number of spinners in the beatmap.
|
|
81
81
|
*/
|
|
82
82
|
spinnerCount: number;
|
|
83
|
+
/**
|
|
84
|
+
* The amount of strains that are considered difficult with respect to the aim skill.
|
|
85
|
+
*/
|
|
86
|
+
aimDifficultStrainCount: number;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/**
|
|
@@ -471,6 +475,12 @@ declare abstract class StrainSkill extends Skill {
|
|
|
471
475
|
* For example, a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
|
472
476
|
*/
|
|
473
477
|
protected abstract readonly strainDecayBase: number;
|
|
478
|
+
protected readonly _objectStrains: number[];
|
|
479
|
+
protected difficulty: number;
|
|
480
|
+
/**
|
|
481
|
+
* The strains of hitobjects.
|
|
482
|
+
*/
|
|
483
|
+
get objectStrains(): readonly number[];
|
|
474
484
|
private readonly sectionLength;
|
|
475
485
|
private currentStrain;
|
|
476
486
|
private currentSectionPeak;
|
|
@@ -480,6 +490,12 @@ declare abstract class StrainSkill extends Skill {
|
|
|
480
490
|
* Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
|
|
481
491
|
*/
|
|
482
492
|
saveCurrentPeak(): void;
|
|
493
|
+
/**
|
|
494
|
+
* Returns the number of strains weighed against the top strain.
|
|
495
|
+
*
|
|
496
|
+
* The result is scaled by clock rate as it affects the total number of strains.
|
|
497
|
+
*/
|
|
498
|
+
countDifficultStrains(): number;
|
|
483
499
|
/**
|
|
484
500
|
* Calculates strain decay for a specified time frame.
|
|
485
501
|
*
|
|
@@ -529,18 +545,6 @@ declare abstract class DroidSkill extends StrainSkill {
|
|
|
529
545
|
* The bonus multiplier that is given for a sequence of notes of equal difficulty.
|
|
530
546
|
*/
|
|
531
547
|
protected abstract readonly starsPerDouble: number;
|
|
532
|
-
protected readonly _objectStrains: number[];
|
|
533
|
-
/**
|
|
534
|
-
* The strains of hitobjects.
|
|
535
|
-
*/
|
|
536
|
-
get objectStrains(): readonly number[];
|
|
537
|
-
private difficulty;
|
|
538
|
-
/**
|
|
539
|
-
* Returns the number of strains weighed against the top strain.
|
|
540
|
-
*
|
|
541
|
-
* The result is scaled by clock rate as it affects the total number of strains.
|
|
542
|
-
*/
|
|
543
|
-
countDifficultStrains(): number;
|
|
544
548
|
process(current: DifficultyHitObject): void;
|
|
545
549
|
difficultyValue(): number;
|
|
546
550
|
/**
|
|
@@ -702,10 +706,6 @@ interface DroidDifficultyAttributes extends DifficultyAttributes {
|
|
|
702
706
|
* The difficulty corresponding to the visual skill.
|
|
703
707
|
*/
|
|
704
708
|
visualDifficulty: number;
|
|
705
|
-
/**
|
|
706
|
-
* The amount of strains that are considered difficult with respect to the aim skill.
|
|
707
|
-
*/
|
|
708
|
-
aimDifficultStrainCount: number;
|
|
709
709
|
/**
|
|
710
710
|
* The amount of strains that are considered difficult with respect to the tap skill.
|
|
711
711
|
*/
|
|
@@ -1009,10 +1009,6 @@ declare abstract class PerformanceCalculator<T extends DifficultyAttributes> {
|
|
|
1009
1009
|
* The difficulty attributes that is being calculated.
|
|
1010
1010
|
*/
|
|
1011
1011
|
readonly difficultyAttributes: T;
|
|
1012
|
-
/**
|
|
1013
|
-
* Penalty for combo breaks.
|
|
1014
|
-
*/
|
|
1015
|
-
protected comboPenalty: number;
|
|
1016
1012
|
/**
|
|
1017
1013
|
* The global multiplier to be applied to the final performance value.
|
|
1018
1014
|
*
|
|
@@ -1073,6 +1069,14 @@ declare abstract class PerformanceCalculator<T extends DifficultyAttributes> {
|
|
|
1073
1069
|
* @param options Options for performance calculation.
|
|
1074
1070
|
*/
|
|
1075
1071
|
protected handleOptions(options?: PerformanceCalculationOptions): void;
|
|
1072
|
+
/**
|
|
1073
|
+
* Calculates a strain-based miss penalty.
|
|
1074
|
+
*
|
|
1075
|
+
* Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
|
|
1076
|
+
* so we use the amount of relatively difficult sections to adjust miss penalty
|
|
1077
|
+
* to make it more punishing on maps with lower amount of hard sections.
|
|
1078
|
+
*/
|
|
1079
|
+
protected calculateStrainBasedMissPenalty(difficultStrainCount: number): number;
|
|
1076
1080
|
/**
|
|
1077
1081
|
* Calculates the amount of misses + sliderbreaks from combo.
|
|
1078
1082
|
*/
|
|
@@ -1205,14 +1209,6 @@ declare class DroidPerformanceCalculator extends PerformanceCalculator<DroidDiff
|
|
|
1205
1209
|
* Calculates the visual performance value of the beatmap.
|
|
1206
1210
|
*/
|
|
1207
1211
|
private calculateVisualValue;
|
|
1208
|
-
/**
|
|
1209
|
-
* Calculates a strain-based miss penalty.
|
|
1210
|
-
*
|
|
1211
|
-
* Strain-based miss penalty assumes that a player will miss on the hardest parts of a map,
|
|
1212
|
-
* so we use the amount of relatively difficult sections to adjust miss penalty
|
|
1213
|
-
* to make it more punishing on maps with lower amount of hard sections.
|
|
1214
|
-
*/
|
|
1215
|
-
private calculateStrainBasedMissPenalty;
|
|
1216
1212
|
/**
|
|
1217
1213
|
* The object-based proportional miss penalty.
|
|
1218
1214
|
*/
|
|
@@ -1396,16 +1392,6 @@ declare abstract class DroidVisualEvaluator {
|
|
|
1396
1392
|
* and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
|
1397
1393
|
*/
|
|
1398
1394
|
declare abstract class OsuSkill extends StrainSkill {
|
|
1399
|
-
/**
|
|
1400
|
-
* The default multiplier applied to the final difficulty value after all other calculations.
|
|
1401
|
-
*
|
|
1402
|
-
* May be overridden via {@link difficultyMultiplier}.
|
|
1403
|
-
*/
|
|
1404
|
-
static readonly defaultDifficultyMultiplier: number;
|
|
1405
|
-
/**
|
|
1406
|
-
* The final multiplier to be applied to the final difficulty value after all other calculations.
|
|
1407
|
-
*/
|
|
1408
|
-
protected readonly difficultyMultiplier: number;
|
|
1409
1395
|
/**
|
|
1410
1396
|
* The weight by which each strain value decays.
|
|
1411
1397
|
*/
|
|
@@ -1434,10 +1420,10 @@ declare class OsuDifficultyHitObject extends DifficultyHitObject {
|
|
|
1434
1420
|
* Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
|
|
1435
1421
|
*/
|
|
1436
1422
|
declare class OsuAim extends OsuSkill {
|
|
1437
|
-
protected readonly strainDecayBase
|
|
1438
|
-
protected readonly reducedSectionCount
|
|
1439
|
-
protected readonly reducedSectionBaseline
|
|
1440
|
-
protected readonly decayWeight
|
|
1423
|
+
protected readonly strainDecayBase = 0.15;
|
|
1424
|
+
protected readonly reducedSectionCount = 10;
|
|
1425
|
+
protected readonly reducedSectionBaseline = 0.75;
|
|
1426
|
+
protected readonly decayWeight = 0.9;
|
|
1441
1427
|
private currentAimStrain;
|
|
1442
1428
|
private readonly skillMultiplier;
|
|
1443
1429
|
private readonly withSliders;
|
|
@@ -1482,6 +1468,10 @@ interface OsuDifficultyAttributes extends DifficultyAttributes {
|
|
|
1482
1468
|
* The difficulty corresponding to the speed skill.
|
|
1483
1469
|
*/
|
|
1484
1470
|
speedDifficulty: number;
|
|
1471
|
+
/**
|
|
1472
|
+
* The amount of strains that are considered difficult with respect to the speed skill.
|
|
1473
|
+
*/
|
|
1474
|
+
speedDifficultStrainCount: number;
|
|
1485
1475
|
}
|
|
1486
1476
|
|
|
1487
1477
|
/**
|
|
@@ -1551,14 +1541,15 @@ declare class OsuDifficultyCalculator extends DifficultyCalculator<OsuDifficulty
|
|
|
1551
1541
|
* Represents the skill required to memorize and hit every object in a beatmap with the Flashlight mod enabled.
|
|
1552
1542
|
*/
|
|
1553
1543
|
declare class OsuFlashlight extends OsuSkill {
|
|
1554
|
-
protected readonly strainDecayBase
|
|
1555
|
-
protected readonly reducedSectionCount
|
|
1556
|
-
protected readonly reducedSectionBaseline
|
|
1557
|
-
protected readonly decayWeight
|
|
1544
|
+
protected readonly strainDecayBase = 0.15;
|
|
1545
|
+
protected readonly reducedSectionCount = 0;
|
|
1546
|
+
protected readonly reducedSectionBaseline = 1;
|
|
1547
|
+
protected readonly decayWeight = 1;
|
|
1558
1548
|
private currentFlashlightStrain;
|
|
1559
1549
|
private readonly skillMultiplier;
|
|
1560
1550
|
private readonly isHidden;
|
|
1561
1551
|
constructor(mods: Mod[]);
|
|
1552
|
+
difficultyValue(): number;
|
|
1562
1553
|
protected strainValueAt(current: OsuDifficultyHitObject): number;
|
|
1563
1554
|
protected calculateInitialStrain(time: number, current: OsuDifficultyHitObject): number;
|
|
1564
1555
|
protected saveToHitObject(current: OsuDifficultyHitObject): void;
|
|
@@ -1604,9 +1595,11 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator<OsuDifficul
|
|
|
1604
1595
|
*/
|
|
1605
1596
|
flashlight: number;
|
|
1606
1597
|
protected finalMultiplier: number;
|
|
1607
|
-
protected readonly mode
|
|
1598
|
+
protected readonly mode = Modes.osu;
|
|
1599
|
+
private comboPenalty;
|
|
1608
1600
|
protected calculateValues(): void;
|
|
1609
1601
|
protected calculateTotalValue(): number;
|
|
1602
|
+
protected handleOptions(options?: PerformanceCalculationOptions): void;
|
|
1610
1603
|
/**
|
|
1611
1604
|
* Calculates the aim performance value of the beatmap.
|
|
1612
1605
|
*/
|
|
@@ -1630,14 +1623,15 @@ declare class OsuPerformanceCalculator extends PerformanceCalculator<OsuDifficul
|
|
|
1630
1623
|
* An evaluator for calculating osu!standard Rhythm skill.
|
|
1631
1624
|
*/
|
|
1632
1625
|
declare abstract class OsuRhythmEvaluator {
|
|
1633
|
-
private static readonly rhythmMultiplier;
|
|
1634
1626
|
private static readonly historyTimeMax;
|
|
1627
|
+
private static readonly historyObjectsMax;
|
|
1628
|
+
private static readonly rhythmOverallMultiplier;
|
|
1629
|
+
private static readonly rhythmRatioMultiplier;
|
|
1635
1630
|
/**
|
|
1636
1631
|
* Calculates a rhythm multiplier for the difficulty of the tap associated
|
|
1637
1632
|
* with historic data of the current object.
|
|
1638
1633
|
*
|
|
1639
1634
|
* @param current The current object.
|
|
1640
|
-
* @param greatWindow The great hit window of the current object.
|
|
1641
1635
|
*/
|
|
1642
1636
|
static evaluateDifficultyOf(current: OsuDifficultyHitObject): number;
|
|
1643
1637
|
}
|
|
@@ -1649,7 +1643,6 @@ declare class OsuSpeed extends OsuSkill {
|
|
|
1649
1643
|
protected readonly strainDecayBase = 0.3;
|
|
1650
1644
|
protected readonly reducedSectionCount = 5;
|
|
1651
1645
|
protected readonly reducedSectionBaseline = 0.75;
|
|
1652
|
-
protected readonly difficultyMultiplier = 1.04;
|
|
1653
1646
|
protected readonly decayWeight = 0.9;
|
|
1654
1647
|
private currentSpeedStrain;
|
|
1655
1648
|
private currentRhythm;
|
|
@@ -1671,8 +1664,11 @@ declare class OsuSpeed extends OsuSkill {
|
|
|
1671
1664
|
declare abstract class OsuSpeedEvaluator extends SpeedEvaluator {
|
|
1672
1665
|
/**
|
|
1673
1666
|
* Spacing threshold for a single hitobject spacing.
|
|
1667
|
+
*
|
|
1668
|
+
* About 1.25 circles distance between hitobject centers.
|
|
1674
1669
|
*/
|
|
1675
1670
|
private static readonly SINGLE_SPACING_THRESHOLD;
|
|
1671
|
+
private static readonly DISTANCE_MULTIPLIER;
|
|
1676
1672
|
/**
|
|
1677
1673
|
* Evaluates the difficulty of tapping the current object, based on:
|
|
1678
1674
|
*
|