diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 7e8d567fbe..48d46636df 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.0505463516206195d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(4.0505463516206195d, 127, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(5.1696411260785498d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new CatchModDoubleTime()); + [TestCase(5.1696411260785498d, 127, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new CatchModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 6ec49d7634..715614a201 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.3449735700206298d, 151, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new ManiaModDoubleTime()); + [TestCase(2.7879104989252959d, 151, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b7984e6995..df577ea8d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,15 +15,20 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6972307565739273d, "diffcalc-test")] - [TestCase(1.4484754139145539d, "zero-length-sliders")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(6.6972307565739273d, 206, "diffcalc-test")] + [TestCase(1.4484754139145539d, 45, "zero-length-sliders")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9382559208689809d, "diffcalc-test")] - [TestCase(1.7548875851757628d, "zero-length-sliders")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new OsuModDoubleTime()); + [TestCase(8.9382559208689809d, 206, "diffcalc-test")] + [TestCase(1.7548875851757628d, 45, "zero-length-sliders")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); + + [TestCase(6.6972307218715166d, 239, "diffcalc-test")] + [TestCase(1.4484754139145537d, 54, "zero-length-sliders")] + public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c5b1baaad1..df6fd19d36 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -61,10 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - - int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + int maxCombo = beatmap.GetMaxCombo(); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 2b1cbc580e..226da7df09 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,15 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2420075288523802d, "diffcalc-test")] - [TestCase(2.2420075288523802d, "diffcalc-test-strong")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.2420075288523802d, 200, "diffcalc-test")] + [TestCase(2.2420075288523802d, 200, "diffcalc-test-strong")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(3.134084469440479d, "diffcalc-test")] - [TestCase(3.134084469440479d, "diffcalc-test-strong")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new TaikoModDoubleTime()); + [TestCase(3.134084469440479d, 200, "diffcalc-test")] + [TestCase(3.134084469440479d, 200, "diffcalc-test-strong")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset().RulesetInfo, beatmap); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3f598cd1e5..dec1ef4294 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Beatmaps { @@ -70,4 +71,27 @@ namespace osu.Game.Beatmaps /// new IReadOnlyList HitObjects { get; } } + + public static class BeatmapExtensions + { + /// + /// Finds the maximum achievable combo by hitting all s in a beatmap. + /// + public static int GetMaxCombo(this IBeatmap beatmap) + { + int combo = 0; + foreach (var h in beatmap.HitObjects) + addCombo(h, ref combo); + return combo; + + static void addCombo(HitObject hitObject, ref int combo) + { + if (hitObject.CreateJudgement().MaxResult.AffectsCombo()) + combo++; + + foreach (var nested in hitObject.NestedHitObjects) + addCombo(nested, ref combo); + } + } + } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6b61dd3efb..b5aec0d659 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Difficulty { @@ -119,15 +120,23 @@ namespace osu.Game.Rulesets.Difficulty /// /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// + /// + /// This can only be used to compute difficulties for legacy mod combinations. + /// /// A collection of structures describing the difficulty of the beatmap for each mod combination. - public IEnumerable CalculateAll(CancellationToken cancellationToken = default) + public IEnumerable CalculateAllLegacyCombinations(CancellationToken cancellationToken = default) { + var rulesetInstance = ruleset.CreateInstance(); + foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { - if (combination is MultiMod multi) - yield return Calculate(multi.Mods, cancellationToken); - else - yield return Calculate(combination.Yield(), cancellationToken); + Mod classicMod = rulesetInstance.CreateAllMods().SingleOrDefault(m => m is ModClassic); + + var finalCombination = ModUtils.FlattenMod(combination); + if (classicMod != null) + finalCombination = finalCombination.Append(classicMod); + + yield return Calculate(finalCombination.ToArray(), cancellationToken); } } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 9f8811c7f9..ed00c7959b 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,10 +22,13 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - protected void Test(double expected, string name, params Mod[] mods) + protected void Test(double expectedStarRating, int expectedMaxCombo, string name, params Mod[] mods) { + var attributes = CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods); + // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. - Assert.That(CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating, Is.EqualTo(expected).Within(0.00001)); + Assert.That(attributes.StarRating, Is.EqualTo(expectedStarRating).Within(0.00001)); + Assert.That(attributes.MaxCombo, Is.EqualTo(expectedMaxCombo)); } private IWorkingBeatmap getBeatmap(string name)