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; }