From e0019c46a56c6c008164cdb7ae3cdb20720a00ad Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Sun, 22 Dec 2024 04:03:52 +1300 Subject: [PATCH 01/14] scale misscount by proportion of difficult sliders --- .../Difficulty/OsuDifficultyAttributes.cs | 4 ++++ .../Difficulty/OsuDifficultyCalculator.cs | 7 +++++++ .../Difficulty/OsuPerformanceCalculator.cs | 4 ++-- .../Difficulty/Skills/Aim.cs | 3 +++ .../Difficulty/Skills/OsuStrainSkill.cs | 19 +++++++++++++++++++ .../Difficulty/Skills/Speed.cs | 2 ++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a3c0209a08f3..0c47521ae840 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -45,6 +45,10 @@ public class OsuDifficultyAttributes : DifficultyAttributes /// [JsonProperty("slider_factor")] public double SliderFactor { get; set; } + [JsonProperty("aim_top_weighted_slider_factor")] + public double AimTopWeightedSliderFactor { get; set; } + [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 575e03051c75..d904285613f5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -50,6 +50,11 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); + (double hitCircles, double sliders) aimTypedDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTypedTopWeightedStrains(); + (double hitCircles, double sliders) speedTypedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTypedTopWeightedStrains(); + + double aimTopWeightedSliderFactor = aimTypedDifficultyStrainCount.sliders / aimTypedDifficultyStrainCount.hitCircles; + double speedTopWeightedSliderFactor = speedTypedDifficultyStrainCount.sliders / speedTypedDifficultyStrainCount.hitCircles; if (mods.Any(m => m is OsuModTouchDevice)) { @@ -105,6 +110,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, DrainRate = drainRate, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 31b00dba2b30..ce3d6aeec18c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -142,7 +142,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount); + aimValue *= calculateMissPenalty(effectiveMissCount * (1 + (usingClassicSliderAccuracy ? attributes.AimTopWeightedSliderFactor : 0)), attributes.AimDifficultStrainCount); double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) @@ -206,7 +206,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount); + speedValue *= calculateMissPenalty(effectiveMissCount * (1 + (usingClassicSliderAccuracy ? attributes.SpeedTopWeightedSliderFactor : 0)), attributes.SpeedDifficultStrainCount); double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index faf91e46520b..233e45de6e7b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -35,6 +36,8 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(current.DeltaTime); currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; + typedObjectStrains.Add((currentStrain, current.BaseObject is Slider)); + return currentStrain; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 6823512cef12..8d9f3c9b56a7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -22,6 +22,7 @@ public abstract class OsuStrainSkill : StrainSkill /// The baseline multiplier applied to the section with the biggest strain. /// protected virtual double ReducedStrainBaseline => 0.75; + protected List<(double difficulty, bool isSlider)> typedObjectStrains = new List<(double, bool)>(); protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -56,6 +57,24 @@ public override double DifficultyValue() return difficulty; } + public (double hitCircles, double sliders) CountTypedTopWeightedStrains() + { + if (typedObjectStrains.Count == 0) + return (0.0, 0.0); + + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + List sliderStrains = typedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); + List circleStrains = typedObjectStrains.Where(typedObjectStrain => !typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); + + + if (consistentTopStrain == 0) + return (circleStrains.Count, sliderStrains.Count); + + // Use a weighted sum of all strains. Constants are arbitrary and give nice values + double sliderObjects = sliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + double circleObjects = circleStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + return (circleObjects, sliderObjects); + } 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 d2c4bbb6180e..ecf82ec9260f 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,7 @@ protected override double StrainValueAt(DifficultyHitObject current) currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; + typedObjectStrains.Add((totalStrain, current.BaseObject is Slider)); return totalStrain; } From 966efedf0b4a490d30d4b1723b0c2bd09efc5b72 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Sun, 22 Dec 2024 16:53:06 +1300 Subject: [PATCH 02/14] cap sliderbreak count at count100 + count50 --- .../Difficulty/OsuPerformanceCalculator.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ce3d6aeec18c..978bd2e61f10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -142,8 +142,10 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= calculateMissPenalty(effectiveMissCount * (1 + (usingClassicSliderAccuracy ? attributes.AimTopWeightedSliderFactor : 0)), attributes.AimDifficultStrainCount); - + { + double estimatedSliderbreaks = Math.Min(countMeh + countOk, effectiveMissCount * attributes.AimTopWeightedSliderFactor); + aimValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.AimDifficultStrainCount); + } double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); @@ -206,7 +208,10 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= calculateMissPenalty(effectiveMissCount * (1 + (usingClassicSliderAccuracy ? attributes.SpeedTopWeightedSliderFactor : 0)), attributes.SpeedDifficultStrainCount); + { + double estimatedSliderbreaks = Math.Min(countMeh + countOk, effectiveMissCount * attributes.SpeedTopWeightedSliderFactor); + speedValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.SpeedDifficultStrainCount); + } double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) From 3e5f319c75478e245cf593f0b035d823e784524d Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Sun, 22 Dec 2024 16:54:15 +1300 Subject: [PATCH 03/14] use countMiss instead of effectiveMissCount as the base for sliderbreaks --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 978bd2e61f10..4962e3053454 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -143,7 +143,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (effectiveMissCount > 0) { - double estimatedSliderbreaks = Math.Min(countMeh + countOk, effectiveMissCount * attributes.AimTopWeightedSliderFactor); + double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.AimTopWeightedSliderFactor); aimValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.AimDifficultStrainCount); } double approachRateFactor = 0.0; @@ -209,7 +209,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (effectiveMissCount > 0) { - double estimatedSliderbreaks = Math.Min(countMeh + countOk, effectiveMissCount * attributes.SpeedTopWeightedSliderFactor); + double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.SpeedTopWeightedSliderFactor); speedValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.SpeedDifficultStrainCount); } From 081e9ae8ef117b68e4ead0283c57bee91d0e5e92 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Sun, 22 Dec 2024 17:30:30 +1300 Subject: [PATCH 04/14] make code inspector happy + cleanup --- .../Difficulty/OsuDifficultyAttributes.cs | 12 ++++++++++++ .../Difficulty/OsuDifficultyCalculator.cs | 6 ++---- .../Difficulty/OsuPerformanceCalculator.cs | 1 + .../Difficulty/Skills/Aim.cs | 9 ++++----- .../Difficulty/Skills/OsuStrainSkill.cs | 19 ++++++++++--------- .../Difficulty/Skills/Speed.cs | 2 +- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index d6fc5c86e768..b46f5258bdc2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -52,8 +52,20 @@ 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; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 61c02180be4f..46b8b154bf2d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -50,11 +50,9 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); - (double hitCircles, double sliders) aimTypedDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTypedTopWeightedStrains(); - (double hitCircles, double sliders) speedTypedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTypedTopWeightedStrains(); - double aimTopWeightedSliderFactor = aimTypedDifficultyStrainCount.sliders / aimTypedDifficultyStrainCount.hitCircles; - double speedTopWeightedSliderFactor = speedTypedDifficultyStrainCount.sliders / speedTypedDifficultyStrainCount.hitCircles; + double aimTopWeightedSliderFactor = ((OsuStrainSkill)skills[0]).CalculateTopWeightedSliderFactor(); + double speedTopWeightedSliderFactor = ((OsuStrainSkill)skills[2]).CalculateTopWeightedSliderFactor(); if (mods.Any(m => m is OsuModTouchDevice)) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 67ffffe3b1b3..bde62d5fff42 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -169,6 +169,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.AimTopWeightedSliderFactor); aimValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.AimDifficultStrainCount); } + double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 71946d0ccec6..773c76043d3b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -29,8 +29,6 @@ public Aim(Mod[] mods, bool withSliders) private double skillMultiplier => 25.18; 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); @@ -40,17 +38,18 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(current.DeltaTime); currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; - typedObjectStrains.Add((currentStrain, current.BaseObject is Slider)); + TypedObjectStrains.Add((currentStrain, current.BaseObject is Slider)); return currentStrain; } public double GetDifficultSliders() { - if (sliderStrains.Count == 0) + IEnumerable sliderStrains = TypedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty); + if (!sliderStrains.Any()) return 0; - double[] sortedStrains = typedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).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 8d9f3c9b56a7..857144245b56 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -22,7 +22,8 @@ public abstract class OsuStrainSkill : StrainSkill /// The baseline multiplier applied to the section with the biggest strain. /// protected virtual double ReducedStrainBaseline => 0.75; - protected List<(double difficulty, bool isSlider)> typedObjectStrains = new List<(double, bool)>(); + + protected List<(double difficulty, bool isSlider)> TypedObjectStrains = new List<(double, bool)>(); protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -57,23 +58,23 @@ public override double DifficultyValue() return difficulty; } - public (double hitCircles, double sliders) CountTypedTopWeightedStrains() + + public double CalculateTopWeightedSliderFactor() { - if (typedObjectStrains.Count == 0) - return (0.0, 0.0); + if (TypedObjectStrains.Count == 0) + return 0; double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical - List sliderStrains = typedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); - List circleStrains = typedObjectStrains.Where(typedObjectStrain => !typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); - + List sliderStrains = TypedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); + List circleStrains = TypedObjectStrains.Where(typedObjectStrain => !typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); if (consistentTopStrain == 0) - return (circleStrains.Count, sliderStrains.Count); + return (double)sliderStrains.Count / circleStrains.Count; // Use a weighted sum of all strains. Constants are arbitrary and give nice values double sliderObjects = sliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); double circleObjects = circleStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); - return (circleObjects, sliderObjects); + return sliderObjects / circleObjects; } 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 ecf82ec9260f..76d113fa493c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -41,7 +41,7 @@ protected override double StrainValueAt(DifficultyHitObject current) currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; - typedObjectStrains.Add((totalStrain, current.BaseObject is Slider)); + TypedObjectStrains.Add((totalStrain, current.BaseObject is Slider)); return totalStrain; } From 845459b767aaf52eb21d31b7582579628300b2ff Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Fri, 27 Dec 2024 22:58:44 +1300 Subject: [PATCH 05/14] refactor to remove unnecesary calculation and need for new tuple --- .../Difficulty/OsuDifficultyCalculator.cs | 8 ++++++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 +++--- .../Difficulty/Skills/OsuStrainSkill.cs | 14 +++++--------- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 ++- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 46b8b154bf2d..281918fd3793 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -51,8 +51,12 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); - double aimTopWeightedSliderFactor = ((OsuStrainSkill)skills[0]).CalculateTopWeightedSliderFactor(); - double speedTopWeightedSliderFactor = ((OsuStrainSkill)skills[2]).CalculateTopWeightedSliderFactor(); + double aimTopWeightedSliderCount = ((OsuStrainSkill)skills[0]).CountTopWeightedSliders(); + // We know that slider count + circle count = total count, so circle count = total count - slider count. + // This means we only need to calculate on sliders, and then work from the total count + double aimTopWeightedSliderFactor = aimTopWeightedSliderCount / (aimDifficultyStrainCount - aimTopWeightedSliderCount); + double speedTopWeightedSliderCount = ((OsuStrainSkill)skills[2]).CountTopWeightedSliders(); + double speedTopWeightedSliderFactor = speedTopWeightedSliderCount / (speedDifficultyStrainCount - speedTopWeightedSliderCount); if (mods.Any(m => m is OsuModTouchDevice)) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 773c76043d3b..bbd13b68f7dd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -38,15 +38,15 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(current.DeltaTime); currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; - TypedObjectStrains.Add((currentStrain, current.BaseObject is Slider)); + if (current.BaseObject is Slider) + sliderStrains.Add(currentStrain); return currentStrain; } public double GetDifficultSliders() { - IEnumerable sliderStrains = TypedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty); - if (!sliderStrains.Any()) + if (sliderStrains.Count == 0) return 0; double[] sortedStrains = sliderStrains.OrderDescending().ToArray(); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 857144245b56..e12d0a45b01e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,7 +23,7 @@ public abstract class OsuStrainSkill : StrainSkill /// protected virtual double ReducedStrainBaseline => 0.75; - protected List<(double difficulty, bool isSlider)> TypedObjectStrains = new List<(double, bool)>(); + protected readonly List sliderStrains = new List(); protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -59,22 +59,18 @@ public override double DifficultyValue() return difficulty; } - public double CalculateTopWeightedSliderFactor() + public double CountTopWeightedSliders() { - if (TypedObjectStrains.Count == 0) + if (sliderStrains.Count == 0) return 0; double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical - List sliderStrains = TypedObjectStrains.Where(typedObjectStrain => typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); - List circleStrains = TypedObjectStrains.Where(typedObjectStrain => !typedObjectStrain.isSlider).Select(typedObjectStrain => typedObjectStrain.difficulty).ToList(); if (consistentTopStrain == 0) - return (double)sliderStrains.Count / circleStrains.Count; + return 0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values - double sliderObjects = sliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); - double circleObjects = circleStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); - return sliderObjects / circleObjects; + return sliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } 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 76d113fa493c..c79de5db3b15 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -41,7 +41,8 @@ protected override double StrainValueAt(DifficultyHitObject current) currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; - TypedObjectStrains.Add((totalStrain, current.BaseObject is Slider)); + if (current.BaseObject is Slider) + sliderStrains.Add(totalStrain); return totalStrain; } From 2f886e78c935bfef7ac0b7b3074ba195735c1a9a Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Sun, 29 Dec 2024 12:53:55 +1300 Subject: [PATCH 06/14] scale sliderbreaks with combo --- .../Difficulty/OsuPerformanceCalculator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bde62d5fff42..43ca18cb2303 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; @@ -166,7 +167,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (effectiveMissCount > 0) { - double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.AimTopWeightedSliderFactor); + double missedComboPercent = 1.0 - (double)score.MaxCombo / attributes.MaxCombo; + double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.AimTopWeightedSliderFactor) * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); aimValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.AimDifficultStrainCount); } @@ -209,7 +211,8 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (effectiveMissCount > 0) { - double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.SpeedTopWeightedSliderFactor); + double missedComboPercent = 1.0 - (double)score.MaxCombo / attributes.MaxCombo; + double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.SpeedTopWeightedSliderFactor) * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); speedValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.SpeedDifficultStrainCount); } From 6b492b2bb028ab7e3b9fa51bce5278a5fce01bd6 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Wed, 1 Jan 2025 01:09:36 +1300 Subject: [PATCH 07/14] use aimNoSliders for sliderbreak factor --- .../Difficulty/OsuDifficultyCalculator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 281918fd3793..eb3a44c07e1a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -51,10 +51,9 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); - double aimTopWeightedSliderCount = ((OsuStrainSkill)skills[0]).CountTopWeightedSliders(); - // We know that slider count + circle count = total count, so circle count = total count - slider count. - // This means we only need to calculate on sliders, and then work from the total count - double aimTopWeightedSliderFactor = aimTopWeightedSliderCount / (aimDifficultyStrainCount - aimTopWeightedSliderCount); + 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); From 09f6889eae605a5642cab177328b21c8a4bbe739 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Wed, 8 Jan 2025 01:51:32 +1300 Subject: [PATCH 08/14] code cleanup --- .../Difficulty/OsuPerformanceCalculator.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 43ca18cb2303..5572ba0af49d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -167,9 +167,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (effectiveMissCount > 0) { - double missedComboPercent = 1.0 - (double)score.MaxCombo / attributes.MaxCombo; - double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.AimTopWeightedSliderFactor) * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); - aimValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.AimDifficultStrainCount); + double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.AimTopWeightedSliderFactor, attributes); + aimValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.AimDifficultStrainCount); } double approachRateFactor = 0.0; @@ -211,9 +210,8 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (effectiveMissCount > 0) { - double missedComboPercent = 1.0 - (double)score.MaxCombo / attributes.MaxCombo; - double estimatedSliderbreaks = Math.Min(countMeh + countOk, countMiss * attributes.SpeedTopWeightedSliderFactor) * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); - speedValue *= calculateMissPenalty(effectiveMissCount + (usingClassicSliderAccuracy ? estimatedSliderbreaks : 0), attributes.SpeedDifficultStrainCount); + double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.SpeedTopWeightedSliderFactor, attributes); + speedValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.SpeedDifficultStrainCount); } double approachRateFactor = 0.0; @@ -318,6 +316,14 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a // to make it more punishing on maps with lower amount of hard sections. 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) + return 0; + double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo; + double estimatedSliderbreaks = countMiss * topWeightedSliderFactor * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); + return Math.Min(countMeh + countOk, estimatedSliderbreaks); + } private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalImperfectHits => countOk + countMeh + countMiss; } From 072b3b20abe9e93b7bc98ad023e210deb47651d3 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Wed, 8 Jan 2025 02:11:17 +1300 Subject: [PATCH 09/14] make inspect code happy --- .../Difficulty/OsuPerformanceCalculator.cs | 3 +++ osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 7 +++---- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 6 +++--- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5572ba0af49d..e6600d1ce085 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -316,14 +316,17 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a // to make it more punishing on maps with lower amount of hard sections. 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) return 0; + double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo; double estimatedSliderbreaks = countMiss * topWeightedSliderFactor * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); return Math.Min(countMeh + countOk, estimatedSliderbreaks); } + private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalImperfectHits => countOk + countMeh + countMiss; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index bbd13b68f7dd..b413db6d35af 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; @@ -39,17 +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 e12d0a45b01e..3bb2ef21c292 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,7 +23,7 @@ public abstract class OsuStrainSkill : StrainSkill /// protected virtual double ReducedStrainBaseline => 0.75; - protected readonly List sliderStrains = new List(); + protected readonly List SliderStrains = new List(); protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -61,7 +61,7 @@ public override double DifficultyValue() public double CountTopWeightedSliders() { - if (sliderStrains.Count == 0) + if (SliderStrains.Count == 0) return 0; double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical @@ -70,7 +70,7 @@ public double CountTopWeightedSliders() return 0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return sliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + return SliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } 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 c79de5db3b15..d107cb75b742 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -42,7 +42,7 @@ protected override double StrainValueAt(DifficultyHitObject current) double totalStrain = currentStrain * currentRhythm; if (current.BaseObject is Slider) - sliderStrains.Add(totalStrain); + SliderStrains.Add(totalStrain); return totalStrain; } From 8436382b58d0bacb77064537bdba090357fa4efa Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Mon, 13 Jan 2025 14:23:20 +1300 Subject: [PATCH 10/14] use diffcalcutils --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 3bb2ef21c292..287c414be39f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -70,7 +70,7 @@ public double CountTopWeightedSliders() return 0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return SliderStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + return SliderStrains.Sum(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; From 671bf2dbdd97fbbd64e529d478711db43cfc356b Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Mon, 13 Jan 2025 22:51:11 +1300 Subject: [PATCH 11/14] fix errors (oops) --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 287c414be39f..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 { @@ -70,7 +71,7 @@ public double CountTopWeightedSliders() return 0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return SliderStrains.Sum(DifficultyCalculationUtils.Logistic(s / consistentTopStrain, 0.88, 10, 1.1)); + 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; From 898f1970919943ed5120eddaea298079f64b901f Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Mon, 13 Jan 2025 23:38:29 +1300 Subject: [PATCH 12/14] scaling changes --- .../Difficulty/OsuPerformanceCalculator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 462c47c4209e..d863a208b073 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -438,12 +438,14 @@ private double calculateEstimatedSliderbreaks(double topWeightedSliderFactor, Os return 0; double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo; - double estimatedSliderbreaks = countMiss * topWeightedSliderFactor * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15); - return Math.Min(countMeh + countOk, estimatedSliderbreaks); + 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 From 9ea844a464e4b8e1a5cf5500d34e97649932cc1f Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Tue, 14 Jan 2025 01:17:01 +1300 Subject: [PATCH 13/14] fix div by zeros --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d863a208b073..5582ade5d4ac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -434,7 +434,7 @@ private double calculateSpeedHighDeviationNerf(OsuDifficultyAttributes attribute private double calculateEstimatedSliderbreaks(double topWeightedSliderFactor, OsuDifficultyAttributes attributes) { - if (!usingClassicSliderAccuracy) + if (!usingClassicSliderAccuracy || countOk == 0) return 0; double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo; From 6ac10675531cb17105e9f9717e7bd744c77885a3 Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Fri, 17 Jan 2025 14:50:19 +1300 Subject: [PATCH 14/14] add estimated sliderbreaks to performance attributes --- .../Difficulty/OsuPerformanceAttributes.cs | 6 ++++++ .../Difficulty/OsuPerformanceCalculator.cs | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) 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 ea91fbbbfbcb..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 }; } @@ -177,8 +190,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (effectiveMissCount > 0) { - double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.AimTopWeightedSliderFactor, attributes); - aimValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.AimDifficultStrainCount); + aimValue *= calculateMissPenalty(effectiveMissCount + estimatedAimSliderbreaks, attributes.AimDifficultStrainCount); } double approachRateFactor = 0.0; @@ -220,8 +232,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (effectiveMissCount > 0) { - double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.SpeedTopWeightedSliderFactor, attributes); - speedValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.SpeedDifficultStrainCount); + speedValue *= calculateMissPenalty(effectiveMissCount + estimatedSpeedSliderbreaks, attributes.SpeedDifficultStrainCount); } double approachRateFactor = 0.0;