From e2a454ae0074befdd88ec06c7a9547fe525dc813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 10:25:59 +0200 Subject: [PATCH 01/11] Add new ruleset method responsible for displaying beatmap attributes consistently everywhere The intention is to use this in every place that wishes to display beatmap attributes (and also remove a bunch of local hack-arounds for mania, etc.) --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 10 ++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 10 ++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 10 ++++++ osu.Game/Localisation/SongSelectStrings.cs | 5 +++ .../Difficulty/RulesetBeatmapDifficulty.cs | 31 +++++++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 16 ++++++++++ 6 files changed, 82 insertions(+) create mode 100644 osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 571c115a85..c1fa9f574b 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; @@ -279,6 +280,15 @@ namespace osu.Game.Rulesets.Catch return adjustedDifficulty; } + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + { + var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", difficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", difficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + } + public override bool EditorShowScrollSpeed => false; } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 90d0080d6e..da9fba3d7e 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; @@ -440,6 +441,15 @@ namespace osu.Game.Rulesets.Mania return adjustedDifficulty; } + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + { + var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.KeyCount, @"KC", difficulty.CircleSize, adjustedDifficulty.CircleSize, 1, 18); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + } + public override IRulesetFilterCriteria CreateRulesetFilterCriteria() { return new ManiaFilterCriteria(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 76488fdd26..792670c904 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; @@ -282,5 +283,14 @@ namespace osu.Game.Rulesets.Taiko return adjustedDifficulty; } + + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + { + var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ScrollSpeed, @"SS", 1f, (float)(adjustedDifficulty.SliderMultiplier / difficulty.SliderMultiplier), 0.25f, 4); + } } } 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/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs new file mode 100644 index 0000000000..fb105d5050 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs @@ -0,0 +1,31 @@ +// 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 lowest allowable value of this attribute. + /// The highest allowable value of this attribute. + public record RulesetBeatmapAttribute( + LocalisableString Label, + string Acronym, + float Value, + float AdjustedValue, + float MinValue, + float MaxValue); +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index da3f628137..f10287f0a6 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; @@ -390,6 +391,21 @@ namespace osu.Game.Rulesets /// The adjusted difficulty attributes. public virtual BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) => new BeatmapDifficulty(difficulty); + /// + /// 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(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + { + var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", difficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", difficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + } + /// /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. /// From 0c7dcfdba3d105a9a7763ad88f05b60a9a8f9a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 10:36:40 +0200 Subject: [PATCH 02/11] Use new ruleset method in song select v2 --- .../BeatmapTitleWedge_DifficultyDisplay.cs | 31 ++----------------- .../BeatmapTitleWedge_StatisticDifficulty.cs | 9 +++++- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index 2b1469d6e2..c3ff8899ad 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -304,35 +304,8 @@ namespace osu.Game.Screens.SelectV2 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), - }; + difficultyStatisticsDisplay.Statistics = rulesetInstance.GetBeatmapAttributesForDisplay(originalDifficulty, mods.Value) + .Select(a => new StatisticDifficulty.Data(a)).ToList(); }); protected override void Update() diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs index d0b6acca88..55bfa3e360 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.Value, attribute.AdjustedValue, attribute.MaxValue) + { + } + } } } } From 7c4dd812b67892e1c30262ca792051fa475fd609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 11:00:15 +0200 Subject: [PATCH 03/11] Refactor beatmap attribute methods This change refactors `GetAdjustedDisplayDifficulty()` and `GetBeatmapAttributesToDisplay()` in two ways: - Both methods now accept `IBeatmapInfo` instead of `IBeatmapDifficultyInfo`. This is done in order to make mania key count display to work, wherein `IBeatmapDifficultyInfo` is not enough to calculate the final key count. - `GetAdjustedDisplayDifficulty()` now applies all `IApplicableToDifficulty` mods itself. I did this after noticing that every real consumer of this method had to do that themselves for very little reason. --- .../CatchRateAdjustedDisplayDifficultyTest.cs | 9 ++++--- osu.Game.Rulesets.Catch/CatchRuleset.cs | 15 +++++------ .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 20 +++++++++------ .../OsuRateAdjustedDisplayDifficultyTest.cs | 12 ++++++--- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +-- .../TaikoRateAdjustedDisplayDifficultyTest.cs | 9 ++++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 15 +++++------ .../Drawables/DifficultyIconTooltip.cs | 10 +------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 7 +----- osu.Game/Rulesets/Ruleset.cs | 25 +++++++++++++------ .../Screens/Select/Details/AdvancedStats.cs | 20 +++------------ .../BeatmapTitleWedge_DifficultyDisplay.cs | 9 ++----- .../Components/BeatmapAttributeText.cs | 13 +++------- 14 files changed, 80 insertions(+), 90 deletions(-) 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 c1fa9f574b..bdfa4e7db4 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -268,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); @@ -280,13 +280,14 @@ namespace osu.Game.Rulesets.Catch return adjustedDifficulty; } - public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", difficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", difficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 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 da9fba3d7e..8bdfad1800 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -416,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 @@ -437,17 +437,23 @@ 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(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, 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", difficulty.CircleSize, adjustedDifficulty.CircleSize, 1, 18); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.KeyCount, @"KC", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 1, 18); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); } public override IRulesetFilterCriteria CreateRulesetFilterCriteria() 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 792670c904..c6c61a26dc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -272,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); @@ -284,13 +284,14 @@ namespace osu.Game.Rulesets.Taiko return adjustedDifficulty; } - public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ScrollSpeed, @"SS", 1f, (float)(adjustedDifficulty.SliderMultiplier / difficulty.SliderMultiplier), 0.25f, 4); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ScrollSpeed, @"SS", 1f, (float)(adjustedDifficulty.SliderMultiplier / originalDifficulty.SliderMultiplier), 0.25f, 4); } } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index cc76e28dfe..f4056607a9 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -131,16 +131,8 @@ 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 ?? []); + BeatmapDifficulty adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(displayedContent.BeatmapInfo, displayedContent.Mods ?? []); circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##"); drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##"); diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 14c02f5da7..37a7844b6d 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -174,13 +173,9 @@ 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 adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(BeatmapInfo.Value, Mods.Value); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f10287f0a6..968f2bf4c7 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -386,24 +386,33 @@ 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(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection mods) + public virtual IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) { - var adjustedDifficulty = GetAdjustedDisplayDifficulty(difficulty, mods); + var originalDifficulty = beatmapInfo.Difficulty; + var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", difficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", difficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", difficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", difficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); + yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); } /// diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 90a4af48f0..5a86cde090 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -17,7 +17,6 @@ using osu.Game.Beatmaps; using osu.Framework.Bindables; using System.Collections.Generic; using osu.Game.Rulesets.Mods; -using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Extensions; @@ -171,24 +170,13 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; + var baseDifficulty = BeatmapInfo?.Difficulty != null ? new BeatmapDifficulty(BeatmapInfo.Difficulty) : null; BeatmapDifficulty adjustedDifficulty = null; - if (baseDifficulty != null) + if (baseDifficulty != null && Ruleset.Value != null) { - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - - adjustedDifficulty = originalDifficulty; - - if (Ruleset.Value != null) - { - adjustedDifficulty = Ruleset.Value.CreateInstance().GetAdjustedDisplayDifficulty(originalDifficulty, Mods.Value); - - TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); - } + adjustedDifficulty = Ruleset.Value.CreateInstance().GetAdjustedDisplayDifficulty(BeatmapInfo, Mods.Value); + TooltipContent = new AdjustedAttributesTooltip.Data(baseDifficulty, adjustedDifficulty); } switch (Ruleset.Value?.OnlineID) diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index c3ff8899ad..f8783c6004 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -294,17 +294,12 @@ namespace osu.Game.Screens.SelectV2 } 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); + var adjustedDifficulty = rulesetInstance.GetAdjustedDisplayDifficulty(beatmap.Value.BeatmapInfo, mods.Value); difficultyStatisticsDisplay.TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); - difficultyStatisticsDisplay.Statistics = rulesetInstance.GetBeatmapAttributesForDisplay(originalDifficulty, mods.Value) + difficultyStatisticsDisplay.Statistics = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value) .Select(a => new StatisticDifficulty.Data(a)).ToList(); }); 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); } } From a952e3704e4def4359828e130a692f88f1c9a1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 12:40:34 +0200 Subject: [PATCH 04/11] Migrate other usages of `GetAdjustedDisplayDifficulty()` to use `GetBeatmapAttributesForDisplay()` instead This ensures parity between all beatmap attribute displays that used the former method. --- .../Drawables/DifficultyIconTooltip.cs | 24 ++-- .../Mods/AdjustedAttributesTooltip.cs | 30 ++--- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 45 +++---- .../Overlays/Mods/VerticalAttributeDisplay.cs | 11 +- .../Screens/Select/Details/AdvancedStats.cs | 125 ++++++++---------- .../BeatmapTitleWedge_DifficultyDisplay.cs | 8 +- 6 files changed, 103 insertions(+), 140 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index f4056607a9..bbf5897282 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 { @@ -132,12 +121,15 @@ namespace osu.Game.Beatmaps.Drawables double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); - BeatmapDifficulty adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(displayedContent.BeatmapInfo, displayedContent.Mods ?? []); + var beatmapAttributes = ruleset.GetBeatmapAttributesForDisplay(displayedContent.BeatmapInfo, displayedContent.Mods ?? []) + .Select(attr => new OsuSpriteText + { + Font = OsuFont.Style.Caption1, + Text = $@"{attr.Acronym}: {attr.Value: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/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index dcb9ecdfc8..8ec7f37658 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.Value, attribute.AdjustedValue)) + attributesFillFlow.Add(new AttributeDisplay(attribute.Acronym, attribute.Value, 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 37a7844b6d..b74e9b1273 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -31,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>(); @@ -83,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() @@ -172,22 +161,30 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); - Ruleset ruleset = GameRuleset.Value.CreateInstance(); - var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(BeatmapInfo.Value, 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.Value, 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/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 5a86cde090..39d392aadf 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,6 +16,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Framework.Bindables; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using osu.Game.Rulesets.Mods; using System.Threading; using System.Threading.Tasks; @@ -33,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; + private readonly FillFlowContainer flow; private readonly StatisticRow starDifficulty; public ITooltip GetCustomTooltip() => new AdjustedAttributesTooltip(); @@ -76,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] @@ -170,41 +157,39 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - var baseDifficulty = BeatmapInfo?.Difficulty != null ? new BeatmapDifficulty(BeatmapInfo.Difficulty) : null; - BeatmapDifficulty adjustedDifficulty = null; - - if (baseDifficulty != null && Ruleset.Value != null) + if (BeatmapInfo != null && Ruleset.Value != null) { - adjustedDifficulty = Ruleset.Value.CreateInstance().GetAdjustedDisplayDifficulty(BeatmapInfo, Mods.Value); - TooltipContent = new AdjustedAttributesTooltip.Data(baseDifficulty, adjustedDifficulty); + var displayAttributes = Ruleset.Value.CreateInstance().GetBeatmapAttributesForDisplay(BeatmapInfo, Mods.Value).ToList(); + TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes); + + // 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++) + { + 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.Value, 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(); } @@ -253,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; @@ -268,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 @@ -280,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; @@ -300,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 f8783c6004..0d280a1b27 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -296,11 +296,9 @@ namespace osu.Game.Screens.SelectV2 BeatmapDifficulty originalDifficulty = beatmap.Value.BeatmapInfo.Difficulty; Ruleset rulesetInstance = ruleset.Value.CreateInstance(); - var adjustedDifficulty = rulesetInstance.GetAdjustedDisplayDifficulty(beatmap.Value.BeatmapInfo, mods.Value); - difficultyStatisticsDisplay.TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); - - difficultyStatisticsDisplay.Statistics = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value) - .Select(a => new StatisticDifficulty.Data(a)).ToList(); + 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() From 1319a5499babe6632687015bb86005f5d3ab0383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 13:59:11 +0200 Subject: [PATCH 05/11] Fix test --- .../SongSelect/TestSceneAdvancedStats.cs | 89 +++++++++++-------- .../Screens/Select/Details/AdvancedStats.cs | 18 ++-- 2 files changed, 62 insertions(+), 45 deletions(-) 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/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 39d392aadf..a4e113ce6f 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Select.Details [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - private readonly FillFlowContainer flow; + protected FillFlowContainer Flow { get; private set; } private readonly StatisticRow starDifficulty; public ITooltip GetCustomTooltip() => new AdjustedAttributesTooltip(); @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select.Details switch (columns) { case 1: - Child = flow = new FillFlowContainer + Child = Flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select.Details break; case 2: - Child = flow = new FillFlowContainer + Child = Flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -120,8 +120,8 @@ namespace osu.Game.Screens.Select.Details break; } - Debug.Assert(flow != null); - flow.SetLayoutPosition(starDifficulty, float.MaxValue); + Debug.Assert(Flow != null); + Flow.SetLayoutPosition(starDifficulty, float.MaxValue); } [BackgroundDependencyLoader] @@ -164,9 +164,9 @@ namespace osu.Game.Screens.Select.Details // 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++) + for (int i = Flow.Count - 1; i < displayAttributes.Count; i++) { - flow.Add(new StatisticRow() + Flow.Add(new StatisticRow() { Width = columns == 1 ? 1 : 0.5f, Padding = columns == 1 ? new MarginPadding() : new MarginPadding { Horizontal = 5, Vertical = 2.5f }, @@ -177,7 +177,7 @@ namespace osu.Game.Screens.Select.Details for (int i = 0; i < displayAttributes.Count; i++) { var attribute = displayAttributes[i]; - var display = (StatisticRow)flow.Where(r => r != starDifficulty).ElementAt(i); + var display = (StatisticRow)Flow.Where(r => r != starDifficulty).ElementAt(i); display.Title = attribute.Label; display.MaxValue = attribute.MaxValue; @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Select.Details } // and hide any extra ones - foreach (var row in flow.Where(r => r != starDifficulty).Skip(displayAttributes.Count)) + foreach (var row in Flow.Where(r => r != starDifficulty).Skip(displayAttributes.Count)) row.Alpha = 0; } From cd2c5075df894d164491adcbadfcad40138cd5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 13:33:48 +0200 Subject: [PATCH 06/11] Rename property to reduce confusion Caused an actual bug in one of the replaced usages. --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 2 +- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 4 ++-- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs | 4 ++-- osu.Game/Screens/Select/Details/AdvancedStats.cs | 2 +- .../Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index bbf5897282..280185ba17 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -125,7 +125,7 @@ namespace osu.Game.Beatmaps.Drawables .Select(attr => new OsuSpriteText { Font = OsuFont.Style.Caption1, - Text = $@"{attr.Acronym}: {attr.Value:0.##}" + Text = $@"{attr.Acronym}: {attr.AdjustedValue:0.##}" }); difficultyFillFlowContainer.Clear(); diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index 8ec7f37658..4df7e18997 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -86,8 +86,8 @@ namespace osu.Game.Overlays.Mods { foreach (var attribute in data.Attributes) { - if (!Precision.AlmostEquals(attribute.Value, attribute.AdjustedValue)) - attributesFillFlow.Add(new AttributeDisplay(attribute.Acronym, attribute.Value, attribute.AdjustedValue)); + if (!Precision.AlmostEquals(attribute.OriginalValue, attribute.AdjustedValue)) + attributesFillFlow.Add(new AttributeDisplay(attribute.Acronym, attribute.OriginalValue, attribute.AdjustedValue)); } } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index b74e9b1273..f714cb3798 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -178,7 +178,7 @@ namespace osu.Game.Overlays.Mods display.Label = attribute.Acronym; display.Current.Value = attribute.AdjustedValue; - display.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(attribute.Value, attribute.AdjustedValue); + display.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(attribute.OriginalValue, attribute.AdjustedValue); display.Alpha = 1; } diff --git a/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs index fb105d5050..6bad79ccb1 100644 --- a/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs +++ b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs @@ -17,14 +17,14 @@ namespace osu.Game.Rulesets.Difficulty /// /// 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 value of this attribute before application of mods. /// The "effective" value of this attribute after application of mods. /// The lowest allowable value of this attribute. /// The highest allowable value of this attribute. public record RulesetBeatmapAttribute( LocalisableString Label, string Acronym, - float Value, + float OriginalValue, float AdjustedValue, float MinValue, float MaxValue); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index a4e113ce6f..95c4d94abc 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Select.Details display.Title = attribute.Label; display.MaxValue = attribute.MaxValue; - display.Value = (attribute.Value, attribute.AdjustedValue); + display.Value = (attribute.OriginalValue, attribute.AdjustedValue); display.Alpha = 1; } diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs index 55bfa3e360..65d8ba3951 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_StatisticDifficulty.cs @@ -195,7 +195,7 @@ namespace osu.Game.Screens.SelectV2 public record Data(LocalisableString Label, float Value, float AdjustedValue, float Maximum, string? Content = null) { public Data(RulesetBeatmapAttribute attribute) - : this(attribute.Label, attribute.Value, attribute.AdjustedValue, attribute.MaxValue) + : this(attribute.Label, attribute.OriginalValue, attribute.AdjustedValue, attribute.MaxValue) { } } From 640fe5752dad120967154f3badc0876aa88dc35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 15:00:55 +0200 Subject: [PATCH 07/11] Fix code quality --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 6 +++--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 +++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 6 +++--- osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs | 2 -- osu.Game/Rulesets/Ruleset.cs | 8 ++++---- osu.Game/Screens/Select/Details/AdvancedStats.cs | 2 +- .../SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs | 1 - 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index bdfa4e7db4..b927f958c0 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -285,9 +285,9 @@ namespace osu.Game.Rulesets.Catch var originalDifficulty = beatmapInfo.Difficulty; var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + 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/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8bdfad1800..3ad77f4a84 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -451,9 +451,9 @@ namespace osu.Game.Rulesets.Mania }; var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.KeyCount, @"KC", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 1, 18); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + 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() diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index c6c61a26dc..d4c180c95e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -289,9 +289,9 @@ namespace osu.Game.Rulesets.Taiko var originalDifficulty = beatmapInfo.Difficulty; var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ScrollSpeed, @"SS", 1f, (float)(adjustedDifficulty.SliderMultiplier / originalDifficulty.SliderMultiplier), 0.25f, 4); + 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/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs index 6bad79ccb1..fc638cd417 100644 --- a/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs +++ b/osu.Game/Rulesets/Difficulty/RulesetBeatmapDifficulty.cs @@ -19,13 +19,11 @@ namespace osu.Game.Rulesets.Difficulty /// 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 lowest allowable value of this attribute. /// The highest allowable value of this attribute. public record RulesetBeatmapAttribute( LocalisableString Label, string Acronym, float OriginalValue, float AdjustedValue, - float MinValue, float MaxValue); } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 968f2bf4c7..0dbe6e8845 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -409,10 +409,10 @@ namespace osu.Game.Rulesets var originalDifficulty = beatmapInfo.Difficulty; var adjustedDifficulty = GetAdjustedDisplayDifficulty(beatmapInfo, mods); - yield return new RulesetBeatmapAttribute(SongSelectStrings.CircleSize, @"CS", originalDifficulty.CircleSize, adjustedDifficulty.CircleSize, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.ApproachRate, @"AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.Accuracy, @"OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty, 0, 10); - yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, adjustedDifficulty.DrainRate, 0, 10); + 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); } /// diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 95c4d94abc..6403eb01b0 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Select.Details // 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++) { - Flow.Add(new StatisticRow() + Flow.Add(new StatisticRow { Width = columns == 1 ? 1 : 0.5f, Padding = columns == 1 ? new MarginPadding() : new MarginPadding { Horizontal = 5, Vertical = 2.5f }, diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index 0d280a1b27..43e8d895b9 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -293,7 +293,6 @@ namespace osu.Game.Screens.SelectV2 return; } - BeatmapDifficulty originalDifficulty = beatmap.Value.BeatmapInfo.Difficulty; Ruleset rulesetInstance = ruleset.Value.CreateInstance(); var displayAttributes = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value).ToList(); From 34292a75eeb936184cb2fdb1277f94d319aed357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jul 2025 15:01:58 +0200 Subject: [PATCH 08/11] Fix test --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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] From e4e62c16d56106b6f239176805423103192691f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Jul 2025 01:18:59 +0900 Subject: [PATCH 09/11] Fix glitchy attribute display when switching between rulesets at song select --- .../BeatmapTitleWedge_DifficultyStatisticsDisplay.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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() { From 7abe4fc93c57877672bfb9bcbd6951506c2343e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Jul 2025 01:19:55 +0900 Subject: [PATCH 10/11] Fix glitchy display of count statistics when changing rulesets This is due to the global betamap becoming `Default` momentarily. Rather than react to this by clearing the display in a single frame, let's transition so it's mostly hidden. --- .../Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index 43e8d895b9..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); From cf41a04a2e130c081a8ef70c7272686fb17137c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 31 Jul 2025 08:46:04 +0200 Subject: [PATCH 11/11] Fix test --- .../Visual/SongSelectV2/TestSceneBeatmapTitleWedge.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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]