diff --git a/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs index 0ec3bfd911..3af581fcf3 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs @@ -22,8 +22,9 @@ namespace osu.Game.Rulesets.Catch.Tests { var ruleset = new CatchRuleset(); var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate }; + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, []); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate)); } @@ -33,8 +34,9 @@ namespace osu.Game.Rulesets.Catch.Tests { var ruleset = new CatchRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModHalfTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new CatchModHalfTime()]); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01)); } @@ -44,8 +46,9 @@ namespace osu.Game.Rulesets.Catch.Tests { var ruleset = new CatchRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModDoubleTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new CatchModDoubleTime()]); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01)); } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 571c115a85..b927f958c0 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Catch.Edit; @@ -267,9 +268,9 @@ namespace osu.Game.Rulesets.Catch } /// - public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); + BeatmapDifficulty adjustedDifficulty = base.GetAdjustedDisplayDifficulty(beatmapInfo, mods); double rate = ModUtils.CalculateRateWithMods(mods); double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN); @@ -279,6 +280,16 @@ namespace osu.Game.Rulesets.Catch return adjustedDifficulty; } + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) + { + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 10); + } + public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 96550618c0..c55465762b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } } - public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty, IReadOnlyList? mods = null) + public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty, IReadOnlyCollection? mods = null) { var converter = new ManiaBeatmapConverter(null, difficulty, new ManiaRuleset()); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 90d0080d6e..3ad77f4a84 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -415,9 +416,9 @@ namespace osu.Game.Rulesets.Mania }; /// - public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); + BeatmapDifficulty adjustedDifficulty = base.GetAdjustedDisplayDifficulty(beatmapInfo, mods); // notably, in mania, hit windows are designed to be independent of track playback rate (see `ManiaHitWindows.SpeedMultiplier`). // *however*, to not make matters *too* simple, mania Hard Rock and Easy differ from all other rulesets @@ -436,10 +437,25 @@ namespace osu.Game.Rulesets.Mania perfectHitWindow /= ManiaModEasy.HIT_WINDOW_DIFFICULTY_MULTIPLIER; adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(perfectHitWindow, ManiaHitWindows.PERFECT_WINDOW_RANGE); + adjustedDifficulty.CircleSize = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); return adjustedDifficulty; } + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) + { + // a special touch-up of key count is required to the original difficulty, since key conversion mods are not `IApplicableToDifficulty` + var originalDifficulty = new BeatmapDifficulty(beatmapInfo.Difficulty) + { + CircleSize = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), []) + }; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.KeyCount, @"KC", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 18); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 10); + } + public override IRulesetFilterCriteria CreateRulesetFilterCriteria() { return new ManiaFilterCriteria(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs index 4108e9388d..fd929dd8f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs @@ -22,8 +22,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var ruleset = new OsuRuleset(); var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate }; + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, []); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate)); } @@ -33,8 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var ruleset = new OsuRuleset(); var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty }; + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, []); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty)); } @@ -44,8 +46,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var ruleset = new OsuRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModHalfTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new OsuModHalfTime()]); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01)); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01)); @@ -56,8 +59,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var ruleset = new OsuRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModDoubleTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new OsuModDoubleTime()]); Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01)); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01)); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index be9f0e276b..8f0974067a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -366,9 +366,9 @@ namespace osu.Game.Rulesets.Osu /// /// - public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapInfo difficulty, IReadOnlyCollection mods) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); + BeatmapDifficulty adjustedDifficulty = base.GetAdjustedDisplayDifficulty(difficulty, mods); double rate = ModUtils.CalculateRateWithMods(mods); double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs index 2a5688ab11..0fb92e0d7d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs @@ -22,8 +22,9 @@ namespace osu.Game.Rulesets.Taiko.Tests { var ruleset = new TaikoRuleset(); var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty }; + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, []); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty)); } @@ -33,8 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Tests { var ruleset = new TaikoRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModHalfTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new TaikoModHalfTime()]); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01)); } @@ -44,8 +46,9 @@ namespace osu.Game.Rulesets.Taiko.Tests { var ruleset = new TaikoRuleset(); var difficulty = new BeatmapDifficulty(); + var beatmapInfo = new BeatmapInfo { Difficulty = difficulty }; - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModDoubleTime()]); + var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(beatmapInfo, [new TaikoModDoubleTime()]); Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01)); } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 76488fdd26..d4c180c95e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -33,6 +33,7 @@ using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Rulesets.Configuration; using osu.Game.Configuration; +using osu.Game.Localisation; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Taiko.Configuration; using osu.Game.Rulesets.Taiko.Edit.Setup; @@ -271,9 +272,9 @@ namespace osu.Game.Rulesets.Taiko } /// - public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); + BeatmapDifficulty adjustedDifficulty = base.GetAdjustedDisplayDifficulty(beatmapInfo, mods); double rate = ModUtils.CalculateRateWithMods(mods); double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, TaikoHitWindows.GREAT_WINDOW_RANGE); @@ -282,5 +283,15 @@ namespace osu.Game.Rulesets.Taiko return adjustedDifficulty; } + + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) + { + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ScrollSpeed, @"SS", 1f, (float)(adjustedDifficulty.SliderMultiplier / originalDifficulty.SliderMultiplier), 4); + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 5da05826cf..b164c530cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -15,11 +15,11 @@ using Newtonsoft.Json.Linq; using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select.Details; @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Visual.Online public void TestSelectedModsDontAffectStatistics() { AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet())); - AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0))); + AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == SongSelectStrings.ApproachRate).Value, () => Is.EqualTo((0, 0))); AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[] { new OsuModDifficultyAdjust @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Online ApproachRate = { Value = 10 } } }); - AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0))); + AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == SongSelectStrings.ApproachRate).Value, () => Is.EqualTo((0, 0))); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 5cf503f21e..3afc8cd1a4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -8,11 +8,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Testing; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; @@ -42,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo { - Ruleset = rulesets.AvailableRulesets.First(), + Ruleset = rulesets.GetRuleset(0)!, Difficulty = new BeatmapDifficulty { CircleSize = 7.2f, @@ -56,30 +55,39 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestNoMod() { - AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); + AddStep("set beatmap and ruleset", () => + { + advancedStats.BeatmapInfo = exampleBeatmapInfo; + advancedStats.Ruleset.Value = exampleBeatmapInfo.Ruleset; + }); AddStep("no mods selected", () => SelectedMods.Value = Array.Empty()); - AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCs); - AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); - AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain)); - AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); - AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate)); + AddAssert("first bar text is correct", () => advancedStats.GetStatistic(SongSelectStrings.CircleSize), () => Is.Not.Null); + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.CircleSize))); + AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.HPDrain))); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.Accuracy))); + AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.ApproachRate))); } [Test] public void TestFirstBarText() { + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("set ruleset to mania", () => advancedStats.Ruleset.Value = new ManiaRuleset().RulesetInfo); - AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCsMania); + AddAssert("first bar text is correct", () => advancedStats.GetStatistic(SongSelectStrings.KeyCount), () => Is.Not.Null); AddStep("set ruleset to osu", () => advancedStats.Ruleset.Value = new OsuRuleset().RulesetInfo); - AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCs); + AddAssert("first bar text is correct", () => advancedStats.GetStatistic(SongSelectStrings.CircleSize), () => Is.Not.Null); } [Test] public void TestEasyMod() { - AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); + AddStep("set beatmap and ruleset", () => + { + advancedStats.BeatmapInfo = exampleBeatmapInfo; + advancedStats.Ruleset.Value = exampleBeatmapInfo.Ruleset; + }); AddStep("select EZ mod", () => { @@ -87,16 +95,20 @@ namespace osu.Game.Tests.Visual.SongSelect advancedStats.Mods.Value = new[] { ruleset.CreateMod() }; }); - AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); - AddAssert("HP drain bar is blue", () => barIsBlue(advancedStats.HpDrain)); - AddAssert("accuracy bar is blue", () => barIsBlue(advancedStats.Accuracy)); - AddAssert("approach rate bar is blue", () => barIsBlue(advancedStats.ApproachRate)); + AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.GetStatistic(SongSelectStrings.CircleSize))); + AddAssert("HP drain bar is blue", () => barIsBlue(advancedStats.GetStatistic(SongSelectStrings.HPDrain))); + AddAssert("accuracy bar is blue", () => barIsBlue(advancedStats.GetStatistic(SongSelectStrings.Accuracy))); + AddAssert("approach rate bar is blue", () => barIsBlue(advancedStats.GetStatistic(SongSelectStrings.ApproachRate))); } [Test] public void TestHardRockMod() { - AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); + AddStep("set beatmap and ruleset", () => + { + advancedStats.BeatmapInfo = exampleBeatmapInfo; + advancedStats.Ruleset.Value = exampleBeatmapInfo.Ruleset; + }); AddStep("select HR mod", () => { @@ -104,16 +116,20 @@ namespace osu.Game.Tests.Visual.SongSelect advancedStats.Mods.Value = new[] { ruleset.CreateMod() }; }); - AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); - AddAssert("HP drain bar is red", () => barIsRed(advancedStats.HpDrain)); - AddAssert("accuracy bar is red", () => barIsRed(advancedStats.Accuracy)); - AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate)); + AddAssert("circle size bar is red", () => barIsRed(advancedStats.GetStatistic(SongSelectStrings.CircleSize))); + AddAssert("HP drain bar is red", () => barIsRed(advancedStats.GetStatistic(SongSelectStrings.HPDrain))); + AddAssert("accuracy bar is red", () => barIsRed(advancedStats.GetStatistic(SongSelectStrings.Accuracy))); + AddAssert("approach rate bar is red", () => barIsRed(advancedStats.GetStatistic(SongSelectStrings.ApproachRate))); } [Test] public void TestUnchangedDifficultyAdjustMod() { - AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); + AddStep("set beatmap and ruleset", () => + { + advancedStats.BeatmapInfo = exampleBeatmapInfo; + advancedStats.Ruleset.Value = exampleBeatmapInfo.Ruleset; + }); AddStep("select unchanged Difficulty Adjust mod", () => { @@ -122,16 +138,20 @@ namespace osu.Game.Tests.Visual.SongSelect advancedStats.Mods.Value = new[] { difficultyAdjustMod }; }); - AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); - AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain)); - AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); - AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate)); + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.CircleSize))); + AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.HPDrain))); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.Accuracy))); + AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.ApproachRate))); } [Test] public void TestChangedDifficultyAdjustMod() { - AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); + AddStep("set beatmap and ruleset", () => + { + advancedStats.BeatmapInfo = exampleBeatmapInfo; + advancedStats.Ruleset.Value = exampleBeatmapInfo.Ruleset; + }); AddStep("select changed Difficulty Adjust mod", () => { @@ -144,10 +164,10 @@ namespace osu.Game.Tests.Visual.SongSelect advancedStats.Mods.Value = new[] { difficultyAdjustMod }; }); - AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); - AddAssert("drain rate bar is blue", () => barIsBlue(advancedStats.HpDrain)); - AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); - AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate)); + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.CircleSize))); + AddAssert("drain rate bar is blue", () => barIsBlue(advancedStats.GetStatistic(SongSelectStrings.HPDrain))); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.GetStatistic(SongSelectStrings.Accuracy))); + AddAssert("approach rate bar is red", () => barIsRed(advancedStats.GetStatistic(SongSelectStrings.ApproachRate))); } private bool barIsWhite(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == Color4.White; @@ -156,10 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect private partial class TestAdvancedStats : AdvancedStats { - public new StatisticRow FirstValue => base.FirstValue; - public new StatisticRow HpDrain => base.HpDrain; - public new StatisticRow Accuracy => base.Accuracy; - public new StatisticRow ApproachRate => base.ApproachRate; + public StatisticRow GetStatistic(LocalisableString title) => Flow.OfType().SingleOrDefault(row => row.Title == title); } } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapTitleWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapTitleWedge.cs index efd9f6a5cd..f1c6d3965f 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapTitleWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapTitleWedge.cs @@ -97,7 +97,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 selectBeatmap(null); AddAssert("check default title", () => titleWedge.DisplayedTitle == Beatmap.Default.BeatmapInfo.Metadata.Title); AddAssert("check default artist", () => titleWedge.DisplayedArtist == Beatmap.Default.BeatmapInfo.Metadata.Artist); - AddAssert("check no statistics", () => difficultyDisplay.ChildrenOfType().All(d => !d.Statistics.Any())); + AddAssert("statistics not visible", + () => difficultyDisplay.ChildrenOfType() + .All(d => d.Alpha == 0 || d.ChildrenOfType().All(s => s.Alpha == 0))); } [Test] diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index cc76e28dfe..280185ba17 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -23,10 +23,6 @@ namespace osu.Game.Beatmaps.Drawables { private OsuSpriteText difficultyName = null!; private StarRatingDisplay starRating = null!; - private OsuSpriteText overallDifficulty = null!; - private OsuSpriteText drainRate = null!; - private OsuSpriteText circleSize = null!; - private OsuSpriteText approachRate = null!; private OsuSpriteText bpm = null!; private OsuSpriteText length = null!; @@ -76,13 +72,6 @@ namespace osu.Game.Beatmaps.Drawables AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), - Children = new Drawable[] - { - circleSize = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, - drainRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, - overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, - approachRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, - } }, miscFillFlowContainer = new FillFlowContainer { @@ -131,21 +120,16 @@ namespace osu.Game.Beatmaps.Drawables double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(displayedContent.BeatmapInfo.Difficulty); - - if (displayedContent.Mods != null) - { - foreach (var mod in displayedContent.Mods.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - } - Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); - BeatmapDifficulty adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(originalDifficulty, displayedContent.Mods ?? []); + var beatmapAttributes = ruleset.GetBeatmapAttributesForDisplay(displayedContent.BeatmapInfo, displayedContent.Mods ?? []) + .Select(attr => new OsuSpriteText + { + Font = OsuFont.Style.Caption1, + Text = $@"{attr.Acronym}: {attr.AdjustedValue:0.##}" + }); - circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##"); - drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##"); - approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##"); - overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); + difficultyFillFlowContainer.Clear(); + difficultyFillFlowContainer.AddRange(beatmapAttributes); TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); length.Text = "Length: " + lengthTimeSpan.ToFormattedDuration(); diff --git a/osu.Game/Localisation/SongSelectStrings.cs b/osu.Game/Localisation/SongSelectStrings.cs index bfc5f3990f..71bf15360e 100644 --- a/osu.Game/Localisation/SongSelectStrings.cs +++ b/osu.Game/Localisation/SongSelectStrings.cs @@ -79,6 +79,11 @@ namespace osu.Game.Localisation /// public static LocalisableString HPDrain => new TranslatableString(getKey(@"hp_drain"), @"HP Drain"); + /// + /// "Scroll Speed" + /// + public static LocalisableString ScrollSpeed => new TranslatableString(getKey(@"scroll_speed"), @"Scroll Speed"); + /// /// "Submitted" /// diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index dcb9ecdfc8..4df7e18997 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,9 +9,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Difficulty; using osuTK; namespace osu.Game.Overlays.Mods @@ -84,25 +84,17 @@ namespace osu.Game.Overlays.Mods if (data != null) { - attemptAdd("CS", bd => bd.CircleSize); - attemptAdd("AR", bd => bd.ApproachRate); - attemptAdd("OD", bd => bd.OverallDifficulty); - attemptAdd("HP", bd => bd.DrainRate); + foreach (var attribute in data.Attributes) + { + if (!Precision.AlmostEquals(attribute.OriginalValue, attribute.AdjustedValue)) + attributesFillFlow.Add(new AttributeDisplay(attribute.Acronym, attribute.OriginalValue, attribute.AdjustedValue)); + } } if (attributesFillFlow.Any()) content.Show(); else content.Hide(); - - void attemptAdd(string name, Func lookup) - { - double originalValue = lookup(data.OriginalDifficulty); - double adjustedValue = lookup(data.AdjustedDifficulty); - - if (!Precision.AlmostEquals(originalValue, adjustedValue)) - attributesFillFlow.Add(new AttributeDisplay(name, originalValue, adjustedValue)); - } } public void SetContent(Data? data) @@ -121,13 +113,11 @@ namespace osu.Game.Overlays.Mods public class Data { - public BeatmapDifficulty OriginalDifficulty { get; } - public BeatmapDifficulty AdjustedDifficulty { get; } + public IReadOnlyCollection Attributes { get; } - public Data(BeatmapDifficulty originalDifficulty, BeatmapDifficulty adjustedDifficulty) + public Data(IReadOnlyCollection attributes) { - OriginalDifficulty = originalDifficulty; - AdjustedDifficulty = adjustedDifficulty; + Attributes = attributes; } } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 14c02f5da7..f714cb3798 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -32,11 +32,6 @@ namespace osu.Game.Overlays.Mods private StarRatingDisplay starRatingDisplay = null!; private BPMDisplay bpmDisplay = null!; - private VerticalAttributeDisplay circleSizeDisplay = null!; - private VerticalAttributeDisplay drainRateDisplay = null!; - private VerticalAttributeDisplay approachRateDisplay = null!; - private VerticalAttributeDisplay overallDifficultyDisplay = null!; - public Bindable BeatmapInfo { get; } = new Bindable(); public Bindable> Mods { get; } = new Bindable>(); @@ -84,13 +79,6 @@ namespace osu.Game.Overlays.Mods }); RightContent.Alpha = 0; - RightContent.AddRange(new Drawable[] - { - circleSizeDisplay = new VerticalAttributeDisplay("CS") { Shear = -OsuGame.SHEAR, }, - approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = -OsuGame.SHEAR, }, - overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { Shear = -OsuGame.SHEAR, }, - drainRateDisplay = new VerticalAttributeDisplay("HP") { Shear = -OsuGame.SHEAR, }, - }); } protected override void LoadComplete() @@ -173,26 +161,30 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(originalDifficulty); - - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - Ruleset ruleset = GameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(adjustedDifficulty, Mods.Value); + var displayAttributes = ruleset.GetBeatmapAttributesForDisplay(BeatmapInfo.Value, Mods.Value).ToList(); - TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); + TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes); - circleSizeDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.CircleSize, adjustedDifficulty.CircleSize); - drainRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.DrainRate, adjustedDifficulty.DrainRate); - approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); - overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); + // if there are not enough attribute displays, make more + for (int i = RightContent.Count; i < displayAttributes.Count; i++) + RightContent.Add(new VerticalAttributeDisplay { Shear = -OsuGame.SHEAR }); - circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize; - drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate; - approachRateDisplay.Current.Value = adjustedDifficulty.ApproachRate; - overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty; + // populate all attribute displays that need to be visible... + for (int i = 0; i < displayAttributes.Count; i++) + { + var attribute = displayAttributes[i]; + var display = (VerticalAttributeDisplay)RightContent[i]; + + display.Label = attribute.Acronym; + display.Current.Value = attribute.AdjustedValue; + display.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(attribute.OriginalValue, attribute.AdjustedValue); + display.Alpha = 1; + } + + // and hide any extra ones + for (int i = displayAttributes.Count; i < RightContent.Count; i++) + RightContent[i].Alpha = 0; }); private void updateCollapsedState() diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index a3e24b486f..572d5f89e5 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -33,7 +33,11 @@ namespace osu.Game.Overlays.Mods /// /// Text to display in the top area of the display. /// - public LocalisableString Label { get; protected set; } + public LocalisableString Label + { + get => text.Text; + set => text.Text = value; + } private readonly EffectCounter counter; private readonly OsuSpriteText text; @@ -67,10 +71,8 @@ namespace osu.Game.Overlays.Mods counter.Colour = newColor; } - public VerticalAttributeDisplay(LocalisableString label) + public VerticalAttributeDisplay() { - Label = label; - AutoSizeAxes = Axes.X; Origin = Anchor.CentreLeft; @@ -91,7 +93,6 @@ namespace osu.Game.Overlays.Mods { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Text = Label, Margin = new MarginPadding { Horizontal = 15 }, // to reserve space for 0.XX value Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold) }, diff --git a/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs new file mode 100644 index 0000000000..fc638cd417 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// A is like a single property from , + /// but adjusted for display in the context of a specific ruleset. + /// The reason why this record exists is that rulesets use in different ways. + /// Some rulesets completely ignore some fields from , + /// some reuse fields in weird ways (like mania reusing to mean key count), + /// some want to provide specific extended information for a field + /// or adjust the "effective display" in different ways. + /// + /// The long label for this beatmap attribute. + /// A two-letter acronym for this beatmap attribute. + /// The value of this attribute before application of mods. + /// The "effective" value of this attribute after application of mods. + /// The highest allowable value of this attribute. + public record RulesetBeatmapAttribute( + LocalisableString Label, + string Acronym, + float OriginalValue, + float AdjustedValue, + float MaxValue); +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index da3f628137..0dbe6e8845 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Extensions; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -385,10 +386,34 @@ namespace osu.Game.Rulesets /// /// It is also not always correct, and arguably is never correct depending on your frame of mind. /// - /// >The that will be adjusted. + /// The for which to display the adjusted difficulty. /// The active mods. /// The adjusted difficulty attributes. - public virtual BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) => new BeatmapDifficulty(difficulty); + public virtual BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) + { + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(beatmapInfo.Difficulty); + + foreach (var mod in mods.OfType()) + mod.ApplyToDifficulty(adjustedDifficulty); + + return adjustedDifficulty; + } + + /// + /// Returns a list of s to be displayed wherever it is wanted to display a given beatmap's difficulty information. + /// The returned data includes both material changes to difficulty from mods, + /// as well as "effective" adjustments coming from . + /// + public virtual IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) + { + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 10); + } /// /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 90a4af48f0..6403eb01b0 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,8 +16,9 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Framework.Bindables; using System.Collections.Generic; -using osu.Game.Rulesets.Mods; +using System.Diagnostics; using System.Linq; +using osu.Game.Rulesets.Mods; using System.Threading; using System.Threading.Tasks; using osu.Framework.Extensions; @@ -34,10 +35,12 @@ namespace osu.Game.Screens.Select.Details { public partial class AdvancedStats : Container, IHasCustomTooltip { + private readonly int columns; + [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; + protected FillFlowContainer Flow { get; private set; } private readonly StatisticRow starDifficulty; public ITooltip GetCustomTooltip() => new AdjustedAttributesTooltip(); @@ -77,65 +80,48 @@ namespace osu.Game.Screens.Select.Details public AdvancedStats(int columns = 1) { + this.columns = columns; + switch (columns) { case 1: - Child = new FillFlowContainer + Child = Flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(), // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + starDifficulty = new StatisticRow(forceDecimalPlaces: true) + { + Title = BeatmapsetsStrings.ShowStatsStars, + MaxValue = 10, + }, }, }; break; case 2: - Child = new FillFlowContainer + Child = Flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, Children = new[] { - FirstValue = new StatisticRow - { - Width = 0.5f, - Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, - }, // circle size/key amount - HpDrain = new StatisticRow - { - Title = BeatmapsetsStrings.ShowStatsDrain, - Width = 0.5f, - Padding = new MarginPadding { Left = 5, Vertical = 2.5f }, - }, - Accuracy = new StatisticRow - { - Title = BeatmapsetsStrings.ShowStatsAccuracy, - Width = 0.5f, - Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, - }, - ApproachRate = new StatisticRow - { - Title = BeatmapsetsStrings.ShowStatsAr, - Width = 0.5f, - Padding = new MarginPadding { Left = 5, Vertical = 2.5f }, - }, - starDifficulty = new StatisticRow(10, true) + starDifficulty = new StatisticRow(forceDecimalPlaces: true) { + MaxValue = 10, Title = BeatmapsetsStrings.ShowStatsStars, Width = 0.5f, - Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, + Padding = new MarginPadding { Horizontal = 5, Vertical = 2.5f }, }, }, }; break; } + + Debug.Assert(Flow != null); + Flow.SetLayoutPosition(starDifficulty, float.MaxValue); } [BackgroundDependencyLoader] @@ -171,52 +157,39 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; - BeatmapDifficulty adjustedDifficulty = null; - - if (baseDifficulty != null) + if (BeatmapInfo != null && Ruleset.Value != null) { - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); + var displayAttributes = Ruleset.Value.CreateInstance().GetBeatmapAttributesForDisplay(BeatmapInfo, Mods.Value).ToList(); + TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - - adjustedDifficulty = originalDifficulty; - - if (Ruleset.Value != null) + // if there are not enough attribute displays, make more + // the subtraction of 1 is to exclude the star rating row which is always present (and always last) + for (int i = Flow.Count - 1; i < displayAttributes.Count; i++) { - adjustedDifficulty = Ruleset.Value.CreateInstance().GetAdjustedDisplayDifficulty(originalDifficulty, Mods.Value); - - TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); + Flow.Add(new StatisticRow + { + Width = columns == 1 ? 1 : 0.5f, + Padding = columns == 1 ? new MarginPadding() : new MarginPadding { Horizontal = 5, Vertical = 2.5f }, + }); } + + // populate all attribute displays that need to be visible... + for (int i = 0; i < displayAttributes.Count; i++) + { + var attribute = displayAttributes[i]; + var display = (StatisticRow)Flow.Where(r => r != starDifficulty).ElementAt(i); + + display.Title = attribute.Label; + display.MaxValue = attribute.MaxValue; + display.Value = (attribute.OriginalValue, attribute.AdjustedValue); + display.Alpha = 1; + } + + // and hide any extra ones + foreach (var row in Flow.Where(r => r != starDifficulty).Skip(displayAttributes.Count)) + row.Alpha = 0; } - switch (Ruleset.Value?.OnlineID) - { - case 3: - // Account for mania differences locally for now. - // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes. - ILegacyRuleset legacyRuleset = (ILegacyRuleset)Ruleset.Value.CreateInstance(); - - // For the time being, the key count is static no matter what, because: - // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. - // b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. - int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, Mods.Value); - - FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; - FirstValue.Value = (keyCount, keyCount); - break; - - default: - FirstValue.Title = BeatmapsetsStrings.ShowStatsCs; - FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize); - break; - } - - HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); - Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); - ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); - updateStarDifficulty(); } @@ -265,7 +238,6 @@ namespace osu.Game.Screens.Select.Details private const float value_width = 25; private const float name_width = 70; - private readonly float maxValue; private readonly bool forceDecimalPlaces; private readonly OsuSpriteText name, valueText; private readonly Bar bar; @@ -280,6 +252,8 @@ namespace osu.Game.Screens.Select.Details set => name.Text = value; } + public float MaxValue { get; set; } + private (float baseValue, float? adjustedValue)? value; public (float baseValue, float? adjustedValue) Value @@ -292,10 +266,10 @@ namespace osu.Game.Screens.Select.Details this.value = value; - bar.Length = value.baseValue / maxValue; + bar.Length = value.baseValue / MaxValue; valueText.Text = (value.adjustedValue ?? value.baseValue).ToString(forceDecimalPlaces ? "0.00" : "0.##"); - ModBar.Length = (value.adjustedValue ?? 0) / maxValue; + ModBar.Length = (value.adjustedValue ?? 0) / MaxValue; if (Precision.AlmostEquals(value.baseValue, value.adjustedValue ?? value.baseValue, 0.05f)) ModBar.AccentColour = valueText.Colour = Color4.White; @@ -312,9 +286,8 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(bool forceDecimalPlaces = false) { - this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index 2b1469d6e2..061eee1cc8 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.SelectV2 if (beatmap.IsDefault) { ratingAndNameContainer.FadeOut(300, Easing.OutQuint); - countStatisticsDisplay.Statistics = Array.Empty(); + countStatisticsDisplay.FadeOut(300, Easing.OutQuint); } else { @@ -261,7 +261,7 @@ namespace osu.Game.Screens.SelectV2 { if (beatmap.IsDefault) { - countStatisticsDisplay.Statistics = Array.Empty(); + countStatisticsDisplay.FadeOut(300, Easing.OutQuint); return; } @@ -279,6 +279,7 @@ namespace osu.Game.Screens.SelectV2 if (cancellationToken.IsCancellationRequested) return; + countStatisticsDisplay.FadeIn(200, Easing.OutQuint); countStatisticsDisplay.Statistics = statistics; }); }, cancellationToken); @@ -293,46 +294,11 @@ namespace osu.Game.Screens.SelectV2 return; } - BeatmapDifficulty originalDifficulty = beatmap.Value.BeatmapInfo.Difficulty; - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(originalDifficulty); - - foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - Ruleset rulesetInstance = ruleset.Value.CreateInstance(); - adjustedDifficulty = rulesetInstance.GetAdjustedDisplayDifficulty(adjustedDifficulty, mods.Value); - difficultyStatisticsDisplay.TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); - - StatisticDifficulty.Data firstStatistic; - - switch (ruleset.Value.OnlineID) - { - case 3: - // Account for mania differences locally for now. - // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes. - ILegacyRuleset legacyRuleset = (ILegacyRuleset)rulesetInstance; - - // For the time being, the key count is static no matter what, because: - // - The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. - // - Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. - int keyCount = legacyRuleset.GetKeyCount(beatmap.Value.BeatmapInfo, mods.Value); - - firstStatistic = new StatisticDifficulty.Data(SongSelectStrings.KeyCount, keyCount, keyCount, 10); - break; - - default: - firstStatistic = new StatisticDifficulty.Data(SongSelectStrings.CircleSize, originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 10); - break; - } - - difficultyStatisticsDisplay.Statistics = new[] - { - firstStatistic, - new StatisticDifficulty.Data(SongSelectStrings.ApproachRate, originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 10), - new StatisticDifficulty.Data(SongSelectStrings.Accuracy, originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 10), - new StatisticDifficulty.Data(SongSelectStrings.HPDrain, originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 10), - }; + var displayAttributes = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value).ToList(); + difficultyStatisticsDisplay.TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes); + difficultyStatisticsDisplay.Statistics = displayAttributes.Select(a => new StatisticDifficulty.Data(a)).ToList(); }); protected override void Update() diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyStatisticsDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyStatisticsDisplay.cs index 365ed9977b..595959cfce 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyStatisticsDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyStatisticsDisplay.cs @@ -144,11 +144,13 @@ namespace osu.Game.Screens.SelectV2 if (tiny) { statisticsFlow.Hide(); + // Slow fade hides fill flow layout weirdness. tinyStatisticsGrid.FadeIn(200, Easing.InQuint); } else { tinyStatisticsGrid.Hide(); + // Slow fade hides fill flow layout weirdness. statisticsFlow.FadeIn(200, Easing.InQuint); } @@ -164,12 +166,16 @@ namespace osu.Game.Screens.SelectV2 float statisticWidth = Math.Max(65, statisticsFlow.Max(s => s.LabelWidth)); foreach (var statistic in statisticsFlow) + { statistic.Width = statisticWidth; + // Slow fade hides fill flow layout weirdness. + statistic.FadeIn(200, Easing.InQuint); + } drawSizeLayout.Invalidate(); }); - private void updateStatistics() + private void updateStatistics() => Scheduler.AddOnce(() => { if (statisticsFlow.Select(s => s.Value.Label) .SequenceEqual(statistics.Select(s => s.Label))) @@ -181,12 +187,13 @@ namespace osu.Game.Screens.SelectV2 { statisticsFlow.ChildrenEnumerable = statistics.Select(d => new StatisticDifficulty { + Alpha = 0, AccentColour = accentColour, Value = d }); updateStatisticsSizing(); } - } + }); private void updateTinyStatistics() { diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs index d0b6acca88..65d8ba3951 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs @@ -13,6 +13,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Rulesets.Difficulty; using osuTK; using osuTK.Graphics; @@ -191,7 +192,13 @@ namespace osu.Game.Screens.SelectV2 } } - public record Data(LocalisableString Label, float Value, float AdjustedValue, float Maximum, string? Content = null); + public record Data(LocalisableString Label, float Value, float AdjustedValue, float Maximum, string? Content = null) + { + public Data(RulesetBeatmapAttribute attribute) + : this(attribute.Label, attribute.OriginalValue, attribute.AdjustedValue, attribute.MaxValue) + { + } + } } } } diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 60a03f4351..3935277dfb 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -237,15 +236,9 @@ namespace osu.Game.Skinning.Components BeatmapDifficulty computeDifficulty() { - BeatmapDifficulty difficulty = new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty); - - foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(difficulty); - - if (ruleset.Value is RulesetInfo rulesetInfo) - difficulty = rulesetInfo.CreateInstance().GetAdjustedDisplayDifficulty(difficulty, mods.Value); - - return difficulty; + return ruleset.Value is RulesetInfo rulesetInfo + ? rulesetInfo.CreateInstance().GetAdjustedDisplayDifficulty(beatmap.Value.BeatmapInfo, mods.Value) + : new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty); } }