diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 395f581b6539..f41413b9bbd7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -53,6 +53,22 @@ public class OsuDifficultyAttributes : DifficultyAttributes
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
+ ///
+ /// Describes how much of is contributed to by hitcircles or sliders
+ /// A value closer to 0.0 indicates most of is contributed by hitcircles
+ /// A value closer to Infinity indicates most of is contributed by sliders
+ ///
+ [JsonProperty("aim_top_weighted_slider_factor")]
+ public double AimTopWeightedSliderFactor { get; set; }
+
+ ///
+ /// Describes how much of is contributed to by hitcircles or sliders
+ /// A value closer to 0.0 indicates most of is contributed by hitcircles
+ /// A value closer to Infinity indicates most of is contributed by sliders
+ ///
+ [JsonProperty("speed_top_weighted_slider_factor")]
+ public double SpeedTopWeightedSliderFactor { get; set; }
+
[JsonProperty("aim_difficult_strain_count")]
public double AimDifficultStrainCount { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 5a61ea586a29..1f5334d198ee 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -51,6 +51,12 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains();
double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains();
+ double aimNoSlidersTopWeightedSliderCount = ((OsuStrainSkill)skills[1]).CountTopWeightedSliders();
+ double aimNoSlidersDifficultyStrainCount = ((OsuStrainSkill)skills[1]).CountTopWeightedStrains();
+ double aimTopWeightedSliderFactor = aimNoSlidersTopWeightedSliderCount / (aimNoSlidersDifficultyStrainCount - aimNoSlidersTopWeightedSliderCount);
+ double speedTopWeightedSliderCount = ((OsuStrainSkill)skills[2]).CountTopWeightedSliders();
+ double speedTopWeightedSliderFactor = speedTopWeightedSliderCount / (speedDifficultyStrainCount - speedTopWeightedSliderCount);
+
if (mods.Any(m => m is OsuModTouchDevice))
{
aimRating = Math.Pow(aimRating, 0.8);
@@ -114,6 +120,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
SliderFactor = sliderFactor,
AimDifficultStrainCount = aimDifficultyStrainCount,
SpeedDifficultStrainCount = speedDifficultyStrainCount,
+ AimTopWeightedSliderFactor = aimTopWeightedSliderFactor,
+ SpeedTopWeightedSliderFactor = speedTopWeightedSliderFactor,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
GreatHitWindow = hitWindowGreat,
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
index de4491a31b5b..e20931c04f4d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
@@ -27,6 +27,12 @@ public class OsuPerformanceAttributes : PerformanceAttributes
[JsonProperty("speed_deviation")]
public double? SpeedDeviation { get; set; }
+ [JsonProperty("estimated_aim_sliderbreaks")]
+ public double EstimatedAimSliderbreaks { get; set; }
+
+ [JsonProperty("estimated_speed_sliderbreaks")]
+ public double EstimatedSpeedSliderbreaks { get; set; }
+
public override IEnumerable GetAttributesForDisplay()
{
foreach (var attribute in base.GetAttributesForDisplay())
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 7013ee55c407..93dd9b3ebea5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -42,6 +42,15 @@ public class OsuPerformanceCalculator : PerformanceCalculator
///
private double effectiveMissCount;
+ ///
+ /// estimated number of sliderbreaks from aim difficulty
+ ///
+ private double estimatedAimSliderbreaks;
+ ///
+ /// estimated number of sliderbreaks from speed difficulty
+ ///
+ private double estimatedSpeedSliderbreaks;
+
private double? speedDeviation;
public OsuPerformanceCalculator()
@@ -93,6 +102,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
effectiveMissCount = Math.Min(totalHits, effectiveMissCount);
+ estimatedAimSliderbreaks = calculateEstimatedSliderbreaks(osuAttributes.AimTopWeightedSliderFactor, osuAttributes);
+ estimatedSpeedSliderbreaks = calculateEstimatedSliderbreaks(osuAttributes.SpeedTopWeightedSliderFactor, osuAttributes);
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
@@ -137,6 +148,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
Flashlight = flashlightValue,
EffectiveMissCount = effectiveMissCount,
SpeedDeviation = speedDeviation,
+ EstimatedAimSliderbreaks = estimatedAimSliderbreaks,
+ EstimatedSpeedSliderbreaks = estimatedSpeedSliderbreaks,
Total = totalValue
};
}
@@ -176,7 +189,9 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
+ {
+ aimValue *= calculateMissPenalty(effectiveMissCount + estimatedAimSliderbreaks, attributes.AimDifficultStrainCount);
+ }
double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
@@ -216,7 +231,9 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
+ {
+ speedValue *= calculateMissPenalty(effectiveMissCount + estimatedSpeedSliderbreaks, attributes.SpeedDifficultStrainCount);
+ }
double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
@@ -426,8 +443,20 @@ private double calculateSpeedHighDeviationNerf(OsuDifficultyAttributes attribute
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
+ private double calculateEstimatedSliderbreaks(double topWeightedSliderFactor, OsuDifficultyAttributes attributes)
+ {
+ if (!usingClassicSliderAccuracy || countOk == 0)
+ return 0;
+
+ double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo;
+ double estimatedSliderbreaks = Math.Min(countOk, effectiveMissCount * topWeightedSliderFactor);
+ // scores with more oks are more likely to have sliderbreaks
+ double okAdjustment = ((countOk - estimatedSliderbreaks) + 0.5) / countOk;
+ return estimatedSliderbreaks * okAdjustment * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15);
+ }
+
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
private int totalImperfectHits => countOk + countMeh + countMiss;
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index f04b679b73b1..4d0ec5926daf 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
@@ -29,8 +28,6 @@ public Aim(Mod[] mods, bool withSliders)
private double skillMultiplier => 25.6;
private double strainDecayBase => 0.15;
- private readonly List sliderStrains = new List();
-
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
@@ -41,19 +38,17 @@ protected override double StrainValueAt(DifficultyHitObject current)
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier;
if (current.BaseObject is Slider)
- {
- sliderStrains.Add(currentStrain);
- }
+ SliderStrains.Add(currentStrain);
return currentStrain;
}
public double GetDifficultSliders()
{
- if (sliderStrains.Count == 0)
+ if (SliderStrains.Count == 0)
return 0;
- double[] sortedStrains = sliderStrains.OrderDescending().ToArray();
+ double[] sortedStrains = SliderStrains.OrderDescending().ToArray();
double maxSliderStrain = sortedStrains.Max();
if (maxSliderStrain == 0)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 6823512cef12..a02dba8d237f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -7,6 +7,7 @@
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Framework.Utils;
+using osu.Game.Rulesets.Difficulty.Utils;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@@ -23,6 +24,8 @@ public abstract class OsuStrainSkill : StrainSkill
///
protected virtual double ReducedStrainBaseline => 0.75;
+ protected readonly List SliderStrains = new List();
+
protected OsuStrainSkill(Mod[] mods)
: base(mods)
{
@@ -57,6 +60,20 @@ public override double DifficultyValue()
return difficulty;
}
+ public double CountTopWeightedSliders()
+ {
+ if (SliderStrains.Count == 0)
+ return 0;
+
+ double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical
+
+ if (consistentTopStrain == 0)
+ return 0;
+
+ // Use a weighted sum of all strains. Constants are arbitrary and give nice values
+ return SliderStrains.Sum(s => DifficultyCalculationUtils.Logistic(s / consistentTopStrain, 0.88, 10, 1.1));
+ }
+
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index bdeea0e918eb..240a360cb948 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -6,6 +6,7 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Objects;
using System.Linq;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
@@ -40,6 +41,8 @@ protected override double StrainValueAt(DifficultyHitObject current)
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
double totalStrain = currentStrain * currentRhythm;
+ if (current.BaseObject is Slider)
+ SliderStrains.Add(totalStrain);
return totalStrain;
}