From e71c95f1fed41cf194d4c004c0d1b5a9e3620f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 14:48:27 +0100 Subject: [PATCH 1/5] Reintroduce `IMod.Ranked` What goes around, comes around. --- osu.Game/Rulesets/Mods/IMod.cs | 5 +++++ osu.Game/Rulesets/Mods/Mod.cs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 744d02a4fa..3a33d14835 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -66,6 +66,11 @@ namespace osu.Game.Rulesets.Mods /// bool AlwaysValidForSubmission { get; } + /// + /// Whether scores with this mod active can give performance points. + /// + bool Ranked { get; } + /// /// Create a fresh instance based on this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 0500b49513..50c867f41b 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + /// + /// Whether scores with this mod active can give performance points. + /// + [JsonIgnore] + public virtual bool Ranked => false; + /// /// The mods this mod cannot be enabled with. /// From f89923aeaee5785aeb4b91b6aaa3dfdf8b8ad791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 14:59:35 +0100 Subject: [PATCH 2/5] Annotate mods that give pp --- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 + osu.Game/Rulesets/Mods/ModDaycore.cs | 1 + osu.Game/Rulesets/Mods/ModDoubleTime.cs | 1 + osu.Game/Rulesets/Mods/ModEasy.cs | 1 + osu.Game/Rulesets/Mods/ModFlashlight.cs | 1 + osu.Game/Rulesets/Mods/ModHalfTime.cs | 1 + osu.Game/Rulesets/Mods/ModHardRock.cs | 1 + osu.Game/Rulesets/Mods/ModHidden.cs | 1 + osu.Game/Rulesets/Mods/ModMuted.cs | 1 + osu.Game/Rulesets/Mods/ModNightcore.cs | 1 + osu.Game/Rulesets/Mods/ModNoFail.cs | 1 + osu.Game/Rulesets/Mods/ModPerfect.cs | 1 + osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 1 + 21 files changed, 21 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 050b302bd8..88d6a19822 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index d9de06a811..189c4b3a5f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModHardRock : ModHardRock { public override double ScoreMultiplier => 1; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 31f52610e9..7dd0c499da 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "One Key"; public override string Acronym => "1K"; public override LocalisableString Description => @"Play with one key."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs index 67e65b887a..a6c57d4597 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Ten Keys"; public override string Acronym => "10K"; public override LocalisableString Description => @"Play with ten keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 0f8148d252..0d04395a52 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Two Keys"; public override string Acronym => "2K"; public override LocalisableString Description => @"Play with two keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 0f8af7940c..c83b0979ee 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Three Keys"; public override string Acronym => "3K"; public override LocalisableString Description => @"Play with three keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index f9690b4298..cc7e270dda 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModMirror : ModMirror, IApplicableToBeatmap { public override LocalisableString Description => "Notes are flipped horizontally."; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index f691731afe..df9544b71e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) }; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToDrawableHitObject(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f1468d414e..917685cdad 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTouchDevice : ModTouchDevice { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override bool Ranked => UsesDefaultConfiguration; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 09b35c249e..359f8a950c 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => null; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Whoaaaaa..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 789291772d..8e430da368 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModDoubleTime; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Zoooooooooom..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 0f51e2a6d5..da43a6b294 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty) { diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index dc2ad6f47e..9227af64b8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Restricted view area."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public abstract BindableFloat SizeMultiplier { get; } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 8b5dd39584..59e40ee9cc 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Less zoom..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4b2d1d050e..1e99891b99 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; protected const float ADJUST_RATIO = 1.4f; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 8b25768575..5a1abf115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "HD"; public override IconUsage? Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 131f501630..3ecd9aa6a1 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Can you still feel the rhythm without music?"; public override ModType Type => ModType.Fun; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; } public abstract class ModMuted : ModMuted, IApplicableToDrawableRuleset, IApplicableToTrack, IApplicableToScoreProcessor diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index b42927256c..bb18940f8c 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModNightcore; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Uguuuuuuuu..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index cc451772b2..1aaef8eac4 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) }; + public override bool Ranked => UsesDefaultConfiguration; private readonly Bindable showHealthBar = new Bindable(); diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 0ba40ba070..f8f498ceb5 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; public override LocalisableString Description => "SS or quit."; + public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 4e4e8662e8..62579a168c 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Miss and fail."; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); From 966093f7cea7097e9095d5595cc8401e317b3fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 15:50:43 +0100 Subject: [PATCH 3/5] Transform score multiplier display to also show ranked state --- .../TestSceneModSelectOverlay.cs | 8 +- ... => TestSceneRankingInformationDisplay.cs} | 18 ++-- .../Localisation/ModSelectOverlayStrings.cs | 20 ++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 ++-- ...isplay.cs => RankingInformationDisplay.cs} | 95 ++++++++++++------- 5 files changed, 105 insertions(+), 55 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneScoreMultiplierDisplay.cs => TestSceneRankingInformationDisplay.cs} (50%) rename osu.Game/Overlays/Mods/{ScoreMultiplierDisplay.cs => RankingInformationDisplay.cs} (60%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 046954db47..99a5897dff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -846,7 +846,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddAssert("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs similarity index 50% rename from osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs index c2ddd814b7..42f243cc21 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Mods; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public partial class TestSceneScoreMultiplierDisplay : OsuTestScene + public partial class TestSceneRankingInformationDisplay : OsuTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); @@ -19,22 +19,24 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestBasic() { - ScoreMultiplierDisplay multiplierDisplay = null!; + RankingInformationDisplay onlinePropertiesDisplay = null!; - AddStep("create content", () => Child = multiplierDisplay = new ScoreMultiplierDisplay + AddStep("create content", () => Child = onlinePropertiesDisplay = new RankingInformationDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre }); - AddStep("set multiplier below 1", () => multiplierDisplay.Current.Value = 0.5); - AddStep("set multiplier to 1", () => multiplierDisplay.Current.Value = 1); - AddStep("set multiplier above 1", () => multiplierDisplay.Current.Value = 1.5); + AddToggleStep("toggle ranked", ranked => onlinePropertiesDisplay.Ranked.Value = ranked); + + AddStep("set multiplier below 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 0.5); + AddStep("set multiplier to 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1); + AddStep("set multiplier above 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1.5); AddSliderStep("set multiplier", 0, 2, 1d, multiplier => { - if (multiplierDisplay.IsNotNull()) - multiplierDisplay.Current.Value = multiplier; + if (onlinePropertiesDisplay.IsNotNull()) + onlinePropertiesDisplay.ModMultiplier.Value = multiplier; }); } } diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 86ebebd293..9513eacf02 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -49,6 +49,26 @@ namespace osu.Game.Localisation /// public static LocalisableString ScoreMultiplier => new TranslatableString(getKey(@"score_multiplier"), @"Score Multiplier"); + /// + /// "Ranked" + /// + public static LocalisableString Ranked => new TranslatableString(getKey(@"ranked"), @"Ranked"); + + /// + /// "Performance points can be granted for the active mods." + /// + public static LocalisableString RankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points can be granted for the active mods."); + + /// + /// "Unranked" + /// + public static LocalisableString Unranked => new TranslatableString(getKey(@"unranked"), @"Unranked"); + + /// + /// "Performance points will not be granted due to active mods." + /// + public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points will not be granted due to active mods."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7271c53e7a..ddf96c1cb3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Mods private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private ScoreMultiplierDisplay? multiplierDisplay; + private RankingInformationDisplay? rankingInformationDisplay; private BeatmapAttributesDisplay? beatmapAttributesDisplay; protected ShearedButton BackButton { get; private set; } = null!; @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.Mods aboveColumnsContent = new Container { RelativeSizeAxes = Axes.X, - Height = ScoreMultiplierDisplay.HEIGHT, + Height = RankingInformationDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, Child = SearchTextBox = new ShearedSearchTextBox { @@ -200,7 +200,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = ScoreMultiplierDisplay.HEIGHT + PADDING, + Top = RankingInformationDisplay.HEIGHT + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -269,7 +269,7 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - multiplierDisplay = new ScoreMultiplierDisplay + rankingInformationDisplay = new RankingInformationDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight @@ -315,7 +315,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - updateMultiplier(); + updateRankingInformation(); updateFromExternalSelection(); updateCustomisation(); @@ -328,7 +328,7 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); } }, true); @@ -450,9 +450,9 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - private void updateMultiplier() + private void updateRankingInformation() { - if (multiplierDisplay == null) + if (rankingInformationDisplay == null) return; double multiplier = 1.0; @@ -460,7 +460,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; - multiplierDisplay.Current.Value = multiplier; + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); } private void updateCustomisation() diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs similarity index 60% rename from osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs rename to osu.Game/Overlays/Mods/RankingInformationDisplay.cs index a86eba81e4..494f8a377f 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,15 +22,13 @@ namespace osu.Game.Overlays.Mods /// /// On the mod select overlay, this provides a local updating view of the aggregate score multiplier coming from mods. /// - public partial class ScoreMultiplierDisplay : ModFooterInformationDisplay, IHasCurrentValue + public partial class RankingInformationDisplay : ModFooterInformationDisplay { public const float HEIGHT = 42; - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } + public Bindable ModMultiplier = new BindableDouble(1); + + public Bindable Ranked { get; } = new BindableBool(true); private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -39,16 +37,11 @@ namespace osu.Game.Overlays.Mods private RollingCounter counter = null!; private Box flashLayer = null!; + private TextWithTooltip rankedText = null!; [Resolved] private OsuColour colours { get; set; } = null!; - public ScoreMultiplierDisplay() - { - Current.Default = 1d; - Current.Value = 1d; - } - [BackgroundDependencyLoader] private void load() { @@ -75,13 +68,20 @@ namespace osu.Game.Overlays.Mods LeftContent.AddRange(new Drawable[] { - new OsuSpriteText + new Container { + Width = 50, + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding(10), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = ModSelectOverlayStrings.ScoreMultiplier, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + Child = rankedText = new TextWithTooltip + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } } }); @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = { BindTarget = Current } + Current = { BindTarget = ModMultiplier } } }); } @@ -106,30 +106,22 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Current.BindValueChanged(e => + ModMultiplier.BindValueChanged(e => { - if (e.NewValue > Current.Default) + if (e.NewValue > ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); } - else if (e.NewValue < Current.Default) + else if (e.NewValue < ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); } else { - MainBackground.FadeColour(ColourProvider.Background4, transition_duration, Easing.OutQuint); counter.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); } - flashLayer - .FadeOutFromOne() - .FadeTo(0.15f, 60, Easing.OutQuint) - .Then().FadeOut(500, Easing.OutQuint); + flash(); const float move_amount = 4; if (e.NewValue > e.OldValue) @@ -140,10 +132,43 @@ namespace osu.Game.Overlays.Mods // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - counter.SetCountWithoutRolling(Current.Value); + counter.SetCountWithoutRolling(ModMultiplier.Value); + + Ranked.BindValueChanged(e => + { + flash(); + + if (e.NewValue) + { + rankedText.Text = ModSelectOverlayStrings.Ranked; + rankedText.TooltipText = ModSelectOverlayStrings.RankedExplanation; + rankedText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(ColourProvider.Background3, transition_duration, Easing.OutQuint); + } + else + { + rankedText.Text = ModSelectOverlayStrings.Unranked; + rankedText.TooltipText = ModSelectOverlayStrings.UnrankedExplanation; + rankedText.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(colours.Orange1, transition_duration, Easing.OutQuint); + } + }, true); } - private partial class EffectCounter : RollingCounter + private void flash() + { + flashLayer + .FadeOutFromOne() + .FadeTo(0.15f, 60, Easing.OutQuint) + .Then().FadeOut(500, Easing.OutQuint); + } + + private partial class TextWithTooltip : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } + + private partial class EffectCounter : RollingCounter, IHasTooltip { protected override double RollingDuration => 250; @@ -155,6 +180,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) }; + + public LocalisableString TooltipText => ModSelectOverlayStrings.ScoreMultiplier; } } } From f87ab197318b08e91757b82ea5898de19487b495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 21:22:36 +0100 Subject: [PATCH 4/5] Apply NRT to `FooterButtonMods` --- osu.Game/Screens/Select/FooterButtonMods.cs | 47 ++++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 69782c25bb..57784dd6ca 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; @@ -31,28 +28,14 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } - protected readonly OsuSpriteText MultiplierText; - private readonly ModDisplay modDisplay; + protected OsuSpriteText MultiplierText { get; private set; } = null!; + private ModDisplay modDisplay = null!; + + private ModSettingChangeTracker? modSettingChangeTracker; + private Color4 lowMultiplierColour; private Color4 highMultiplierColour; - public FooterButtonMods() - { - ButtonContentContainer.Add(modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }); - ButtonContentContainer.Add(MultiplierText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - }); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -62,10 +45,24 @@ namespace osu.Game.Screens.Select highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = GlobalAction.ToggleModSelection; - } - [CanBeNull] - private ModSettingChangeTracker modSettingChangeTracker; + ButtonContentContainer.AddRange(new Drawable[] + { + modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }, + MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + } + }); + } protected override void LoadComplete() { From 865f4d76fff5fe0cff3d7c2d6cf2aae95c99ffac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 21:51:41 +0100 Subject: [PATCH 5/5] Add unranked indicator to song select footer too --- .../TestSceneFooterButtonMods.cs | 11 ++++ osu.Game/Screens/Select/FooterButtonMods.cs | 58 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index a95bb2c9e3..b79ce6c75f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -67,6 +68,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } + [Test] + public void TestUnrankedBadge() + { + AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); + AddAssert("Unranked badge shown", () => footerButtonMods.UnrankedBadge.Alpha == 1); + AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddAssert("Unranked badge not shown", () => footerButtonMods.UnrankedBadge.Alpha == 0); + } + private void changeMods(IReadOnlyList mods) { footerButtonMods.Current.Value = mods; @@ -83,6 +93,7 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestFooterButtonMods : FooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; + public new Drawable UnrankedBadge => base.UnrankedBadge; } } } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 57784dd6ca..5685910c0a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -9,6 +9,9 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; @@ -16,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Utils; namespace osu.Game.Screens.Select @@ -29,13 +33,27 @@ namespace osu.Game.Screens.Select } protected OsuSpriteText MultiplierText { get; private set; } = null!; - private ModDisplay modDisplay = null!; + protected Container UnrankedBadge { get; private set; } = null!; + + private readonly ModDisplay modDisplay; private ModSettingChangeTracker? modSettingChangeTracker; private Color4 lowMultiplierColour; private Color4 highMultiplierColour; + public FooterButtonMods() + { + // must be created in ctor for correct operation of `Current`. + modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -48,19 +66,38 @@ namespace osu.Game.Screens.Select ButtonContentContainer.AddRange(new Drawable[] { - modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }, + modDisplay, MultiplierText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold), - } + }, + UnrankedBadge = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Gray2, + Padding = new MarginPadding(5), + UseFullGlyphHeight = false, + Text = ModSelectOverlayStrings.Unranked.ToLower() + } + } + }, }); } @@ -98,6 +135,9 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); + + bool anyUnrankedMods = Current.Value?.Any(m => !m.Ranked) == true; + UnrankedBadge.FadeTo(anyUnrankedMods ? 1 : 0); }); } }