From 7235de9e3aebb420ddfed17e4776ca04825b9891 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 30 Jan 2025 13:32:40 +0000 Subject: [PATCH 1/9] Calculate hit windows in performance calculator instead of databased difficulty attributes --- .../Difficulty/OsuDifficultyAttributes.cs | 25 -------------- .../Difficulty/OsuDifficultyCalculator.cs | 5 --- .../Difficulty/OsuPerformanceCalculator.cs | 34 ++++++++++++++----- .../Difficulty/DifficultyAttributes.cs | 1 - 4 files changed, 25 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 395f581b6539..264fe63b6dfc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -71,24 +71,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("overall_difficulty")] public double OverallDifficulty { get; set; } - /// - /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - [JsonProperty("great_hit_window")] - public double GreatHitWindow { get; set; } - - /// - /// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - [JsonProperty("ok_hit_window")] - public double OkHitWindow { get; set; } - - /// - /// The perceived hit window for a MEH hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - [JsonProperty("meh_hit_window")] - public double MehHitWindow { get; set; } - /// /// The beatmap's drain rate. This doesn't scale with rate-adjusting mods. /// @@ -119,7 +101,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty); yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_DIFFICULTY, StarRating); - yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); if (ShouldSerializeFlashlightDifficulty()) yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); @@ -130,9 +111,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount); yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount); yield return (ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT, AimDifficultSliderCount); - - yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow); - yield return (ATTRIB_ID_MEH_HIT_WINDOW, MehHitWindow); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -144,15 +122,12 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; StarRating = values[ATTRIB_ID_DIFFICULTY]; - GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT]; SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT]; SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; AimDifficultSliderCount = values[ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT]; - OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW]; - MehHitWindow = values[ATTRIB_ID_MEH_HIT_WINDOW]; DrainRate = onlineInfo.DrainRate; HitCircleCount = onlineInfo.CircleCount; SliderCount = onlineInfo.SliderCount; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 1505c5159251..9aaca0c7d9a2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -101,8 +101,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - double hitWindowOk = hitWindows.WindowFor(HitResult.Ok) / clockRate; - double hitWindowMeh = hitWindows.WindowFor(HitResult.Meh) / clockRate; OsuDifficultyAttributes attributes = new OsuDifficultyAttributes { @@ -118,9 +116,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat SpeedDifficultStrainCount = speedDifficultyStrainCount, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, - GreatHitWindow = hitWindowGreat, - OkHitWindow = hitWindowOk, - MehHitWindow = hitWindowMeh, DrainRate = drainRate, MaxCombo = beatmap.GetMaxCombo(), HitCircleCount = hitCirclesCount, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index f19118063094..38ff95d8caf1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,10 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -41,6 +45,11 @@ public class OsuPerformanceCalculator : PerformanceCalculator /// private double effectiveMissCount; + private double clockRate; + private double greatHitWindow; + private double okHitWindow; + private double mehHitWindow; + private double? speedDeviation; public OsuPerformanceCalculator() @@ -113,6 +122,17 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } + var track = new TrackVirtual(10000); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + clockRate = track.Rate; + + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(score.BeatmapInfo!.Difficulty.OverallDifficulty); + + greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate; + okHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate; + mehHitWindow = hitWindows.WindowFor(HitResult.Meh) / clockRate; + speedDeviation = calculateSpeedDeviation(osuAttributes); double aimValue = computeAimValue(score, osuAttributes); @@ -352,10 +372,6 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a double objectCount = relevantCountGreat + relevantCountOk + relevantCountMeh + relevantCountMiss; - double hitWindowGreat = attributes.GreatHitWindow; - double hitWindowOk = attributes.OkHitWindow; - double hitWindowMeh = attributes.MehHitWindow; - // The probability that a player hits a circle is unknown, but we can estimate it to be // the number of greats on circles divided by the number of circles, and then add one // to the number of circles as a bias correction. @@ -370,22 +386,22 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a // Compute the deviation assuming greats and oks are normally distributed, and mehs are uniformly distributed. // Begin with greats and oks first. Ignoring mehs, we can be 99% confident that the deviation is not higher than: - double deviation = hitWindowGreat / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound)); + double deviation = greatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound)); - double randomValue = Math.Sqrt(2 / Math.PI) * hitWindowOk * Math.Exp(-0.5 * Math.Pow(hitWindowOk / deviation, 2)) - / (deviation * DifficultyCalculationUtils.Erf(hitWindowOk / (Math.Sqrt(2) * deviation))); + double randomValue = Math.Sqrt(2 / Math.PI) * okHitWindow * Math.Exp(-0.5 * Math.Pow(okHitWindow / deviation, 2)) + / (deviation * DifficultyCalculationUtils.Erf(okHitWindow / (Math.Sqrt(2) * deviation))); deviation *= Math.Sqrt(1 - randomValue); // Value deviation approach as greatCount approaches 0 - double limitValue = hitWindowOk / Math.Sqrt(3); + double limitValue = okHitWindow / Math.Sqrt(3); // If precision is not enough to compute true deviation - use limit value if (pLowerBound == 0 || randomValue >= 1 || deviation > limitValue) deviation = limitValue; // Then compute the variance for mehs. - double mehVariance = (hitWindowMeh * hitWindowMeh + hitWindowOk * hitWindowMeh + hitWindowOk * hitWindowOk) / 3; + double mehVariance = (mehHitWindow * mehHitWindow + okHitWindow * mehHitWindow + okHitWindow * okHitWindow) / 3; // Find the total deviation. deviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(deviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 1d6cee043bfb..f5ed5a180b58 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -31,7 +31,6 @@ public class DifficultyAttributes protected const int ATTRIB_ID_OK_HIT_WINDOW = 27; protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29; protected const int ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT = 31; - protected const int ATTRIB_ID_MEH_HIT_WINDOW = 33; /// /// The mods which were applied to the beatmap. From 84ff83bc42aa5a5311e30c928dd013d34649d29d Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 30 Jan 2025 14:12:02 +0000 Subject: [PATCH 2/9] Apply mods to beatmap difficulty in osu! performance calculator --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 38ff95d8caf1..09ba29afe2eb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -126,6 +126,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); clockRate = track.Rate; + score.Mods.OfType().ForEach(m => m.ApplyToDifficulty(score.BeatmapInfo!.Difficulty)); + HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(score.BeatmapInfo!.Difficulty.OverallDifficulty); From 8f1ae13a01e9a2b7359203cb3b92d0dc5c97df75 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 30 Jan 2025 14:20:17 +0000 Subject: [PATCH 3/9] Remove `GreatHitWindow` difficulty attribute for osu!mania --- .../Difficulty/ManiaDifficultyAttributes.cs | 12 --------- .../Difficulty/ManiaDifficultyCalculator.cs | 27 ------------------- 2 files changed, 39 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index db60e757e111..512d98f7132d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; @@ -10,22 +9,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaDifficultyAttributes : DifficultyAttributes { - /// - /// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - /// - /// Rate-adjusting mods do not affect the hit window at all in osu-stable. - /// - [JsonProperty("great_hit_window")] - public double GreatHitWindow { get; set; } - public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) yield return v; yield return (ATTRIB_ID_DIFFICULTY, StarRating); - yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -33,7 +22,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val base.FromDatabaseAttributes(values, onlineInfo); StarRating = values[ATTRIB_ID_DIFFICULTY]; - GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 1efa7cb42f1b..a8d4726b340c 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -50,9 +50,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat { StarRating = skills.OfType().Single().DifficultyValue() * difficulty_multiplier, Mods = mods, - // In osu-stable mania, rate-adjustment mods don't affect the hit window. - // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. - GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), MaxCombo = beatmap.HitObjects.Sum(maxComboForObject), }; @@ -124,29 +121,5 @@ protected override Mod[] DifficultyAdjustmentMods }).ToArray(); } } - - private double getHitWindow300(Mod[] mods) - { - if (isForCurrentRuleset) - { - double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty)); - return applyModAdjustments(34 + 3 * od, mods); - } - - if (Math.Round(originalOverallDifficulty) > 4) - return applyModAdjustments(34, mods); - - return applyModAdjustments(47, mods); - - static double applyModAdjustments(double value, Mod[] mods) - { - if (mods.Any(m => m is ManiaModHardRock)) - value /= 1.4; - else if (mods.Any(m => m is ManiaModEasy)) - value *= 1.4; - - return value; - } - } } } From f710237685155a395b6283aae0e0a8e992f124df Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 5 Feb 2025 09:56:49 +0000 Subject: [PATCH 4/9] Remove use of approach rate and overall difficulty attributes for osu! --- .../Difficulty/OsuDifficultyAttributes.cs | 16 ----- .../Difficulty/OsuDifficultyCalculator.cs | 10 --- .../Difficulty/OsuPerformanceCalculator.cs | 64 +++++++++++-------- 3 files changed, 37 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 264fe63b6dfc..f7d8c649c12c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -59,18 +59,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("speed_difficult_strain_count")] public double SpeedDifficultStrainCount { get; set; } - /// - /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). - /// - [JsonProperty("approach_rate")] - public double ApproachRate { get; set; } - - /// - /// The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc). - /// - [JsonProperty("overall_difficulty")] - public double OverallDifficulty { get; set; } - /// /// The beatmap's drain rate. This doesn't scale with rate-adjusting mods. /// @@ -98,8 +86,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes yield return (ATTRIB_ID_AIM, AimDifficulty); yield return (ATTRIB_ID_SPEED, SpeedDifficulty); - yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty); - yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_DIFFICULTY, StarRating); if (ShouldSerializeFlashlightDifficulty()) @@ -119,8 +105,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val AimDifficulty = values[ATTRIB_ID_AIM]; SpeedDifficulty = values[ATTRIB_ID_SPEED]; - OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; - ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 9aaca0c7d9a2..30339fbaa7ab 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -15,8 +15,6 @@ using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -90,18 +88,12 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); - HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); - - double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - OsuDifficultyAttributes attributes = new OsuDifficultyAttributes { StarRating = starRating, @@ -114,8 +106,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat SliderFactor = sliderFactor, AimDifficultStrainCount = aimDifficultyStrainCount, SpeedDifficultStrainCount = speedDifficultyStrainCount, - ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, - OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, MaxCombo = beatmap.GetMaxCombo(), HitCircleCount = hitCirclesCount, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 09ba29afe2eb..a2619733662c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; @@ -49,6 +50,8 @@ public class OsuPerformanceCalculator : PerformanceCalculator private double greatHitWindow; private double okHitWindow; private double mehHitWindow; + private double overallDifficulty; + private double approachRate; private double? speedDeviation; @@ -73,6 +76,26 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); effectiveMissCount = countMiss; + var difficulty = score.BeatmapInfo!.Difficulty.Clone(); + + score.Mods.OfType().ForEach(m => m.ApplyToDifficulty(difficulty)); + + var track = new TrackVirtual(10000); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + clockRate = track.Rate; + + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(difficulty.OverallDifficulty); + + greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate; + okHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate; + mehHitWindow = hitWindows.WindowFor(HitResult.Meh) / clockRate; + + double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + + overallDifficulty = (80 - greatHitWindow) / 6; + approachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5; + if (osuAttributes.SliderCount > 0) { if (usingClassicSliderAccuracy) @@ -115,26 +138,13 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // https://www.desmos.com/calculator/bc9eybdthb // we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0 // this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11) - double okMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 1.8) : 1.0); - double mehMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 5) : 1.0); + double okMultiplier = Math.Max(0.0, overallDifficulty > 0.0 ? 1 - Math.Pow(overallDifficulty / 13.33, 1.8) : 1.0); + double mehMultiplier = Math.Max(0.0, overallDifficulty > 0.0 ? 1 - Math.Pow(overallDifficulty / 13.33, 5) : 1.0); // As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it. effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } - var track = new TrackVirtual(10000); - score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); - clockRate = track.Rate; - - score.Mods.OfType().ForEach(m => m.ApplyToDifficulty(score.BeatmapInfo!.Difficulty)); - - HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(score.BeatmapInfo!.Difficulty.OverallDifficulty); - - greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate; - okHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate; - mehHitWindow = hitWindows.WindowFor(HitResult.Meh) / clockRate; - speedDeviation = calculateSpeedDeviation(osuAttributes); double aimValue = computeAimValue(score, osuAttributes); @@ -200,10 +210,10 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount); double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - else if (attributes.ApproachRate < 8.0) - approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); + if (approachRate > 10.33) + approachRateFactor = 0.3 * (approachRate - 10.33); + else if (approachRate < 8.0) + approachRateFactor = 0.05 * (8.0 - approachRate); if (score.Mods.Any(h => h is OsuModRelax)) approachRateFactor = 0.0; @@ -215,12 +225,12 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + aimValue *= 1.0 + 0.04 * (12.0 - approachRate); } aimValue *= accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. - aimValue *= 0.98 + Math.Pow(Math.Max(0, attributes.OverallDifficulty), 2) / 2500; + aimValue *= 0.98 + Math.Pow(Math.Max(0, overallDifficulty), 2) / 2500; return aimValue; } @@ -240,8 +250,8 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount); double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + if (approachRate > 10.33) + approachRateFactor = 0.3 * (approachRate - 10.33); if (score.Mods.Any(h => h is OsuModAutopilot)) approachRateFactor = 0.0; @@ -256,7 +266,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + speedValue *= 1.0 + 0.04 * (12.0 - approachRate); } double speedHighDeviationMultiplier = calculateSpeedHighDeviationNerf(attributes); @@ -270,7 +280,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Math.Max(0, attributes.OverallDifficulty), 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - attributes.OverallDifficulty) / 2); + speedValue *= (0.95 + Math.Pow(Math.Max(0, overallDifficulty), 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - overallDifficulty) / 2); return speedValue; } @@ -297,7 +307,7 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution. - double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; + double accuracyValue = Math.Pow(1.52163, overallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer. accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); @@ -334,7 +344,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. - flashlightValue *= 0.98 + Math.Pow(Math.Max(0, attributes.OverallDifficulty), 2) / 2500; + flashlightValue *= 0.98 + Math.Pow(Math.Max(0, overallDifficulty), 2) / 2500; return flashlightValue; } From 5bf25a3cbf2fcaf010edb65dd77a6409bde2470d Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 5 Feb 2025 09:57:05 +0000 Subject: [PATCH 5/9] Remove use of hit window difficulty attributes in osu!taiko --- .../Difficulty/TaikoDifficultyAttributes.cs | 22 ---------------- .../Difficulty/TaikoDifficultyCalculator.cs | 5 ---- .../Difficulty/TaikoPerformanceCalculator.cs | 25 ++++++++++++++++--- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 37e6996e5abd..b43468ab18ae 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -49,32 +49,12 @@ public class TaikoDifficultyAttributes : DifficultyAttributes [JsonProperty("stamina_difficult_strains")] public double StaminaTopStrains { get; set; } - /// - /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - /// - /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. - /// - [JsonProperty("great_hit_window")] - public double GreatHitWindow { get; set; } - - /// - /// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc). - /// - /// - /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. - /// - [JsonProperty("ok_hit_window")] - public double OkHitWindow { get; set; } - public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) yield return v; yield return (ATTRIB_ID_DIFFICULTY, StarRating); - yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); - yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow); yield return (ATTRIB_ID_MONO_STAMINA_FACTOR, MonoStaminaFactor); } @@ -83,8 +63,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val base.FromDatabaseAttributes(values, onlineInfo); StarRating = values[ATTRIB_ID_DIFFICULTY]; - GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; - OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW]; MonoStaminaFactor = values[ATTRIB_ID_MONO_STAMINA_FACTOR]; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index f3b976f97048..fb916bc5b838 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -133,9 +133,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double combinedRating = combinedDifficultyValue(rhythm, reading, colour, stamina, isRelax, isConvert); double starRating = rescale(combinedRating * 1.4); - HitWindows hitWindows = new TaikoHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); - TaikoDifficultyAttributes attributes = new TaikoDifficultyAttributes { StarRating = starRating, @@ -148,8 +145,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat RhythmTopStrains = rhythmDifficultStrains, ColourTopStrains = colourDifficultStrains, StaminaTopStrains = staminaDifficultStrains, - GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, - OkHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate, MaxCombo = beatmap.GetMaxCombo(), }; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index bcd3693119e7..ee474c4f54c3 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -4,11 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty @@ -21,6 +24,9 @@ public class TaikoPerformanceCalculator : PerformanceCalculator private int countMiss; private double? estimatedUnstableRate; + private double clockRate; + private double greatHitWindow; + private double effectiveMissCount; public TaikoPerformanceCalculator() @@ -38,6 +44,19 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); estimatedUnstableRate = computeDeviationUpperBound(taikoAttributes) * 10; + var track = new TrackVirtual(10000); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + clockRate = track.Rate; + + var difficulty = score.BeatmapInfo!.Difficulty.Clone(); + + score.Mods.OfType().ForEach(m => m.ApplyToDifficulty(difficulty)); + + HitWindows hitWindows = new TaikoHitWindows(); + hitWindows.SetDifficulty(difficulty.OverallDifficulty); + + greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate; + // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; @@ -104,7 +123,7 @@ private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert) { - if (attributes.GreatHitWindow <= 0 || estimatedUnstableRate == null) + if (greatHitWindow <= 0 || estimatedUnstableRate == null) return 0; double accuracyValue = Math.Pow(70 / estimatedUnstableRate.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; @@ -125,7 +144,7 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a /// private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes) { - if (countGreat == 0 || attributes.GreatHitWindow <= 0) + if (countGreat == 0 || greatHitWindow <= 0) return null; const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). @@ -139,7 +158,7 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); // We can be 99% confident that the deviation is not higher than: - return attributes.GreatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound)); + return greatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound)); } private int totalHits => countGreat + countOk + countMeh + countMiss; From 6a1001f6366f14deb5e341e0348807d96d3bd88c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 5 Feb 2025 09:57:17 +0000 Subject: [PATCH 6/9] Remove use of approach rate attribute in osu!catch --- .../Difficulty/CatchDifficultyAttributes.cs | 12 ------------ .../Difficulty/CatchDifficultyCalculator.cs | 1 - .../Difficulty/CatchPerformanceCalculator.cs | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 5c64643fd4d0..82c3cfe7351b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; @@ -10,15 +9,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyAttributes : DifficultyAttributes { - /// - /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). - /// - /// - /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing. - /// - [JsonProperty("approach_rate")] - public double ApproachRate { get; set; } - public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) @@ -26,7 +16,6 @@ public class CatchDifficultyAttributes : DifficultyAttributes // Todo: osu!catch should not output star rating in the 'aim' attribute. yield return (ATTRIB_ID_AIM, StarRating); - yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -34,7 +23,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val base.FromDatabaseAttributes(values, onlineInfo); StarRating = values[ATTRIB_ID_AIM]; - ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 99df2731ff8b..790acc060081 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -43,7 +43,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat { StarRating = Math.Sqrt(skills.OfType().Single().DifficultyValue()) * difficulty_multiplier, Mods = mods, - ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, MaxCombo = beatmap.GetMaxCombo(), }; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 55232a9598c8..62a9fe250eea 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -3,6 +3,9 @@ using System; using System.Linq; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -50,7 +53,19 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s if (catchAttributes.MaxCombo > 0) value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0); - double approachRate = catchAttributes.ApproachRate; + var difficulty = score.BeatmapInfo!.Difficulty.Clone(); + + score.Mods.OfType().ForEach(m => m.ApplyToDifficulty(difficulty)); + + var track = new TrackVirtual(10000); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + double clockRate = track.Rate; + + // this is the same as osu!, so there's potential to share the implementation... maybe + double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + + double approachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0; + double approachRateFactor = 1.0; if (approachRate > 9.0) approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9 From 1f14242c4a384c0a2da644b413cc963ae586e381 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 5 Feb 2025 09:57:26 +0000 Subject: [PATCH 7/9] Remove unused attribute IDs --- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index f5ed5a180b58..59511973f7a1 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -17,18 +17,13 @@ public class DifficultyAttributes { protected const int ATTRIB_ID_AIM = 1; protected const int ATTRIB_ID_SPEED = 3; - protected const int ATTRIB_ID_OVERALL_DIFFICULTY = 5; - protected const int ATTRIB_ID_APPROACH_RATE = 7; protected const int ATTRIB_ID_MAX_COMBO = 9; protected const int ATTRIB_ID_DIFFICULTY = 11; - protected const int ATTRIB_ID_GREAT_HIT_WINDOW = 13; - protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23; protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25; - protected const int ATTRIB_ID_OK_HIT_WINDOW = 27; protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29; protected const int ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT = 31; From 7226e16c0eea76cbece7aee382c315466f34f0f8 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 5 Feb 2025 10:08:18 +0000 Subject: [PATCH 8/9] Code quality --- .../Difficulty/CatchDifficultyCalculator.cs | 3 --- .../Difficulty/ManiaDifficultyCalculator.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 790acc060081..6434adb63c94 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -36,9 +36,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (beatmap.HitObjects.Count == 0) return new CatchDifficultyAttributes { Mods = mods }; - // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; - CatchDifficultyAttributes attributes = new CatchDifficultyAttributes { StarRating = Math.Sqrt(skills.OfType().Single().DifficultyValue()) * difficulty_multiplier, diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index a8d4726b340c..06b8018f2b46 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -27,7 +27,6 @@ public class ManiaDifficultyCalculator : DifficultyCalculator private const double difficulty_multiplier = 0.018; private readonly bool isForCurrentRuleset; - private readonly double originalOverallDifficulty; public override int Version => 20241007; @@ -35,7 +34,6 @@ public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset); - originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) From 78781ccb4776fdcceb26582b4890587d05e0e577 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 6 Feb 2025 11:03:06 +0000 Subject: [PATCH 9/9] Fix `computeDeviationUpperBound` being called before `greatHitWindow` is set --- .../Difficulty/TaikoPerformanceCalculator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ee474c4f54c3..9e049df87c6c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -42,7 +42,6 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - estimatedUnstableRate = computeDeviationUpperBound(taikoAttributes) * 10; var track = new TrackVirtual(10000); score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); @@ -57,6 +56,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate; + estimatedUnstableRate = computeDeviationUpperBound() * 10; + // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; @@ -142,7 +143,7 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a /// and the hit judgements, assuming the player's mean hit error is 0. The estimation is consistent in that /// two SS scores on the same map with the same settings will always return the same deviation. /// - private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes) + private double? computeDeviationUpperBound() { if (countGreat == 0 || greatHitWindow <= 0) return null;