From 1025e1939ba90b96595c3ab0d9fc5a86a3f0527a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Mar 2022 11:54:18 +0300 Subject: [PATCH 001/126] Disable "Adaptive Speed" mod in multiplayer --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e30ec36e9c..5ccf89e703 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,6 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && !(mod is ModAdaptiveSpeed); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } } From d90a33485318f926e51c6f9da270876756bfab30 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 03:40:15 +0300 Subject: [PATCH 002/126] Introduce multiplayer playability and free mod validity in `Mod` --- osu.Game/Rulesets/Mods/IMod.cs | 12 ++++++++++++ osu.Game/Rulesets/Mods/Mod.cs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index d5d1de91de..cda59bae55 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -39,6 +39,18 @@ namespace osu.Game.Rulesets.Mods /// bool UserPlayable { get; } + /// + /// Whether this mod is playable in a multiplayer match. + /// Should be false for mods that affect the gameplay progress based on user input (e.g. ). + /// + bool PlayableInMultiplayer { get; } + + /// + /// Whether this mod is valid to be a "free mod" in a multiplayer match. + /// Should be false for mods that affect the gameplay progress (e.g. and ). + /// + bool ValidFreeModInMultiplayer { 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 b2d4be54ce..a6562b4f4c 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -94,6 +94,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool UserPlayable => true; + [JsonIgnore] + public virtual bool PlayableInMultiplayer => UserPlayable; + + [JsonIgnore] + public virtual bool ValidFreeModInMultiplayer => PlayableInMultiplayer; + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; From 187059a37f69ed4ceff8ea4e2e2a0b8dda3b0085 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 03:40:40 +0300 Subject: [PATCH 003/126] Replace hardcoded overrides with the newly introduced `Mod` properties --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 ++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 ++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 1115b95e6f..54a4c054c9 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; + public override bool PlayableInMultiplayer => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; [SettingSource("Initial rate", "The starting speed of the track")] diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ebe18f2188..3a11c3034b 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { + public override bool ValidFreeModInMultiplayer => false; + public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(ITrack track) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index b6b2decede..59aac62686 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } + public override bool ValidFreeModInMultiplayer => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5ccf89e703..e4f1d9587d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,8 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && !(mod is ModAdaptiveSpeed); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.PlayableInMultiplayer; - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidFreeModInMultiplayer; } } From 59741ccee60d9b05466d9b7a1fea6157106ba5d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:15:05 +0300 Subject: [PATCH 004/126] Add multiplayer mod validity check methods for server consumption --- osu.Game/OsuGame.cs | 15 ++++++++++----- osu.Game/Utils/ModUtils.cs | 26 +++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ae117d03d2..bc2d2083fe 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -575,11 +575,16 @@ namespace osu.Game if (SelectedMods.Disabled) return; - if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) - { - // ensure we always have a valid set of mods. - SelectedMods.Value = mods.NewValue.Except(invalid).ToArray(); - } + var validMods = mods.NewValue; + + if (!ModUtils.CheckCompatibleSet(validMods, out var incompatible)) + validMods = validMods.Except(incompatible).ToArray(); + + if (!ModUtils.CheckValidForGameplay(validMods, out var invalid)) + validMods = validMods.Except(invalid).ToArray(); + + // ensure we always have a valid set of mods. + SelectedMods.Value = validMods; } #endregion diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..d169ace80a 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -112,14 +112,34 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Can be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); + + /// + /// Check the provided combination of mods are valid for a multiplayer match session. + /// + /// The mods to check. + /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Whether the input mods were all valid. If false, will contain all invalid entries. + public static bool CheckValidForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.PlayableInMultiplayer, out invalidMods); + + /// + /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. + /// + /// The mods to check. + /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Whether the input mods were all valid. If false, will contain all invalid entries. + public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.ValidFreeModInMultiplayer, out invalidMods); + + private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { mods = mods.ToArray(); - - CheckCompatibleSet(mods, out invalidMods); + invalidMods = null; foreach (var mod in mods) { - if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod) + if (!valid(mod)) { invalidMods ??= new List(); invalidMods.Add(mod); From 07e9f3780a8335ca07d3e076193c0b7cbdc80982 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:15:48 +0300 Subject: [PATCH 005/126] Consider `UnknownMod` to be "playable in multiplayer" --- osu.Game/Rulesets/Mods/UnknownMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index b426386d7a..790306c0ca 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0; public override bool UserPlayable => false; + public override bool PlayableInMultiplayer => true; public override ModType Type => ModType.System; From b3ac544d655e6846a94b6bf2d31332b471d90de4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:31:51 +0300 Subject: [PATCH 006/126] Revert "Consider `UnknownMod` to be "playable in multiplayer"" This reverts commit 07e9f3780a8335ca07d3e076193c0b7cbdc80982. --- osu.Game/Rulesets/Mods/UnknownMod.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index 790306c0ca..b426386d7a 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0; public override bool UserPlayable => false; - public override bool PlayableInMultiplayer => true; public override ModType Type => ModType.System; From d90f21e140bea785d07fd33c8ef1af126c16a16b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Mar 2022 00:13:40 +0300 Subject: [PATCH 007/126] Reword mod documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Mods/IMod.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index cda59bae55..5f4fecb649 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -41,13 +41,13 @@ namespace osu.Game.Rulesets.Mods /// /// Whether this mod is playable in a multiplayer match. - /// Should be false for mods that affect the gameplay progress based on user input (e.g. ). + /// Should be false for mods that make gameplay duration dependent on user input (e.g. ). /// bool PlayableInMultiplayer { get; } /// /// Whether this mod is valid to be a "free mod" in a multiplayer match. - /// Should be false for mods that affect the gameplay progress (e.g. and ). + /// Should be false for mods that affect the gameplay duration (e.g. and ). /// bool ValidFreeModInMultiplayer { get; } From b0d04a78f7d2e4b7fadaee32841dca3387c44f34 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Mar 2022 00:21:16 +0300 Subject: [PATCH 008/126] Reword mod utility documentation regarding nullability --- osu.Game/Utils/ModUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d169ace80a..81b78c18ac 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -109,7 +109,7 @@ namespace osu.Game.Utils /// Check the provided combination of mods are valid for a local gameplay session. /// /// The mods to check. - /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); @@ -118,7 +118,7 @@ namespace osu.Game.Utils /// Check the provided combination of mods are valid for a multiplayer match session. /// /// The mods to check. - /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) => checkValid(mods, m => m.PlayableInMultiplayer, out invalidMods); @@ -127,7 +127,7 @@ namespace osu.Game.Utils /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. /// /// The mods to check. - /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) => checkValid(mods, m => m.ValidFreeModInMultiplayer, out invalidMods); From 51e5dd7d0e58fb681db6da9e34e9d435e1026a58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Mar 2022 02:08:11 +0300 Subject: [PATCH 009/126] Introduce `IsPlayable(...)` and obsolete `UserPlayable` --- .../BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/IMod.cs | 26 ++++++++++--------- osu.Game/Rulesets/Mods/Mod.cs | 11 +++----- osu.Game/Rulesets/Mods/ModUsage.cs | 26 +++++++++++++++++++ 4 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModUsage.cs diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 25aed4c980..97b89c6f74 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.Solo)).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 5f4fecb649..bdfb273b13 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -33,24 +33,26 @@ namespace osu.Game.Rulesets.Mods /// IconUsage? Icon { get; } + /// + /// Whether this mod is playable for the given usage. + /// + /// + /// + /// Should be always false for cases where the user is not interacting with the game. + /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). + /// Should be false in for mods that affect the gameplay duration (e.g. and ). + /// + /// + /// The mod usage. + bool IsPlayable(ModUsage usage); + /// /// Whether this mod is playable by an end user. /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). /// + [Obsolete("Override IsPlayable instead.")] // Can be removed 20220918 bool UserPlayable { get; } - /// - /// Whether this mod is playable in a multiplayer match. - /// Should be false for mods that make gameplay duration dependent on user input (e.g. ). - /// - bool PlayableInMultiplayer { get; } - - /// - /// Whether this mod is valid to be a "free mod" in a multiplayer match. - /// Should be false for mods that affect the gameplay duration (e.g. and ). - /// - bool ValidFreeModInMultiplayer { 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 a6562b4f4c..00aef1a598 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -91,16 +91,13 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; + public virtual bool IsPlayable(ModUsage usage) => true; + [JsonIgnore] + [Obsolete("Override IsPlayable instead.")] // Can be removed 20220918 public virtual bool UserPlayable => true; - [JsonIgnore] - public virtual bool PlayableInMultiplayer => UserPlayable; - - [JsonIgnore] - public virtual bool ValidFreeModInMultiplayer => PlayableInMultiplayer; - - [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override IsPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; /// diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs new file mode 100644 index 0000000000..82ff6bc418 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mods +{ + /// + /// The usage of this mod to determine its playability. + /// + public enum ModUsage + { + /// + /// In a solo gameplay session. + /// + Solo, + + /// + /// In a multiplayer match, as a required mod. + /// + MultiplayerRequired, + + /// + /// In a multiplayer match, as a "free" mod. + /// + MultiplayerFree, + } +} From f2248ecc08000c1c259e306ad8d599b0a1c48c8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Mar 2022 02:11:18 +0300 Subject: [PATCH 010/126] Update usages to use `IsPlayable` instead --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 +- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++-- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 3 ++- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 ++- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- osu.Game/Utils/ModUtils.cs | 8 ++++---- 12 files changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 54a4c054c9..17ef9f926e 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; - public override bool PlayableInMultiplayer => false; + public override bool IsPlayable(ModUsage usage) => usage == ModUsage.Solo; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 60b9c29fe0..5491cbec07 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; - public override bool UserPlayable => false; + public override bool IsPlayable(ModUsage usage) => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 3a11c3034b..810b93c4dd 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { - public override bool ValidFreeModInMultiplayer => false; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerFree; public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 59aac62686..fa6a9f3e5b 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override bool ValidFreeModInMultiplayer => false; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerFree; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index b426386d7a..75d86e67bc 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "This mod could not be resolved by the game."; public override double ScoreMultiplier => 0; - public override bool UserPlayable => false; + public override bool IsPlayable(ModUsage usage) => false; public override ModType Type => ModType.System; diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index d5abaaab4e..974e2b9305 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value(m); + set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.Solo) && value(m); } public FreeModSelectOverlay() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e4f1d9587d..4c1350a56b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,8 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.PlayableInMultiplayer; + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerRequired); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidFreeModInMultiplayer; + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerFree); } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 7b64784316..75e5ea60a4 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.UserPlayable); + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.Solo)); /// /// Checks whether a given is valid for per-player free-mod selection. diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 42091c521f..27aa4fe1f1 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -187,7 +188,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; - if (score.NewValue.Mods.Any(m => !m.UserPlayable)) + if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.Solo))) return; var hitEvents = score.NewValue.HitEvents; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b1f2bccddf..5de8aa1dc1 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -47,7 +48,7 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (Mods.Value.Any(m => !m.UserPlayable)) + if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.Solo))) { handleTokenFailure(new InvalidOperationException("Non-user playable mod selected.")); return false; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index cb842ce4a0..09b6cc0f23 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking.Statistics; @@ -145,7 +146,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable); + bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.Solo)); ScorePanelList.AddScore(Score, shouldFlair); } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 81b78c18ac..4915f5bdc6 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -115,13 +115,13 @@ namespace osu.Game.Utils => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); /// - /// Check the provided combination of mods are valid for a multiplayer match session. + /// Check the provided combination of mods are valid as "required mods" in a multiplayer match session. /// /// The mods to check. /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. - public static bool CheckValidForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.PlayableInMultiplayer, out invalidMods); + public static bool CheckValidRequiredModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerRequired), out invalidMods); /// /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. @@ -130,7 +130,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.ValidFreeModInMultiplayer, out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerFree), out invalidMods); private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { From 70e943fbccb1d1e2746aeb605f63b208cf8b6a76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Mar 2022 06:36:51 +0300 Subject: [PATCH 011/126] `ModUsage.Solo` -> `ModUsage.User` --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- osu.Game/Rulesets/Mods/ModUsage.cs | 2 +- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 97b89c6f74..caeb757d7a 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.Solo)).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.User)).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 17ef9f926e..801cd3cba7 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; - public override bool IsPlayable(ModUsage usage) => usage == ModUsage.Solo; + public override bool IsPlayable(ModUsage usage) => usage == ModUsage.User; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 82ff6bc418..714a99056b 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods /// /// In a solo gameplay session. /// - Solo, + User, /// /// In a multiplayer match, as a required mod. diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 974e2b9305..37cffd8343 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.Solo) && value(m); + set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.User) && value(m); } public FreeModSelectOverlay() diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 75e5ea60a4..d4f89b5206 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.Solo)); + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.User)); /// /// Checks whether a given is valid for per-player free-mod selection. diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 27aa4fe1f1..bab391337a 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; - if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.Solo))) + if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.User))) return; var hitEvents = score.NewValue.HitEvents; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5de8aa1dc1..205ee9ab9c 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.Solo))) + if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.User))) { handleTokenFailure(new InvalidOperationException("Non-user playable mod selected.")); return false; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 09b6cc0f23..ac3816c4ec 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.Solo)); + bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.User)); ScorePanelList.AddScore(Score, shouldFlair); } From 820a672940b151a43b54f510dc03b8476bd95ce9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Mar 2022 06:37:08 +0300 Subject: [PATCH 012/126] Reword xmldoc to make more sense --- osu.Game/Rulesets/Mods/ModUsage.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 714a99056b..0892f528d5 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -4,22 +4,22 @@ namespace osu.Game.Rulesets.Mods { /// - /// The usage of this mod to determine its playability. + /// The usage of this mod to determine whether it's playable in such context. /// public enum ModUsage { /// - /// In a solo gameplay session. + /// Used for a per-user gameplay session. Determines whether the mod is playable by an end user. /// User, /// - /// In a multiplayer match, as a required mod. + /// Used as a "required mod" for a multiplayer match. /// MultiplayerRequired, /// - /// In a multiplayer match, as a "free" mod. + /// Used as a "free mod" for a multiplayer match. /// MultiplayerFree, } From add9f3ec9177815f7dab386927c47ad436d9576f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Mar 2022 13:12:24 +0300 Subject: [PATCH 013/126] Rename multiplayer mod usages to make more sense --- osu.Game/Rulesets/Mods/IMod.cs | 4 ++-- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Mods/ModUsage.cs | 12 +++++++----- .../Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++-- osu.Game/Utils/ModUtils.cs | 4 ++-- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index bdfb273b13..325b75b76e 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -39,8 +39,8 @@ namespace osu.Game.Rulesets.Mods /// /// /// Should be always false for cases where the user is not interacting with the game. - /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). - /// Should be false in for mods that affect the gameplay duration (e.g. and ). + /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). + /// Should be false in for mods that affect the gameplay duration (e.g. and ). /// /// /// The mod usage. diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 810b93c4dd..ab724673b6 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerFree; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerPerPlayer; public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index fa6a9f3e5b..96b38301b5 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerFree; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerPerPlayer; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 0892f528d5..59a62bfc6f 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -9,18 +9,20 @@ namespace osu.Game.Rulesets.Mods public enum ModUsage { /// - /// Used for a per-user gameplay session. Determines whether the mod is playable by an end user. + /// Used for a per-user gameplay session. + /// Determines whether the mod is playable by an end user. /// User, /// - /// Used as a "required mod" for a multiplayer match. + /// Used in multiplayer but must be applied to all users. + /// This is generally the case for mods which affect the length of gameplay. /// - MultiplayerRequired, + MultiplayerRoomWide, /// - /// Used as a "free mod" for a multiplayer match. + /// Used in multiplayer either at a room or per-player level (i.e. "free mod"). /// - MultiplayerFree, + MultiplayerPerPlayer, } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 4c1350a56b..63467dbb9d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,8 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerRequired); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerRoomWide); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerFree); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerPerPlayer); } } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 4915f5bdc6..946386a7ce 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -121,7 +121,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidRequiredModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerRequired), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerRoomWide), out invalidMods); /// /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. @@ -130,7 +130,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerFree), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerPerPlayer), out invalidMods); private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { From 5f878ed82b98a1d3ec14af96879c4d68bee8d167 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Mar 2022 16:07:08 +0300 Subject: [PATCH 014/126] Delegate `IsPlayable` to the obsoleted `UserPlayable` by default Handles consumers who still haven't updated to use `IsPlayable` yet. --- osu.Game/Rulesets/Mods/Mod.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 00aef1a598..debb1a1e48 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -91,7 +91,9 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; - public virtual bool IsPlayable(ModUsage usage) => true; +#pragma warning disable 618 + public virtual bool IsPlayable(ModUsage usage) => UserPlayable; +#pragma warning restore 618 [JsonIgnore] [Obsolete("Override IsPlayable instead.")] // Can be removed 20220918 From 145fca2704457fe58baaf294b960812ce41dd49d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Mar 2022 16:17:19 +0300 Subject: [PATCH 015/126] Fix failing test scenes --- osu.Game.Tests/Mods/ModUtilsTest.cs | 12 ------------ osu.Game/Utils/ModUtils.cs | 6 +++--- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 4c126f0a3b..e858abfe44 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -134,18 +134,6 @@ namespace osu.Game.Tests.Mods private static readonly object[] invalid_mod_test_scenarios = { - // incompatible pair. - new object[] - { - new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() }, - new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) } - }, - // incompatible pair with derived class. - new object[] - { - new Mod[] { new OsuModNightcore(), new OsuModHalfTime() }, - new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) } - }, // system mod. new object[] { diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 946386a7ce..0d46494319 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -106,7 +106,7 @@ namespace osu.Game.Utils } /// - /// Check the provided combination of mods are valid for a local gameplay session. + /// Checks that all s in a combination are valid for a local gameplay session. /// /// The mods to check. /// Invalid mods, if any were found. Will be null if all mods were valid. @@ -115,7 +115,7 @@ namespace osu.Game.Utils => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); /// - /// Check the provided combination of mods are valid as "required mods" in a multiplayer match session. + /// Checks that all s in a combination are valid as "required mods" in a multiplayer match session. /// /// The mods to check. /// Invalid mods, if any were found. Will be null if all mods were valid. @@ -124,7 +124,7 @@ namespace osu.Game.Utils => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerRoomWide), out invalidMods); /// - /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. + /// Checks that all s in a combination are valid as "free mods" in a multiplayer match session. /// /// The mods to check. /// Invalid mods, if any were found. Will be null if all mods were valid. From b218046fa28ef9ebb257663adf5388d9174345ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Mar 2022 15:38:48 +0300 Subject: [PATCH 016/126] Remove redundant line from mod usage --- osu.Game/Rulesets/Mods/ModUsage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 59a62bfc6f..746387a062 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Mods { /// /// Used for a per-user gameplay session. - /// Determines whether the mod is playable by an end user. /// User, From 6cd67928aba27e2936fd8f0a6cfc0ae7838e1e67 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Mar 2022 15:48:50 +0300 Subject: [PATCH 017/126] Simplify documentation of `ModUsage` --- osu.Game/Rulesets/Mods/ModUsage.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 746387a062..04d99149dd 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -4,23 +4,23 @@ namespace osu.Game.Rulesets.Mods { /// - /// The usage of this mod to determine whether it's playable in such context. + /// The context in which a is playable. /// public enum ModUsage { /// - /// Used for a per-user gameplay session. + /// This mod can be used for a per-user gameplay session. /// User, /// - /// Used in multiplayer but must be applied to all users. + /// This mod can be used in multiplayer but must be applied to all users. /// This is generally the case for mods which affect the length of gameplay. /// MultiplayerRoomWide, /// - /// Used in multiplayer either at a room or per-player level (i.e. "free mod"). + /// This mod can be used in multiplayer either at a room or per-player level (i.e. "free mod"). /// MultiplayerPerPlayer, } From 4f16da893286b09da833f3b3aa24037019fdb266 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 2 May 2022 20:04:34 +0800 Subject: [PATCH 018/126] Set EnforceCodeStyleInBuild --- .globalconfig | 55 +++++++++++++++++++++++++++++++++++++++++++ Directory.Build.props | 1 + osu.sln | 1 + 3 files changed, 57 insertions(+) create mode 100644 .globalconfig diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000000..607798492c --- /dev/null +++ b/.globalconfig @@ -0,0 +1,55 @@ +is_global = true + +# .NET Code Style +# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ + +# IDE0001: Simplify names +dotnet_diagnostic.IDE0001.severity = warning + +# IDE0002: Simplify member access +dotnet_diagnostic.IDE0002.severity = warning + +# IDE0003: Remove qualification +dotnet_diagnostic.IDE0003.severity = warning + +# IDE0004: Remove unnecessary cast +dotnet_diagnostic.IDE0004.severity = warning + +# IDE0005: Remove unnecessary imports +dotnet_diagnostic.IDE0005.severity = warning + +# IDE0034: Simplify default literal +dotnet_diagnostic.IDE0034.severity = warning + +# IDE0036: Sort modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0040: Add accessibility modifier +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0049: Use keyword for type name +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = warning + +# IDE0051: Private method is unused +dotnet_diagnostic.IDE0051.severity = silent + +# IDE0052: Private member is unused +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = warning + +# IDE0130: Namespace mismatch with folder +dotnet_diagnostic.IDE0130.severity = warning + +# IDE1006: Naming style +dotnet_diagnostic.IDE1006.severity = warning + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error diff --git a/Directory.Build.props b/Directory.Build.props index 709545bf1d..f3ddc68838 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,7 @@ + true $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset diff --git a/osu.sln b/osu.sln index b5018db362..aeec0843be 100644 --- a/osu.sln +++ b/osu.sln @@ -56,6 +56,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props From 7cf4dabe29961c7f9d74f88e5827269598ed45ce Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 2 May 2022 20:07:53 +0800 Subject: [PATCH 019/126] Fix IDE0005 and IDE0034 --- osu.Game/Migrations/20181007180454_StandardizePaths.cs | 4 +--- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- .../Overlays/Profile/Header/Components/LevelProgressBar.cs | 2 +- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 3 +-- .../Edit/Compose/Components/SelectionBoxRotationHandle.cs | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index 274b8030a9..11a020eb9c 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -1,6 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using System.IO; +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 9ee002fd9d..f528f54cdf 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0; - ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString); + ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index ec9cb55042..d8eb5b65ac 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateProgress(APIUser user) { levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; - levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default(LocalisableString); + levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default; } } } diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 48a4c31f30..c05c160463 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Rankings startDateColumn.Value = dateToString(response.Spotlight.StartDate); endDateColumn.Value = dateToString(response.Spotlight.EndDate); mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0"); - participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default(LocalisableString); + participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default; } private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd"); diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index bdbd2942d1..6c85ec2753 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; @@ -25,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { - new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString), } + new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default, } }; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 22479bd9b3..f13ed0456a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateTooltipText() { - TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default(LocalisableString); + TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default; } } } From 5513710b2efbe9684cbf850ea7ef8c8ce04dcb38 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 2 May 2022 21:51:28 +0800 Subject: [PATCH 020/126] Fix IDE0055 --- .../CatchSelectionBlueprintTestScene.cs | 5 +- .../TestSceneManageCollectionsDialog.cs | 3 +- .../OnlinePlay/Components/DrawableGameType.cs | 76 ++++++++++--------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs index e345e03c96..88fd3b36ba 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -29,13 +29,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchSelectionBlueprintTestScene() { - EditorBeatmap = new EditorBeatmap(new CatchBeatmap + var catchBeatmap = new CatchBeatmap { BeatmapInfo = { Ruleset = new CatchRuleset().RulesetInfo, } - }) { Difficulty = { CircleSize = 0 } }; + }; + EditorBeatmap = new EditorBeatmap(catchBeatmap) { Difficulty = { CircleSize = 0 } }; EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 100 diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 51ca55f37f..d5983ac827 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -141,7 +141,8 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add dropdown", () => { - Add(new CollectionFilterDropdown + Add( + new CollectionFilterDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs index 613f16563c..f360a80599 100644 --- a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs +++ b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs @@ -87,44 +87,46 @@ namespace osu.Game.Screens.OnlinePlay.Components }, }; - // case MatchType.TagCoop: - // return new SpriteIcon - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // Size = new Vector2(size), - // Icon = FontAwesome.Solid.Sync, - // Colour = colours.Blue, - // - // Shadow = false - // }; +#pragma warning disable IDE0055 // Indentation of commented code + // case MatchType.TagCoop: + // return new SpriteIcon + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // Size = new Vector2(size), + // Icon = FontAwesome.Solid.Sync, + // Colour = colours.Blue, + // + // Shadow = false + // }; - // case MatchType.TagTeamCoop: - // return new FillFlowContainer - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // AutoSizeAxes = Axes.Both, - // Direction = FillDirection.Horizontal, - // Spacing = new Vector2(2f), - // Children = new[] - // { - // new SpriteIcon - // { - // Icon = FontAwesome.Solid.Sync, - // Size = new Vector2(size * 0.75f), - // Colour = colours.Blue, - // Shadow = false, - // }, - // new SpriteIcon - // { - // Icon = FontAwesome.Solid.Sync, - // Size = new Vector2(size * 0.75f), - // Colour = colours.Pink, - // Shadow = false, - // }, - // }, - // }; + // case MatchType.TagTeamCoop: + // return new FillFlowContainer + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // AutoSizeAxes = Axes.Both, + // Direction = FillDirection.Horizontal, + // Spacing = new Vector2(2f), + // Children = new[] + // { + // new SpriteIcon + // { + // Icon = FontAwesome.Solid.Sync, + // Size = new Vector2(size * 0.75f), + // Colour = colours.Blue, + // Shadow = false, + // }, + // new SpriteIcon + // { + // Icon = FontAwesome.Solid.Sync, + // Size = new Vector2(size * 0.75f), + // Colour = colours.Pink, + // Shadow = false, + // }, + // }, + // }; +#pragma warning restore IDE0055 } } From 547038f13b9840e263ce1bc6c0d57841acf8aeb6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 May 2022 14:28:53 +0800 Subject: [PATCH 021/126] Align editorconfig with framework --- .editorconfig | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/.editorconfig b/.editorconfig index 840fa98334..35ac84fca0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,14 @@ # EditorConfig is awesome: http://editorconfig.org root = true +[*.{csproj,props,targets}] +charset = utf-8-bom +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + [*.cs] end_of_line = crlf insert_final_newline = true @@ -8,8 +16,19 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = true +#license header +file_header_template = Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text. + #Roslyn naming styles +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -157,7 +176,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = false:silent +csharp_style_deconstructed_variable_declaration = true:warning #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -168,28 +187,15 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = false:silent -csharp_style_prefer_range_operator = false:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning csharp_style_prefer_switch_expression = false:none -#Supressing roslyn built-in analyzers -# Suppress: EC112 - -#Private method is unused -dotnet_diagnostic.IDE0051.severity = silent -#Private member is unused -dotnet_diagnostic.IDE0052.severity = silent - -#Rules for disposable -dotnet_diagnostic.IDE0067.severity = none -dotnet_diagnostic.IDE0068.severity = none -dotnet_diagnostic.IDE0069.severity = none - -#Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none - -# Banned APIs -dotnet_diagnostic.RS0030.severity = error +[*.{yaml,yml}] +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true dotnet_diagnostic.OLOC001.words_in_name = 5 dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. From 1202c29ea1aa4318a111e196f583c430700ff951 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 May 2022 14:33:14 +0800 Subject: [PATCH 022/126] Add license headers to EF migration files --- osu.Game/Migrations/20171019041408_InitialCreate.cs | 5 ++++- osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs | 5 ++++- .../20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs | 5 ++++- .../Migrations/20171209034410_AddRulesetInfoShortName.cs | 5 ++++- osu.Game/Migrations/20180125143340_Settings.cs | 5 ++++- osu.Game/Migrations/20180131154205_AddMuteBinding.cs | 5 ++++- osu.Game/Migrations/20180219060912_AddSkins.cs | 5 ++++- .../Migrations/20180529055154_RemoveUniqueHashConstraints.cs | 5 ++++- .../Migrations/20180621044111_UpdateTaikoDefaultBindings.cs | 5 ++++- osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs | 5 ++++- osu.Game/Migrations/20180913080842_AddRankStatus.cs | 5 ++++- osu.Game/Migrations/20181007180454_StandardizePaths.cs | 5 ++++- osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs | 5 ++++- osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs | 5 ++++- osu.Game/Migrations/20190225062029_AddUserIDColumn.cs | 5 ++++- osu.Game/Migrations/20190525060824_SkinSettings.cs | 5 ++++- .../20190605091246_AddDateAddedColumnToBeatmapSet.cs | 5 ++++- osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs | 5 ++++- osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs | 5 ++++- osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs | 5 ++++- osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs | 5 ++++- .../Migrations/20210412045700_RefreshVolumeBindingsAgain.cs | 5 ++++- .../Migrations/20210511060743_AddSkinInstantiationInfo.cs | 5 ++++- .../20210514062639_AddAuthorIdToBeatmapMetadata.cs | 5 ++++- osu.Game/Migrations/20210824185035_AddCountdownSettings.cs | 5 ++++- .../Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs | 5 ++++- 26 files changed, 104 insertions(+), 26 deletions(-) diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index 9b6881f98c..08ab64fd08 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs index c9fc59c5a2..4ec3952941 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs index 084ae67940..6aba12f86f 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs index 09cf0af89c..5688455f79 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 166d3c086d..1feb37531f 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs index 5564a30bbf..8646d1d76b 100644 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Infrastructure; using osu.Game.Database; using osu.Game.Input.Bindings; diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs index a0270ab0fd..319748bed6 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs index 27269cc5fc..91eabe8868 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs index 71304ea979..d888ccd5a2 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index 506d65f761..fdea636ac6 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs index bba4944bb7..bb147dff84 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index 11a020eb9c..30f27043a0 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs index 860264a7dd..ee825a1e9c 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs index 2b6f94c5a4..58980132f3 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -1,4 +1,7 @@ -using System; +// 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 Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs index 0720e0eac7..f2eef600dc 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs index 99237419b7..7779b55bb7 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs index 55dc18b6a3..0620a0624f 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -1,4 +1,7 @@ -using System; +// 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 Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs index f5963ebf5e..f8ce354aa1 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs index 9ed0943acd..af82b4db20 100644 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs index ec4475971c..3d2ddbf6fc 100644 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs index be6968aa5d..58a35a7bf3 100644 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs index 155d6670a8..4d3941dd20 100644 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs index 1d5b0769a4..887635fa85 100644 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs index 98fe9b5e13..7b579e27b9 100644 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs index 564f5f4520..d1b09e2c1d 100644 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs index bf3f855d5f..f6fc1f4420 100644 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { From 2a388ba3eaba2df84a1f5b5a6c7e3d22149f1a7e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 May 2022 14:34:54 +0800 Subject: [PATCH 023/126] Turn IDE0052 to silent according to the occurrences --- .globalconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.globalconfig b/.globalconfig index 607798492c..462dbc74ed 100644 --- a/.globalconfig +++ b/.globalconfig @@ -37,7 +37,7 @@ dotnet_diagnostic.IDE0055.severity = warning dotnet_diagnostic.IDE0051.severity = silent # IDE0052: Private member is unused -dotnet_diagnostic.IDE0052.severity = warning +dotnet_diagnostic.IDE0052.severity = silent # IDE0073: File header dotnet_diagnostic.IDE0073.severity = warning From 8e96af8ff0b7caaaf7e2542856daa6a7ecd001af Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 4 May 2022 13:43:59 +0800 Subject: [PATCH 024/126] Update indentation to be clearer --- .../TestSceneManageCollectionsDialog.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d5983ac827..888002eb36 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -141,15 +141,13 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add dropdown", () => { - Add( - new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } - ); + Add(new CollectionFilterDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + }); }); AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] { From 35eeeba4c60b9240296e077ae5968d40100877ee Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 4 May 2022 13:57:53 +0800 Subject: [PATCH 025/126] Disable code style analysis on Xamarin projects --- osu.Android.props | 3 ++- osu.iOS.props | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b6260fd1d4..299f236220 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,4 +1,4 @@ - + 8.0 bin\$(Configuration) @@ -19,6 +19,7 @@ cjk,mideast,other,rare,west SdkOnly prompt + false True diff --git a/osu.iOS.props b/osu.iOS.props index e472b5f1a8..d16c6803f4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -1,4 +1,4 @@ - + 8.0 {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -11,6 +11,7 @@ iPhone Developer true + false From a17bbd62b7d05491a1ed2d5575819feb0c7fcdc5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 4 May 2022 14:06:01 +0800 Subject: [PATCH 026/126] Disable EnforceCodeStyleInBuild in Xamarin CI run --- .github/workflows/ci.yml | 4 ++-- Directory.Build.props | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2066f27de..b4d7f894d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,7 +128,7 @@ jobs: # cannot accept .sln(f) files as arguments. # Build just the main game for now. - name: Build - run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug + run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug /p:EnforceCodeStyleInBuild=false build-only-ios: name: Build only (iOS) @@ -147,4 +147,4 @@ jobs: # cannot accept .sln(f) files as arguments. # Build just the main game for now. - name: Build - run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug \ No newline at end of file + run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug /p:EnforceCodeStyleInBuild=false diff --git a/Directory.Build.props b/Directory.Build.props index f3ddc68838..65ffe02584 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,4 @@ - + 8.0 @@ -20,7 +20,7 @@ - true + true $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 81ce0e6565f244f6d1c59f00653ca972ab49a63d Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 4 May 2022 12:55:22 +0100 Subject: [PATCH 027/126] Reimplement sliderticks --- .../Mods/OsuModStrictTracking.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index ab45e5192d..a1e6946157 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -70,6 +70,11 @@ namespace osu.Game.Rulesets.Osu.Mods drawableRuleset.Playfield.RegisterPool(10, 100); } + private class StrictTrackingSliderTick : SliderTick + { + public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); + } + private class StrictTrackingSliderTailCircle : SliderTailCircle { public StrictTrackingSliderTailCircle(Slider slider) @@ -109,6 +114,18 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (e.Type) { + case SliderEventType.Tick: + AddNested(new StrictTrackingSliderTick + { + SpanIndex = e.SpanIndex, + SpanStartTime = e.SpanStartTime, + StartTime = e.Time, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + }); + break; + case SliderEventType.Head: AddNested(HeadCircle = new SliderHeadCircle { From 20e277d2e587069d0534bf457efa8999d0ac9629 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 May 2022 17:08:41 +0300 Subject: [PATCH 028/126] Apply proposed naming changes --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/IMod.cs | 4 ++-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Mods/ModUsage.cs | 6 +++--- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++-- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Utils/ModUtils.cs | 4 ++-- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index caeb757d7a..17ea117018 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.User)).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.SoloLocal)).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 325b75b76e..d37ef53a66 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -39,8 +39,8 @@ namespace osu.Game.Rulesets.Mods /// /// /// Should be always false for cases where the user is not interacting with the game. - /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). - /// Should be false in for mods that affect the gameplay duration (e.g. and ). + /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). + /// Should be false in for mods that affect the gameplay duration (e.g. and ). /// /// /// The mod usage. diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 801cd3cba7..700ad30c08 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; - public override bool IsPlayable(ModUsage usage) => usage == ModUsage.User; + public override bool IsPlayable(ModUsage usage) => usage == ModUsage.SoloLocal; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 59d9026f3b..acb39bfbe6 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerPerPlayer; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 96b38301b5..c554c39010 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerPerPlayer; + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs index 04d99149dd..3f25154d73 100644 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ b/osu.Game/Rulesets/Mods/ModUsage.cs @@ -11,17 +11,17 @@ namespace osu.Game.Rulesets.Mods /// /// This mod can be used for a per-user gameplay session. /// - User, + SoloLocal, /// /// This mod can be used in multiplayer but must be applied to all users. /// This is generally the case for mods which affect the length of gameplay. /// - MultiplayerRoomWide, + MultiplayerGlobal, /// /// This mod can be used in multiplayer either at a room or per-player level (i.e. "free mod"). /// - MultiplayerPerPlayer, + MultiplayerLocal, } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 37cffd8343..13d5dd8927 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.User) && value(m); + set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.SoloLocal) && value(m); } public FreeModSelectOverlay() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5bf217031a..d7e752623d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -95,8 +95,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerRoomWide); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerGlobal); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerPerPlayer); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerLocal); } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index d9da230f58..b8b0604c79 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.User)); + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.SoloLocal)); /// /// Checks whether a given is valid for per-player free-mod selection. diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 554b58f481..af2cecfba5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; - if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.User))) + if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.SoloLocal))) return; var hitEvents = score.NewValue.HitEvents; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 6846992b13..b33ff87d55 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.User))) + if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.SoloLocal))) { handleTokenFailure(new InvalidOperationException("Non-user playable mod selected.")); return false; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index ee2c00a135..e0ae0309ce 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.User)); + bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.SoloLocal)); ScorePanelList.AddScore(Score, shouldFlair); } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index c5cf469f7c..e19020a959 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -121,7 +121,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidRequiredModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerRoomWide), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); /// /// Checks that all s in a combination are valid as "free mods" in a multiplayer match session. @@ -130,7 +130,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerPerPlayer), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods); private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { From 8f04db5df5136f9431e62a6fffce273f2ba8d6bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 May 2022 17:21:19 +0300 Subject: [PATCH 029/126] Bring back behaviour of checking incompatibility on gameplay validity --- osu.Game.Tests/Mods/ModUtilsTest.cs | 12 ++++++++++++ osu.Game/OsuGame.cs | 15 +++++---------- osu.Game/Utils/ModUtils.cs | 15 +++++++++++---- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index e858abfe44..4c126f0a3b 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -134,6 +134,18 @@ namespace osu.Game.Tests.Mods private static readonly object[] invalid_mod_test_scenarios = { + // incompatible pair. + new object[] + { + new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() }, + new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) } + }, + // incompatible pair with derived class. + new object[] + { + new Mod[] { new OsuModNightcore(), new OsuModHalfTime() }, + new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) } + }, // system mod. new object[] { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 29985b0893..e9fe8c43de 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -577,16 +577,11 @@ namespace osu.Game if (SelectedMods.Disabled) return; - var validMods = mods.NewValue; - - if (!ModUtils.CheckCompatibleSet(validMods, out var incompatible)) - validMods = validMods.Except(incompatible).ToArray(); - - if (!ModUtils.CheckValidForGameplay(validMods, out var invalid)) - validMods = validMods.Except(invalid).ToArray(); - - // ensure we always have a valid set of mods. - SelectedMods.Value = validMods; + if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) + { + // ensure we always have a valid set of mods. + SelectedMods.Value = mods.NewValue.Except(invalid).ToArray(); + } } #endregion diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index e19020a959..c18125054a 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -112,7 +112,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); + => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods, true); /// /// Checks that all s in a combination are valid as "required mods" in a multiplayer match session. @@ -121,7 +121,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidRequiredModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods, true); /// /// Checks that all s in a combination are valid as "free mods" in a multiplayer match session. @@ -130,13 +130,20 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods, false); - private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) + private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods, bool checkCompatibility) { mods = mods.ToArray(); invalidMods = null; + if (checkCompatibility) + { + // exclude multi mods from compatibility checks. + // the loop below automatically marks all multi mods as not valid for gameplay anyway. + CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods); + } + foreach (var mod in mods) { if (!valid(mod)) From 8488a29e9e4db0dd26ab6ec2ecacd24681b764b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 May 2022 17:22:11 +0300 Subject: [PATCH 030/126] Renew obsoletion date --- osu.Game/Rulesets/Mods/IMod.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index d37ef53a66..c0d4aa5c9f 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mods /// Whether this mod is playable by an end user. /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). /// - [Obsolete("Override IsPlayable instead.")] // Can be removed 20220918 + [Obsolete("Override IsPlayable instead.")] // Can be removed 20221104 bool UserPlayable { get; } /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index debb1a1e48..b2f1b7e24f 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mods #pragma warning restore 618 [JsonIgnore] - [Obsolete("Override IsPlayable instead.")] // Can be removed 20220918 + [Obsolete("Override IsPlayable instead.")] // Can be removed 20221104 public virtual bool UserPlayable => true; [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override IsPlayable to false.")] // Can be removed 20211009 From d0df9e8051f7580b94e888079557d6cf62018b40 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 May 2022 18:56:27 +0300 Subject: [PATCH 031/126] Inline `CheckCompatibleSet` method to avoid ugly boolean flag --- osu.Game/Utils/ModUtils.cs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index c18125054a..40db981ef0 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -112,7 +112,16 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods, true); + { + mods = mods.ToArray(); + + // exclude multi mods from compatibility checks. + // the loop below automatically marks all multi mods as not valid for gameplay anyway. + if (!CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods)) + return false; + + return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); + } /// /// Checks that all s in a combination are valid as "required mods" in a multiplayer match session. @@ -121,7 +130,14 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidRequiredModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods, true); + { + mods = mods.ToArray(); + + if (!CheckCompatibleSet(mods, out invalidMods)) + return false; + + return checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); + } /// /// Checks that all s in a combination are valid as "free mods" in a multiplayer match session. @@ -130,20 +146,13 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods, false); + => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods); - private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods, bool checkCompatibility) + private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { mods = mods.ToArray(); invalidMods = null; - if (checkCompatibility) - { - // exclude multi mods from compatibility checks. - // the loop below automatically marks all multi mods as not valid for gameplay anyway. - CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods); - } - foreach (var mod in mods) { if (!valid(mod)) From e3c7c5d0b9ceca3ea19683280321c66e08155c42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 May 2022 08:12:54 +0300 Subject: [PATCH 032/126] Improve validity methods to include system, non-implemented, and multi mods --- osu.Game/Utils/ModUtils.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 40db981ef0..f11a06e3be 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -115,12 +115,15 @@ namespace osu.Game.Utils { mods = mods.ToArray(); - // exclude multi mods from compatibility checks. - // the loop below automatically marks all multi mods as not valid for gameplay anyway. - if (!CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods)) + // checking compatibility of multi mods would try to flatten them and return incompatible mods. + // in gameplay context, we never want MultiMod selected in the first place, therefore check against it first. + if (!checkValid(mods, m => !(m is MultiMod), out invalidMods)) return false; - return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); + if (!CheckCompatibleSet(mods, out invalidMods)) + return false; + + return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation, out invalidMods); } /// @@ -133,10 +136,15 @@ namespace osu.Game.Utils { mods = mods.ToArray(); + // checking compatibility of multi mods would try to flatten them and return incompatible mods. + // in gameplay context, we never want MultiMod selected in the first place, therefore check against it first. + if (!checkValid(mods, m => !(m is MultiMod), out invalidMods)) + return false; + if (!CheckCompatibleSet(mods, out invalidMods)) return false; - return checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); + return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); } /// @@ -146,7 +154,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.IsPlayable(ModUsage.MultiplayerLocal), out invalidMods); + => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.IsPlayable(ModUsage.MultiplayerLocal) && !(m is MultiMod), out invalidMods); private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { From 86aa2125fe9878eda71857a0e1d0ca0823ab2899 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 May 2022 08:13:29 +0300 Subject: [PATCH 033/126] Add test coverage for multiplayer mod validity methods --- osu.Game.Tests/Mods/ModUtilsTest.cs | 161 ++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 4c126f0a3b..2447233d3c 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -137,33 +137,137 @@ namespace osu.Game.Tests.Mods // incompatible pair. new object[] { - new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() }, - new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) } + new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() }, + new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) } }, // incompatible pair with derived class. new object[] { - new Mod[] { new OsuModNightcore(), new OsuModHalfTime() }, - new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) } + new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() }, + new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) } }, // system mod. new object[] { - new Mod[] { new OsuModDoubleTime(), new OsuModTouchDevice() }, + new Mod[] { new OsuModHidden(), new OsuModTouchDevice() }, new[] { typeof(OsuModTouchDevice) } }, // multi mod. new object[] { - new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModDaycore() }, + new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) }, new[] { typeof(MultiMod) } }, + // invalid multiplayer mod is valid for local. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() }, + null + }, + // invalid free mod is valid for local. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, + null + }, // valid pair. new object[] { - new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() }, + new Mod[] { new OsuModHidden(), new OsuModHardRock() }, null - } + }, + }; + + private static readonly object[] invalid_multiplayer_mod_test_scenarios = + { + // incompatible pair. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() }, + new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) } + }, + // incompatible pair with derived class. + new object[] + { + new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() }, + new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) } + }, + // system mod. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModTouchDevice() }, + new[] { typeof(OsuModTouchDevice) } + }, + // multi mod. + new object[] + { + new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) }, + new[] { typeof(MultiMod) } + }, + // invalid multiplayer mod. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() }, + new[] { typeof(InvalidMultiplayerMod) } + }, + // invalid free mod is valid for multiplayer global. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, + null + }, + // valid pair. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + null + }, + }; + + private static readonly object[] invalid_free_mod_test_scenarios = + { + // system mod. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModTouchDevice() }, + new[] { typeof(OsuModTouchDevice) } + }, + // multi mod. + new object[] + { + new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) }, + new[] { typeof(MultiMod) } + }, + // invalid multiplayer mod. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() }, + new[] { typeof(InvalidMultiplayerMod) } + }, + // invalid free mod. + new object[] + { + new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, + new[] { typeof(InvalidMultiplayerFreeMod) } + }, + // incompatible pair is valid for free mods. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() }, + null, + }, + // incompatible pair with derived class is valid for free mods. + new object[] + { + new Mod[] { new OsuModDeflate(), new OsuModSpinIn() }, + null, + }, + // valid pair. + new object[] + { + new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + null + }, }; [TestCaseSource(nameof(invalid_mod_test_scenarios))] @@ -179,6 +283,32 @@ namespace osu.Game.Tests.Mods Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } + [TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))] + public void TestInvalidMultiplayerModScenarios(Mod[] inputMods, Type[] expectedInvalid) + { + bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid); + + Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + + if (isValid) + Assert.IsNull(invalid); + else + Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + } + + [TestCaseSource(nameof(invalid_free_mod_test_scenarios))] + public void TestInvalidFreeModScenarios(Mod[] inputMods, Type[] expectedInvalid) + { + bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid); + + Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + + if (isValid) + Assert.IsNull(invalid); + else + Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + } + public abstract class CustomMod1 : Mod, IModCompatibilitySpecification { } @@ -187,6 +317,21 @@ namespace osu.Game.Tests.Mods { } + public class InvalidMultiplayerMod : Mod + { + public override string Name => string.Empty; + public override string Description => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + public override bool HasImplementation => true; + public override bool IsPlayable(ModUsage usage) => usage == ModUsage.SoloLocal; + } + + private class InvalidMultiplayerFreeMod : InvalidMultiplayerMod + { + public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; + } + public interface IModCompatibilitySpecification { } From 8501a4161958514bfae52c5e78aee2ee26a870e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 May 2022 14:37:38 +0300 Subject: [PATCH 034/126] Bring back separate bool properties as non-cascading --- osu.Game.Tests/Mods/ModUtilsTest.cs | 12 ++++++--- .../TestSceneFreeModSelectScreen.cs | 3 +-- .../BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/IMod.cs | 26 +++++++++--------- osu.Game/Rulesets/Mods/Mod.cs | 11 ++++---- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 3 ++- osu.Game/Rulesets/Mods/ModAutoplay.cs | 4 ++- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Mods/ModUsage.cs | 27 ------------------- osu.Game/Rulesets/Mods/UnknownMod.cs | 4 ++- .../OnlinePlay/FreeModSelectOverlay.cs | 2 +- .../Screens/OnlinePlay/FreeModSelectScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 4 +-- .../OnlinePlay/OnlinePlaySongSelect.cs | 2 +- .../PlayerSettings/BeatmapOffsetControl.cs | 3 +-- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +-- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 +-- osu.Game/Utils/ModUtils.cs | 4 +-- 19 files changed, 49 insertions(+), 70 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModUsage.cs diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 2447233d3c..6c9dddf51f 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -324,12 +324,18 @@ namespace osu.Game.Tests.Mods public override string Acronym => string.Empty; public override double ScoreMultiplier => 1; public override bool HasImplementation => true; - public override bool IsPlayable(ModUsage usage) => usage == ModUsage.SoloLocal; + public override bool ValidForMultiplayer => false; + public override bool ValidForMultiplayerAsFreeMod => false; } - private class InvalidMultiplayerFreeMod : InvalidMultiplayerMod + private class InvalidMultiplayerFreeMod : Mod { - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; + public override string Name => string.Empty; + public override string Description => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + public override bool HasImplementation => true; + public override bool ValidForMultiplayerAsFreeMod => false; } public interface IModCompatibilitySpecification diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs index fad784a09e..b5f901e51d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("all visible mods are playable", () => this.ChildrenOfType() .Where(panel => panel.IsPresent) - .All(panel => panel.Mod.HasImplementation && panel.Mod.IsPlayable(ModUsage.SoloLocal))); + .All(panel => panel.Mod.HasImplementation && panel.Mod.UserPlayable)); AddToggleStep("toggle visibility", visible => { diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 17ea117018..25aed4c980 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.IsPlayable(ModUsage.SoloLocal)).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesetInstance.AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index c0d4aa5c9f..30fa1ea8cb 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -33,26 +33,24 @@ namespace osu.Game.Rulesets.Mods /// IconUsage? Icon { get; } - /// - /// Whether this mod is playable for the given usage. - /// - /// - /// - /// Should be always false for cases where the user is not interacting with the game. - /// Should be false in for mods that make gameplay duration dependent on user input (e.g. ). - /// Should be false in for mods that affect the gameplay duration (e.g. and ). - /// - /// - /// The mod usage. - bool IsPlayable(ModUsage usage); - /// /// Whether this mod is playable by an end user. /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). /// - [Obsolete("Override IsPlayable instead.")] // Can be removed 20221104 bool UserPlayable { get; } + /// + /// Whether this mod is valid for multiplayer matches. + /// Should be false for mods that make gameplay duration dependent on user input (e.g. ). + /// + bool ValidForMultiplayer { get; } + + /// + /// Whether this mod is valid as a free mod in multiplayer matches. + /// Should be false for mods that affect the gameplay duration (e.g. and ). + /// + bool ValidForMultiplayerAsFreeMod { 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 b2f1b7e24f..93990d36e1 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -91,13 +91,14 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; -#pragma warning disable 618 - public virtual bool IsPlayable(ModUsage usage) => UserPlayable; -#pragma warning restore 618 + [JsonIgnore] + public virtual bool UserPlayable => true; [JsonIgnore] - [Obsolete("Override IsPlayable instead.")] // Can be removed 20221104 - public virtual bool UserPlayable => true; + public virtual bool ValidForMultiplayer => true; + + [JsonIgnore] + public virtual bool ValidForMultiplayerAsFreeMod => true; [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override IsPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 700ad30c08..93251f7b2d 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; - public override bool IsPlayable(ModUsage usage) => usage == ModUsage.SoloLocal; + public override bool ValidForMultiplayer => false; + public override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 9df25b006d..0ebe11b393 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,7 +24,9 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; - public override bool IsPlayable(ModUsage usage) => false; + public override bool UserPlayable => false; + public override bool ValidForMultiplayer => false; + public override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index acb39bfbe6..05953f903f 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; + public override bool ValidForMultiplayerAsFreeMod => false; public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index c554c39010..fe6d54332c 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override bool IsPlayable(ModUsage usage) => usage != ModUsage.MultiplayerLocal; + public override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; diff --git a/osu.Game/Rulesets/Mods/ModUsage.cs b/osu.Game/Rulesets/Mods/ModUsage.cs deleted file mode 100644 index 3f25154d73..0000000000 --- a/osu.Game/Rulesets/Mods/ModUsage.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Mods -{ - /// - /// The context in which a is playable. - /// - public enum ModUsage - { - /// - /// This mod can be used for a per-user gameplay session. - /// - SoloLocal, - - /// - /// This mod can be used in multiplayer but must be applied to all users. - /// This is generally the case for mods which affect the length of gameplay. - /// - MultiplayerGlobal, - - /// - /// This mod can be used in multiplayer either at a room or per-player level (i.e. "free mod"). - /// - MultiplayerLocal, - } -} diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index 75d86e67bc..72de0ad653 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -15,7 +15,9 @@ namespace osu.Game.Rulesets.Mods public override string Description => "This mod could not be resolved by the game."; public override double ScoreMultiplier => 0; - public override bool IsPlayable(ModUsage usage) => false; + public override bool UserPlayable => false; + public override bool ValidForMultiplayer => false; + public override bool ValidForMultiplayerAsFreeMod => false; public override ModType Type => ModType.System; diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 13d5dd8927..d5abaaab4e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && m.IsPlayable(ModUsage.SoloLocal) && value(m); + set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value(m); } public FreeModSelectOverlay() diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 415ae60706..5a7a60b479 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.IsPlayable(ModUsage.SoloLocal) && value.Invoke(m); + set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } public FreeModSelectScreen() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index d7e752623d..929c3ee321 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -95,8 +95,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.IsPlayable(ModUsage.MultiplayerGlobal); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.ValidForMultiplayer; - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.IsPlayable(ModUsage.MultiplayerLocal); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidForMultiplayerAsFreeMod; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b8b0604c79..6a559dbb2c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.IsPlayable(ModUsage.SoloLocal)); + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.UserPlayable); /// /// Checks whether a given is valid for per-player free-mod selection. diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index af2cecfba5..1662ca399f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -20,7 +20,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -188,7 +187,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; - if (score.NewValue.Mods.Any(m => !m.IsPlayable(ModUsage.SoloLocal))) + if (score.NewValue.Mods.Any(m => !m.UserPlayable)) return; var hitEvents = score.NewValue.HitEvents; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b33ff87d55..b62dc1e5a6 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,7 +11,6 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (Mods.Value.Any(m => !m.IsPlayable(ModUsage.SoloLocal))) + if (Mods.Value.Any(m => !m.UserPlayable)) { handleTokenFailure(new InvalidOperationException("Non-user playable mod selected.")); return false; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e0ae0309ce..98514cd846 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -18,7 +18,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking.Statistics; @@ -146,7 +145,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.IsPlayable(ModUsage.SoloLocal)); + bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable); ScorePanelList.AddScore(Score, shouldFlair); } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index f11a06e3be..a252a9b416 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -144,7 +144,7 @@ namespace osu.Game.Utils if (!CheckCompatibleSet(mods, out invalidMods)) return false; - return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.IsPlayable(ModUsage.MultiplayerGlobal), out invalidMods); + return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayer, out invalidMods); } /// @@ -154,7 +154,7 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) - => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.IsPlayable(ModUsage.MultiplayerLocal) && !(m is MultiMod), out invalidMods); + => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayerAsFreeMod && !(m is MultiMod), out invalidMods); private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { From 43c9058d09dbff83898f8b7f841c524d8a72d454 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 May 2022 14:39:05 +0300 Subject: [PATCH 035/126] Fix wrong obsolete message --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 93990d36e1..af1550f8a9 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool ValidForMultiplayerAsFreeMod => true; - [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override IsPlayable to false.")] // Can be removed 20211009 + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; /// From 2039d3db6a76031a7f23d9c84140f0f1f8f94d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 14:37:57 +0200 Subject: [PATCH 036/126] Use standard slider ticks in strict tracking mod --- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index a1e6946157..8bbfa68fdf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -70,11 +70,6 @@ namespace osu.Game.Rulesets.Osu.Mods drawableRuleset.Playfield.RegisterPool(10, 100); } - private class StrictTrackingSliderTick : SliderTick - { - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); - } - private class StrictTrackingSliderTailCircle : SliderTailCircle { public StrictTrackingSliderTailCircle(Slider slider) @@ -115,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (e.Type) { case SliderEventType.Tick: - AddNested(new StrictTrackingSliderTick + AddNested(new SliderTick { SpanIndex = e.SpanIndex, SpanStartTime = e.SpanStartTime, From 74c0cb2f6eb9ddf40563e70da00f9454e29b22ca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 May 2022 16:16:55 +0300 Subject: [PATCH 037/126] Add note about not checking compatibility in free mods validity method --- osu.Game/Utils/ModUtils.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index a252a9b416..ea092a8ca3 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -150,6 +150,11 @@ namespace osu.Game.Utils /// /// Checks that all s in a combination are valid as "free mods" in a multiplayer match session. /// + /// + /// Note that this does not check compatibility between mods, + /// given that the passed mods are expected to be the ones to be allowed for the multiplayer match, + /// not to be confused with the list of mods the user currently has selected for the multiplayer match. + /// /// The mods to check. /// Invalid mods, if any were found. Will be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. From aafb363a3487e547ea349d0717f489557eb82252 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 6 May 2022 01:24:19 +0800 Subject: [PATCH 038/126] Only enable EnforceCodeStyleInBuild in CI --- .github/workflows/ci.yml | 7 +++++-- Directory.Build.props | 1 - osu.Android.props | 1 - osu.iOS.props | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4d7f894d4..57069a0ae2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,9 @@ jobs: path: ${{ github.workspace }}/inspectcode key: inspectcode-${{ hashFiles('.config/dotnet-tools.json') }}-${{ hashFiles('.github/workflows/ci.yml' ) }} + - name: Dotnet code style + run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true + - name: CodeFileSanity run: | # TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround. @@ -128,7 +131,7 @@ jobs: # cannot accept .sln(f) files as arguments. # Build just the main game for now. - name: Build - run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug /p:EnforceCodeStyleInBuild=false + run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug build-only-ios: name: Build only (iOS) @@ -147,4 +150,4 @@ jobs: # cannot accept .sln(f) files as arguments. # Build just the main game for now. - name: Build - run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug /p:EnforceCodeStyleInBuild=false + run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug diff --git a/Directory.Build.props b/Directory.Build.props index 65ffe02584..73a150d3e3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,7 +20,6 @@ - true $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset diff --git a/osu.Android.props b/osu.Android.props index 299f236220..2866ec24a6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -19,7 +19,6 @@ cjk,mideast,other,rare,west SdkOnly prompt - false True diff --git a/osu.iOS.props b/osu.iOS.props index d16c6803f4..f987ae9bf8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -11,7 +11,6 @@ iPhone Developer true - false From 30ffc7b23f0d354cad739df4ca2e4c62ba42a294 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 6 May 2022 01:28:30 +0800 Subject: [PATCH 039/126] Remove dotnet-format --- .config/dotnet-tools.json | 6 ------ .github/workflows/ci.yml | 4 ---- 2 files changed, 10 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5a3eadf607..1132396608 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-format": { - "version": "3.1.37601", - "commands": [ - "dotnet-format" - ] - }, "jetbrains.resharper.globaltools": { "version": "2022.1.0-eap10", "commands": [ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57069a0ae2..514acef525 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,10 +49,6 @@ jobs: done <<< $(dotnet codefilesanity) exit $exit_code - # Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded. - # - name: .NET Format (Dry Run) - # run: dotnet format --dry-run --check - - name: InspectCode run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN From 701ddade47739c20143e56e4da271663b24bce7d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 6 May 2022 01:32:37 +0800 Subject: [PATCH 040/126] Revert editorconfig changes from framework --- .editorconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 35ac84fca0..c0ea55f4c8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -176,7 +176,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -187,8 +187,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none [*.{yaml,yml}] From 407db7ff9d288d9f1a81d05027f732eb33019c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 May 2022 21:49:02 +0200 Subject: [PATCH 041/126] Replace old mod select overlay with new design --- .../Navigation/TestSceneScreenNavigation.cs | 2 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 36 ++++++------------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a1f41d4caf..8ba074a7d1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -551,7 +551,7 @@ namespace osu.Game.Tests.Visual.Navigation public class TestPlaySongSelect : PlaySongSelect { - public ModSelectOverlay ModSelectOverlay => ModSelect; + public ModSelectScreen ModSelectOverlay => ModSelect; public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 6a559dbb2c..0eeb32230d 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(e); } - protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay + protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen { IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2a1ed2a7a8..3c46433b45 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select [Resolved(CanBeNull = true)] private LegacyImportManager legacyImportManager { get; set; } - protected ModSelectOverlay ModSelect { get; private set; } + protected ModSelectScreen ModSelect { get; private set; } protected Sample SampleConfirm { get; private set; } @@ -252,35 +252,19 @@ namespace osu.Game.Screens.Select { AddRangeInternal(new Drawable[] { - new GridContainer // used for max height implementation + FooterPanels = new Container { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Padding = new MarginPadding { Bottom = Footer.HEIGHT }, + Children = new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.Relative, 1f, maxSize: ModSelectOverlay.HEIGHT + Footer.HEIGHT), - }, - Content = new[] - { - null, - new Drawable[] - { - FooterPanels = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = Footer.HEIGHT }, - Children = new Drawable[] - { - BeatmapOptions = new BeatmapOptionsOverlay(), - ModSelect = CreateModSelectOverlay() - } - } - } + BeatmapOptions = new BeatmapOptionsOverlay(), } }, - Footer = new Footer() + Footer = new Footer(), + ModSelect = CreateModSelectOverlay() }); } @@ -332,7 +316,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(); + protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 1744d7e4f0cd7130b0dad22da73bb708d9295c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 May 2022 21:53:04 +0200 Subject: [PATCH 042/126] Fix new mod select overlay dimming itself --- .../Containers/OsuFocusedOverlayContainer.cs | 6 +-- osu.Game/OsuGame.cs | 45 ++++++++++++++++++- osu.Game/Screens/Select/SongSelect.cs | 21 ++++++++- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 68351acd7e..0e8e481e9d 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -127,14 +127,14 @@ namespace osu.Game.Graphics.Containers if (didChange) samplePopIn?.Play(); - if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); + if (BlockScreenWideMouse && DimMainContent) game?.ShowBlockingOverlay(this); break; case Visibility.Hidden: if (didChange) samplePopOut?.Play(); - if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); + if (BlockScreenWideMouse) game?.HideBlockingOverlay(this); break; } @@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Containers protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game?.RemoveBlockingOverlay(this); + game?.HideBlockingOverlay(this); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e9fe8c43de..da8cdde944 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -171,6 +171,7 @@ namespace osu.Game private readonly string[] args; private readonly List focusedOverlays = new List(); + private readonly List externalOverlays = new List(); private readonly List visibleBlockingOverlays = new List(); @@ -186,19 +187,59 @@ namespace osu.Game private void updateBlockingOverlayFade() => ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); - public void AddBlockingOverlay(OverlayContainer overlay) + /// + /// Registers a blocking that was not created by itself for later use. + /// + /// + /// The goal of this method is to allow child screens, like to register their own full-screen blocking overlays + /// with background dim. + /// In those cases, for the dim to work correctly, the overlays need to be added at the `OsuGame` level directly, rather as children of the screens. + /// + /// + /// An that should be disposed of when the should be unregistered. + /// Disposing of this will automatically expire the . + /// + internal IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer) + { + if (overlayContainer.Parent != null) + throw new ArgumentException($@"Overlays registered via {nameof(RegisterBlockingOverlay)} should not be added to the scene graph."); + + if (externalOverlays.Contains(overlayContainer)) + throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(RegisterBlockingOverlay)} once."); + + externalOverlays.Add(overlayContainer); + overlayContent.Add(overlayContainer); + return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer)); + } + + /// + /// Should be called when has been shown and should begin blocking background input. + /// + internal void ShowBlockingOverlay(OverlayContainer overlay) { if (!visibleBlockingOverlays.Contains(overlay)) visibleBlockingOverlays.Add(overlay); updateBlockingOverlayFade(); } - public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() => + /// + /// Should be called when a blocking has been hidden and should stop blocking background input. + /// + internal void HideBlockingOverlay(OverlayContainer overlay) => Schedule(() => { visibleBlockingOverlays.Remove(overlay); updateBlockingOverlayFade(); }); + /// + /// Unregisters a blocking that was not created by itself. + /// + private void unregisterBlockingOverlay(OverlayContainer overlayContainer) + { + externalOverlays.Remove(overlayContainer); + overlayContainer.Expire(); + } + /// /// Close all game-wide overlays. /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 3c46433b45..448e702b96 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -35,6 +35,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using System.Diagnostics; +using JetBrains.Annotations; using osu.Game.Screens.Play; using osu.Game.Database; using osu.Game.Skinning; @@ -116,9 +117,15 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; + [CanBeNull] + private IDisposable modSelectOverlayRegistration; + [Resolved] private MusicController music { get; set; } + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) { @@ -264,10 +271,13 @@ namespace osu.Game.Screens.Select } }, Footer = new Footer(), - ModSelect = CreateModSelectOverlay() }); } + // preload the mod select overlay for later use in `LoadComplete()`. + // therein it will be registered at the `OsuGame` level to properly function as a blocking overlay. + LoadComponent(ModSelect = CreateModSelectOverlay()); + if (Footer != null) { foreach (var (button, overlay) in CreateFooterButtons()) @@ -301,6 +311,13 @@ namespace osu.Game.Screens.Select } } + protected override void LoadComplete() + { + base.LoadComplete(); + + modSelectOverlayRegistration = game?.RegisterBlockingOverlay(ModSelect); + } + /// /// Creates the buttons to be displayed in the footer. /// @@ -700,6 +717,8 @@ namespace osu.Game.Screens.Select if (music != null) music.TrackChanged -= ensureTrackLooping; + + modSelectOverlayRegistration?.Dispose(); } /// From ac08498f35f8db294d26dfd0dba91df914cf492d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 May 2022 22:17:40 +0200 Subject: [PATCH 043/126] Use plum colour scheme in multiplayer to match rest of multiplayer screens --- .../UserInterface/TestSceneShearedOverlayContainer.cs | 5 ++++- osu.Game/Overlays/FirstRunSetupOverlay.cs | 7 +++++-- osu.Game/Overlays/Mods/ModSelectScreen.cs | 7 +++++-- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 6 ++---- osu.Game/Overlays/Mods/UserModSelectScreen.cs | 5 +++++ osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs | 2 ++ osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 3 ++- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs index 5a9cafde27..4dd64c6536 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs @@ -66,7 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestShearedOverlayContainer : ShearedOverlayContainer { - protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green; + public TestShearedOverlayContainer() + : base(OverlayColourScheme.Green) + { + } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 75778e6c4d..607bef76dd 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -31,8 +31,6 @@ namespace osu.Game.Overlays [Cached] public class FirstRunSetupOverlay : ShearedOverlayContainer { - protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Purple; - [Resolved] private IPerformFromScreenRunner performer { get; set; } = null!; @@ -70,6 +68,11 @@ namespace osu.Game.Overlays private Container content = null!; + public FirstRunSetupOverlay() + : base(OverlayColourScheme.Purple) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index a7f8a167f9..bf47e4351a 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Mods { public abstract class ModSelectScreen : ShearedOverlayContainer { - protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green; - [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); @@ -65,6 +63,11 @@ namespace osu.Game.Overlays.Mods private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; + protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green) + : base(colourScheme) + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index eca192c8e5..92e88bfaaf 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -51,17 +51,15 @@ namespace osu.Game.Overlays.Mods /// protected Container FooterContent { get; private set; } - protected abstract OverlayColourScheme ColourScheme { get; } - protected override bool StartHidden => true; protected override bool BlockNonPositionalInput => true; - protected ShearedOverlayContainer() + protected ShearedOverlayContainer(OverlayColourScheme colourScheme) { RelativeSizeAxes = Axes.Both; - ColourProvider = new OverlayColourProvider(ColourScheme); + ColourProvider = new OverlayColourProvider(colourScheme); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Mods/UserModSelectScreen.cs b/osu.Game/Overlays/Mods/UserModSelectScreen.cs index ca33d35605..a018797cba 100644 --- a/osu.Game/Overlays/Mods/UserModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/UserModSelectScreen.cs @@ -12,6 +12,11 @@ namespace osu.Game.Overlays.Mods { public class UserModSelectScreen : ModSelectScreen { + public UserModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green) + : base(colourScheme) + { + } + protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys); protected override IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 5a7a60b479..63b7d1dbb5 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osuTK.Input; @@ -20,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay } public FreeModSelectScreen() + : base(OverlayColourScheme.Plum) { IsValidMod = _ => true; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 0eeb32230d..d8474ef86a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -153,7 +154,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(e); } - protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen + protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum) { IsValidMod = IsValidMod }; From 4eefbd5bc21af4b34fd374fd255b29dacca3d195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 May 2022 22:25:13 +0200 Subject: [PATCH 044/126] Use new free mod select design in room creation flow --- .../Screens/OnlinePlay/OnlinePlaySongSelect.cs | 17 ++++++++++++++--- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index d8474ef86a..659084addf 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -46,7 +46,6 @@ namespace osu.Game.Screens.OnlinePlay protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); - private readonly FreeModSelectOverlay freeModSelectOverlay; private readonly Room room; private WorkingBeatmap initialBeatmap; @@ -54,13 +53,16 @@ namespace osu.Game.Screens.OnlinePlay private IReadOnlyList initialMods; private bool itemSelected; + private FreeModSelectScreen freeModSelectOverlay; + private IDisposable freeModSelectOverlayRegistration; + protected OnlinePlaySongSelect(Room room) { this.room = room; Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectOverlay + freeModSelectOverlay = new FreeModSelectScreen { SelectedMods = { BindTarget = FreeMods }, IsValidMod = IsValidFreeMod, @@ -76,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - FooterPanels.Add(freeModSelectOverlay); + LoadComponent(freeModSelectOverlay); } protected override void LoadComplete() @@ -95,6 +97,8 @@ namespace osu.Game.Screens.OnlinePlay Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); + + freeModSelectOverlayRegistration = Game?.RegisterBlockingOverlay(freeModSelectOverlay); } private void onModsChanged(ValueChangedEvent> mods) @@ -183,5 +187,12 @@ namespace osu.Game.Screens.OnlinePlay private bool checkCompatibleFreeMod(Mod mod) => Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods. && ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods. + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + freeModSelectOverlayRegistration?.Dispose(); + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 448e702b96..55d28ec8a4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Select private MusicController music { get; set; } [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } + protected new OsuGame Game { get; private set; } [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - modSelectOverlayRegistration = game?.RegisterBlockingOverlay(ModSelect); + modSelectOverlayRegistration = Game?.RegisterBlockingOverlay(ModSelect); } /// From 66473972daafbcf0b1ec7358a21dbae001ec149f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 May 2022 22:30:20 +0200 Subject: [PATCH 045/126] Use new user mod select design in multiplayer lobby --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index cc1f842f8c..5b892e1925 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -57,6 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Match protected readonly IBindable RoomId = new Bindable(); + [Resolved] + private OsuGame game { get; set; } + [Resolved] private MusicController music { get; set; } @@ -77,7 +81,11 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - private ModSelectOverlay userModsSelectOverlay; + private ModSelectScreen userModsSelectOverlay; + + [CanBeNull] + private IDisposable userModsSelectOverlayRegistration; + private RoomSettingsOverlay settingsOverlay; private Drawable mainContent; @@ -180,11 +188,6 @@ namespace osu.Game.Screens.OnlinePlay.Match Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = userModsSelectOverlay = new UserModSelectOverlay - { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false - } }, } } @@ -227,6 +230,12 @@ namespace osu.Game.Screens.OnlinePlay.Match } } }; + + LoadComponent(userModsSelectOverlay = new UserModSelectScreen(OverlayColourScheme.Plum) + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + }); } protected override void LoadComplete() @@ -254,6 +263,8 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); + + userModsSelectOverlayRegistration = game?.RegisterBlockingOverlay(userModsSelectOverlay); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -459,5 +470,12 @@ namespace osu.Game.Screens.OnlinePlay.Match public class UserModSelectButton : PurpleTriangleButton { } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + userModsSelectOverlayRegistration?.Dispose(); + } } } From a56eab2c4757dfb3955e3eefb507c051c723f5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 15:47:10 +0200 Subject: [PATCH 046/126] Extract interface for overlay management --- .../Containers/OsuFocusedOverlayContainer.cs | 12 ++--- osu.Game/OsuGame.cs | 36 +++++---------- osu.Game/Overlays/IOverlayManager.cs | 44 +++++++++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 +-- .../OnlinePlay/OnlinePlaySongSelect.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 4 +- 6 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Overlays/IOverlayManager.cs diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0e8e481e9d..512602d120 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Graphics.Containers protected virtual bool DimMainContent => true; [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } + private IOverlayManager overlayManager { get; set; } [Resolved] private PreviewTrackManager previewTrackManager { get; set; } @@ -50,8 +50,8 @@ namespace osu.Game.Graphics.Containers protected override void LoadComplete() { - if (game != null) - OverlayActivationMode.BindTo(game.OverlayActivationMode); + if (overlayManager != null) + OverlayActivationMode.BindTo(overlayManager.OverlayActivationMode); OverlayActivationMode.BindValueChanged(mode => { @@ -127,14 +127,14 @@ namespace osu.Game.Graphics.Containers if (didChange) samplePopIn?.Play(); - if (BlockScreenWideMouse && DimMainContent) game?.ShowBlockingOverlay(this); + if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this); break; case Visibility.Hidden: if (didChange) samplePopOut?.Play(); - if (BlockScreenWideMouse) game?.HideBlockingOverlay(this); + if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this); break; } @@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Containers protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game?.HideBlockingOverlay(this); + overlayManager?.HideBlockingOverlay(this); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index da8cdde944..fcf5889a7e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -63,7 +63,7 @@ namespace osu.Game /// The full osu! experience. Builds on top of to add menus and binding logic /// for initial components that are generally retrieved via DI. /// - public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner + public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager { /// /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). @@ -184,48 +184,34 @@ namespace osu.Game SentryLogger = new SentryLogger(this); } + #region IOverlayManager + + IBindable IOverlayManager.OverlayActivationMode => OverlayActivationMode; + private void updateBlockingOverlayFade() => ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); - /// - /// Registers a blocking that was not created by itself for later use. - /// - /// - /// The goal of this method is to allow child screens, like to register their own full-screen blocking overlays - /// with background dim. - /// In those cases, for the dim to work correctly, the overlays need to be added at the `OsuGame` level directly, rather as children of the screens. - /// - /// - /// An that should be disposed of when the should be unregistered. - /// Disposing of this will automatically expire the . - /// - internal IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer) + IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer) { if (overlayContainer.Parent != null) - throw new ArgumentException($@"Overlays registered via {nameof(RegisterBlockingOverlay)} should not be added to the scene graph."); + throw new ArgumentException($@"Overlays registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} should not be added to the scene graph."); if (externalOverlays.Contains(overlayContainer)) - throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(RegisterBlockingOverlay)} once."); + throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once."); externalOverlays.Add(overlayContainer); overlayContent.Add(overlayContainer); return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer)); } - /// - /// Should be called when has been shown and should begin blocking background input. - /// - internal void ShowBlockingOverlay(OverlayContainer overlay) + void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay) { if (!visibleBlockingOverlays.Contains(overlay)) visibleBlockingOverlays.Add(overlay); updateBlockingOverlayFade(); } - /// - /// Should be called when a blocking has been hidden and should stop blocking background input. - /// - internal void HideBlockingOverlay(OverlayContainer overlay) => Schedule(() => + void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay) => Schedule(() => { visibleBlockingOverlays.Remove(overlay); updateBlockingOverlayFade(); @@ -240,6 +226,8 @@ namespace osu.Game overlayContainer.Expire(); } + #endregion + /// /// Close all game-wide overlays. /// diff --git a/osu.Game/Overlays/IOverlayManager.cs b/osu.Game/Overlays/IOverlayManager.cs new file mode 100644 index 0000000000..940ee2d8db --- /dev/null +++ b/osu.Game/Overlays/IOverlayManager.cs @@ -0,0 +1,44 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Select; + +namespace osu.Game.Overlays +{ + [Cached] + internal interface IOverlayManager + { + /// + /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. + /// + IBindable OverlayActivationMode { get; } + + /// + /// Registers a blocking that was not created by itself for later use. + /// + /// + /// The goal of this method is to allow child screens, like to register their own full-screen blocking overlays + /// with background dim. + /// In those cases, for the dim to work correctly, the overlays need to be added at a game level directly, rather as children of the screens. + /// + /// + /// An that should be disposed of when the should be unregistered. + /// Disposing of this will automatically expire the . + /// + IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer); + + /// + /// Should be called when has been shown and should begin blocking background input. + /// + void ShowBlockingOverlay(OverlayContainer overlay); + + /// + /// Should be called when a blocking has been hidden and should stop blocking background input. + /// + void HideBlockingOverlay(OverlayContainer overlay); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 5b892e1925..b7a9b45bb8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -58,8 +58,8 @@ namespace osu.Game.Screens.OnlinePlay.Match protected readonly IBindable RoomId = new Bindable(); - [Resolved] - private OsuGame game { get; set; } + [Resolved(CanBeNull = true)] + private IOverlayManager overlayManager { get; set; } [Resolved] private MusicController music { get; set; } @@ -264,7 +264,7 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); - userModsSelectOverlayRegistration = game?.RegisterBlockingOverlay(userModsSelectOverlay); + userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 659084addf..c262544d6c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay private IReadOnlyList initialMods; private bool itemSelected; - private FreeModSelectScreen freeModSelectOverlay; + private readonly FreeModSelectScreen freeModSelectOverlay; private IDisposable freeModSelectOverlayRegistration; protected OnlinePlaySongSelect(Room room) @@ -98,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); - freeModSelectOverlayRegistration = Game?.RegisterBlockingOverlay(freeModSelectOverlay); + freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelectOverlay); } private void onModsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 55d28ec8a4..bb2746ee95 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Select private MusicController music { get; set; } [Resolved(CanBeNull = true)] - protected new OsuGame Game { get; private set; } + internal IOverlayManager OverlayManager { get; private set; } [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - modSelectOverlayRegistration = Game?.RegisterBlockingOverlay(ModSelect); + modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); } /// From fdb21fedaba887647523f0c25fdad6c3bc0775d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 16:31:02 +0200 Subject: [PATCH 047/126] Implement `IOverlayManager` in `ScreenTestScene` --- osu.Game/Tests/Visual/ScreenTestScene.cs | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index e9069d8c9c..0fa2f3e786 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -1,12 +1,15 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Testing; +using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Screens; @@ -15,11 +18,12 @@ namespace osu.Game.Tests.Visual /// /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). /// - public abstract class ScreenTestScene : OsuManualInputManagerTestScene + public abstract class ScreenTestScene : OsuManualInputManagerTestScene, IOverlayManager { protected readonly OsuScreenStack Stack; private readonly Container content; + private readonly Container overlayContent; protected override Container Content => content; @@ -36,7 +40,11 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both }, content = new Container { RelativeSizeAxes = Axes.Both }, - DialogOverlay = new DialogOverlay() + overlayContent = new Container + { + RelativeSizeAxes = Axes.Both, + Child = DialogOverlay = new DialogOverlay() + } }); Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); @@ -65,5 +73,26 @@ namespace osu.Game.Tests.Visual return false; }); } + + #region IOverlayManager + + IBindable IOverlayManager.OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); + + // in the blocking methods below it is important to be careful about threading (e.g. use `Expire()` rather than `Remove()`, and schedule transforms), + // because in the worst case the clean-up methods could be called from async disposal. + + IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer) + { + overlayContent.Add(overlayContainer); + return new InvokeOnDisposal(() => overlayContainer.Expire()); + } + + void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay) + => Schedule(() => Stack.FadeColour(OsuColour.Gray(0.5f), 500, Easing.OutQuint)); + + void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay) + => Schedule(() => Stack.FadeColour(Colour4.White, 500, Easing.OutQuint)); + + #endregion } } From 0caea7717641ad85849219257ea563e2ad8e53f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 17:27:21 +0200 Subject: [PATCH 048/126] Fix mod selects not hiding when their owner screens exit --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 10 ++++++++-- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 ++ osu.Game/Screens/Select/SongSelect.cs | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index b7a9b45bb8..ec4e329f7a 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -309,7 +309,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnSuspending(ScreenTransitionEvent e) { - endHandlingTrack(); + onLeaving(); base.OnSuspending(e); } @@ -327,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Match RoomManager?.PartRoom(); Mods.Value = Array.Empty(); - endHandlingTrack(); + onLeaving(); return base.OnExiting(e); } @@ -423,6 +423,12 @@ namespace osu.Game.Screens.OnlinePlay.Match Beatmap.BindValueChanged(applyLoopingToTrack, true); } + private void onLeaving() + { + userModsSelectOverlay.Hide(); + endHandlingTrack(); + } + private void endHandlingTrack() { Beatmap.ValueChanged -= applyLoopingToTrack; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index c262544d6c..c4503773ad 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -155,6 +155,8 @@ namespace osu.Game.Screens.OnlinePlay Mods.Value = initialMods; } + freeModSelectOverlay.Hide(); + return base.OnExiting(e); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bb2746ee95..eb5e996972 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -659,6 +659,7 @@ namespace osu.Game.Screens.Select return true; beatmapInfoWedge.Hide(); + ModSelect.Hide(); this.FadeOut(100); From 8f65e0e60f4931f736e6b342e18b6a67e1ecbb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:08:19 +0200 Subject: [PATCH 049/126] Add failing test coverage for toggling mod overlay with hotkey --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d27f16a624..083e24be7b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -18,6 +19,7 @@ using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -918,6 +920,19 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); } + [Test] + public void TestModOverlayToggling() + { + changeRuleset(0); + createSongSelect(); + + AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1)); + AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible); + + AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1)); + AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden); + } + private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); @@ -993,6 +1008,7 @@ namespace osu.Game.Tests.Visual.SongSelect public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + public new ModSelectScreen ModSelect => base.ModSelect; public new void PresentScore(ScoreInfo score) => base.PresentScore(score); From 34cf4c6a3895557d6dc5f1d07b702857b512ccca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:22:07 +0200 Subject: [PATCH 050/126] Fix mod overlay not closing on toggle hotkey --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 21 +++++++++++++++++++ .../OnlinePlay/OnlinePlaySongSelect.cs | 2 ++ osu.Game/Screens/Select/SongSelect.cs | 5 ++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index bf47e4351a..7c0ed941c6 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select; using osuTK; using osuTK.Input; @@ -44,6 +45,15 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Hotkey that should be used to hide the mod select screen. + /// + /// + /// This is handled locally here rather than via , because this overlay is being registered at the game level + /// and therefore takes away keyboard focus from the screen stack. + /// + internal GlobalAction? Hotkey { get; set; } + /// /// Whether configurable s can be configured by the local user. /// @@ -321,12 +331,23 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (e.Action == GlobalAction.Back && customisationVisible.Value) { customisationVisible.Value = false; return true; } + if (e.Action == Hotkey) + { + if (customisationVisible.Value) + customisationVisible.Value = false; + Hide(); + return true; + } + return base.OnPressed(e); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index c4503773ad..4b6c06481b 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -162,6 +163,7 @@ namespace osu.Game.Screens.OnlinePlay protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum) { + Hotkey = GlobalAction.ToggleModSelection, IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eb5e996972..7317b20f88 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -333,7 +333,10 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(); + protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen + { + Hotkey = GlobalAction.ToggleModSelection + }; protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 17c19063f7945ad3d40287f471875c0a109835f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:24:11 +0200 Subject: [PATCH 051/126] Fix user dim test scene accumulating no fail instances in `SelectedMods` The old design was apparently somehow tolerant of multiple instances of the same mod in `SelectedMods`, but the new one is not. Fix the test, because appending to a list of mods that was getting modified by previous tests is generally not how tests should be written. --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index f7140537ee..ef115ab66b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; @@ -283,7 +282,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null); AddStep("Set default user settings", () => { - SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); + SelectedMods.Value = new[] { new OsuModNoFail() }; songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); From 199bdb8e7ded820c6a0b1663ef307d8564f9a831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:43:39 +0200 Subject: [PATCH 052/126] Replace reference to old mod select in multiplayer test --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 6a69917fb4..8e26d1c562 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -622,7 +622,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); From ecc28050992e69ec8a1068ede63d8cd313623680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:47:03 +0200 Subject: [PATCH 053/126] Replace references to old mod select classes in match song select test Also note the change from `songSelect.ChildrenOfType<>()...` to `this.ChildrenOfType<>()...` - because the new design is registered at game-level, the mod select is not a child of the song select screen anymore. --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 714951cc42..d170b12dc3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertHasFreeModButton(Type type, bool hasButton = true) { AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", - () => songSelect.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type)); + () => this.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type)); } private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect From d296f78dfe097fdf70c759a416538ef7a1fa3fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 21:59:44 +0200 Subject: [PATCH 054/126] Replace references to old mod select classes in multiplayer match screen test --- .../Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 057032c413..2abde82e92 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -168,8 +168,13 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); + AddUntilStep("mod select contents loaded", + () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); AddUntilStep("mod select contains only double time mod", - () => this.ChildrenOfType().SingleOrDefault()?.ChildrenOfType().SingleOrDefault()?.Mod is OsuModDoubleTime); + () => this.ChildrenOfType() + .SingleOrDefault()? + .ChildrenOfType() + .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } } } From a2ab79620a2a7dd63260a23951b9b2d572046d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 May 2022 22:15:49 +0200 Subject: [PATCH 055/126] Update song select exit via click test to match new expectations --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8ba074a7d1..f8eee7be56 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -253,12 +253,12 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); - AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); - // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. - AddUntilStep("Back button is hovered", () => Game.ChildrenOfType().First().Children.Any(c => c.IsHovered)); + AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2( + songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1, + songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2))); + AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left)); - AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); exitViaBackButtonAndConfirm(); } From 69592722f80f0869a1ddbe42fbe76564c6252c5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 May 2022 18:25:02 +0900 Subject: [PATCH 056/126] Highlight distance snap grid rings that are close to the current time value --- .../Components/CircularDistanceSnapGrid.cs | 45 +++++++++++++++++-- .../Compose/Components/DistanceSnapGrid.cs | 7 +-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 91fad08aff..5682b5563e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -2,11 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { @@ -51,14 +55,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { float diameter = (i + 1) * DistanceBetweenTicks * 2; - AddInternal(new CircularProgress + AddInternal(new Ring(ReferenceObject, GetColourForIndexFromPlacement(i)) { - Origin = Anchor.Centre, Position = StartPosition, - Current = { Value = 1 }, + Origin = Anchor.Centre, Size = new Vector2(diameter), InnerRadius = 4 * 1f / diameter, - Colour = GetColourForIndexFromPlacement(i) }); } } @@ -100,5 +102,40 @@ namespace osu.Game.Screens.Edit.Compose.Components return (snappedPosition, snappedTime); } + + public class Ring : CircularProgress + { + [Resolved] + private IDistanceSnapProvider snapProvider { get; set; } + + [Resolved(canBeNull: true)] + private EditorClock editorClock { get; set; } + + private readonly HitObject referenceObject; + + private readonly Color4 baseColour; + + public Ring(HitObject referenceObject, Color4 baseColour) + { + this.referenceObject = referenceObject; + + Colour = this.baseColour = baseColour; + + Current.Value = 1; + } + + protected override void Update() + { + base.Update(); + + if (editorClock == null) + return; + + float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, editorClock.CurrentTime - referenceObject.StartTime); + float timeBasedAlpha = Math.Clamp(1 - Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); + + Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha)); + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 2f39db06d4..1f64a50c02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -4,14 +4,15 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { @@ -135,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The 0-based beat index from the point of placement. /// The applicable colour. - protected ColourInfo GetColourForIndexFromPlacement(int placementIndex) + protected Color4 GetColourForIndexFromPlacement(int placementIndex) { var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); double beatLength = timingPoint.BeatLength / beatDivisor.Value; @@ -144,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); int repeatIndex = placementIndex / beatDivisor.Value; - return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1)); + return colour.Opacity(0.5f / (repeatIndex + 1)); } } } From 112496204d26f8709428b9eab026969953330910 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 May 2022 18:50:28 +0300 Subject: [PATCH 057/126] Make nested class private --- .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 5682b5563e..b3df72f827 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return (snappedPosition, snappedTime); } - public class Ring : CircularProgress + private class Ring : CircularProgress { [Resolved] private IDistanceSnapProvider snapProvider { get; set; } From d78f1d158d3f5582fc99efd20a92c48e5e73331d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 May 2022 18:51:34 +0300 Subject: [PATCH 058/126] Use `GetEndTime()` instead of `StartTime` Companion to https://github.com/ppy/osu/pull/18103/commits/246479bf34c725d886742017099f9d509e58b2f7 --- .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index b3df72f827..6cb05f0596 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (editorClock == null) return; - float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, editorClock.CurrentTime - referenceObject.StartTime); + float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, editorClock.CurrentTime - referenceObject.GetEndTime()); float timeBasedAlpha = Math.Clamp(1 - Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha)); From 723fce81741de8b858edb0a60090b850fc18f67a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 May 2022 18:54:08 +0300 Subject: [PATCH 059/126] Move inverting value to happen outside clamp Looks to have no effect, but makes sense to happen outside instead. --- .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 6cb05f0596..c0a3f7d97f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return; float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, editorClock.CurrentTime - referenceObject.GetEndTime()); - float timeBasedAlpha = Math.Clamp(1 - Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); + float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha)); } From cc251ed5c3fe4a50126817d8893fba4a4a591536 Mon Sep 17 00:00:00 2001 From: Hugo Denizart Date: Fri, 6 May 2022 19:52:25 +0200 Subject: [PATCH 060/126] =?UTF-8?q?=F0=9F=94=A7=20Update=20Sentry=20DSN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osu.Game/Utils/SentryLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index dbf04283b6..d9c8199f75 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -24,7 +24,7 @@ namespace osu.Game.Utils var options = new SentryOptions { - Dsn = "https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255", + Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2", Release = game.Version }; From 049fed42e2084497b6766d7af736026fe77b96b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 May 2022 21:42:20 +0300 Subject: [PATCH 061/126] Fix side overlay offsetting not affecting fullscreen overlays --- osu.Game/OsuGame.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e9fe8c43de..7f47c15f68 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1153,6 +1153,7 @@ namespace osu.Game horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO; ScreenOffsetContainer.X = horizontalOffset; + overlayContent.X = horizontalOffset; MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } From 8080f784fd5f9a3e202d7f27808b8a08ac67835b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 May 2022 22:20:04 +0300 Subject: [PATCH 062/126] Apply offset by a factor of 0.8x from screen offset --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7f47c15f68..b3de23ee08 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1153,7 +1153,7 @@ namespace osu.Game horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO; ScreenOffsetContainer.X = horizontalOffset; - overlayContent.X = horizontalOffset; + overlayContent.X = horizontalOffset * 0.8f; MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } From 380cd1e03619e45ad580356342f99498c0f59592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 21:35:22 +0200 Subject: [PATCH 063/126] Add test coverage for lack of customisation on free mod select --- .../TestSceneFreeModSelectScreen.cs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs index b5f901e51d..8eaa45696f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs @@ -6,24 +6,19 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeModSelectScreen : MultiplayerTestScene { + private FreeModSelectScreen freeModSelectScreen; + [Test] public void TestFreeModSelect() { - FreeModSelectScreen freeModSelectScreen = null; - - AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen - { - State = { Value = Visibility.Visible } - }); - AddUntilStep("all column content loaded", - () => freeModSelectScreen.ChildrenOfType().Any() - && freeModSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + createFreeModSelect(); AddUntilStep("all visible mods are playable", () => this.ChildrenOfType() @@ -36,5 +31,26 @@ namespace osu.Game.Tests.Visual.Multiplayer freeModSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden; }); } + + [Test] + public void TestCustomisationNotAvailable() + { + createFreeModSelect(); + + AddStep("select difficulty adjust", () => freeModSelectScreen.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); + AddWaitStep("wait some", 3); + AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); + } + + private void createFreeModSelect() + { + AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen + { + State = { Value = Visibility.Visible } + }); + AddUntilStep("all column content loaded", + () => freeModSelectScreen.ChildrenOfType().Any() + && freeModSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + } } } From c199b8fcb66fade4234b6e24597d42f4afccc442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 21:35:36 +0200 Subject: [PATCH 064/126] Simplify state management in `ModColumn` Bad sign when you can't follow your own code. All of the various state changing methods were flattened into one because it was too hard to follow what was calling what and why. --- osu.Game/Overlays/Mods/ModColumn.cs | 43 ++++++++++------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index a792c0a81e..270839112a 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Mods set { filter = value; - updateFilter(); + updateState(); } } @@ -292,9 +292,7 @@ namespace osu.Game.Overlays.Mods { panelFlow.ChildrenEnumerable = loaded; - updateActiveState(); - updateToggleAllState(); - updateFilter(); + updateState(); foreach (var panel in panelFlow) { @@ -308,10 +306,19 @@ namespace osu.Game.Overlays.Mods }); } - private void updateActiveState() + private void updateState() { foreach (var panel in panelFlow) + { panel.Active.Value = SelectedMods.Contains(panel.Mod); + panel.ApplyFilter(Filter); + } + + if (toggleAllCheckbox != null && !SelectionAnimationRunning) + { + toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0; + toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); + } } /// @@ -323,13 +330,12 @@ namespace osu.Game.Overlays.Mods private void panelStateChanged(ModPanel panel) { - updateToggleAllState(); - var newSelectedMods = panel.Active.Value ? SelectedMods.Append(panel.Mod) : SelectedMods.Except(panel.Mod.Yield()); SelectedMods = newSelectedMods.ToArray(); + updateState(); if (!externalSelectionUpdateInProgress) SelectionChangedByUser?.Invoke(); } @@ -364,7 +370,7 @@ namespace osu.Game.Overlays.Mods } SelectedMods = newSelection; - updateActiveState(); + updateState(); externalSelectionUpdateInProgress = false; } @@ -403,15 +409,6 @@ namespace osu.Game.Overlays.Mods } } - private void updateToggleAllState() - { - if (toggleAllCheckbox != null && !SelectionAnimationRunning) - { - toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0; - toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); - } - } - /// /// Selects all mods. /// @@ -507,18 +504,6 @@ namespace osu.Game.Overlays.Mods #endregion - #region Filtering support - - private void updateFilter() - { - foreach (var modPanel in panelFlow) - modPanel.ApplyFilter(Filter); - - updateToggleAllState(); - } - - #endregion - #region Keyboard selection support protected override bool OnKeyDown(KeyDownEvent e) From 621f7467898e290bdd2d9d9fe3cc4daadd209366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 21:46:56 +0200 Subject: [PATCH 065/126] Do not modify selected mods through panel state change during external update --- osu.Game/Overlays/Mods/ModColumn.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 270839112a..f6fa591666 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -330,14 +330,16 @@ namespace osu.Game.Overlays.Mods private void panelStateChanged(ModPanel panel) { + if (externalSelectionUpdateInProgress) + return; + var newSelectedMods = panel.Active.Value ? SelectedMods.Append(panel.Mod) : SelectedMods.Except(panel.Mod.Yield()); SelectedMods = newSelectedMods.ToArray(); updateState(); - if (!externalSelectionUpdateInProgress) - SelectionChangedByUser?.Invoke(); + SelectionChangedByUser?.Invoke(); } /// From 8c73ed72078a47eb7f166164cb80e642b5486218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 21:38:29 +0200 Subject: [PATCH 066/126] Fix sequence equality check not using reference comparison --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index a1b4d7d8a0..838c3597ae 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Layout; +using osu.Framework.Lists; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; @@ -264,7 +265,9 @@ namespace osu.Game.Overlays.Mods { var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray(); - if (candidateSelection.SequenceEqual(SelectedMods.Value)) + // the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion. + // TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6 + if (candidateSelection.SequenceEqual(SelectedMods.Value, new FuncEqualityComparer(ReferenceEquals))) return; SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); From 71758390b5db8186bd149647d162417e5607e730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 May 2022 13:37:36 +0900 Subject: [PATCH 067/126] Fix time based distance grid alpha not correctly accounting for distance spacing multiplier --- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index c0a3f7d97f..2c6bb766ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -131,7 +131,12 @@ namespace osu.Game.Screens.Edit.Compose.Components if (editorClock == null) return; - float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, editorClock.CurrentTime - referenceObject.GetEndTime()); + float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value; + double timeFromReferencePoint = editorClock.CurrentTime - referenceObject.GetEndTime(); + + float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, timeFromReferencePoint) + * distanceSpacingMultiplier; + float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha)); From ce14fddcb2c2950285dd89d96b8587cd9c3fbf6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 May 2022 14:02:07 +0900 Subject: [PATCH 068/126] Use above-1 offset instead for more correct feeling parallax --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b3de23ee08..69adca4190 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1153,7 +1153,7 @@ namespace osu.Game horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO; ScreenOffsetContainer.X = horizontalOffset; - overlayContent.X = horizontalOffset * 0.8f; + overlayContent.X = horizontalOffset * 1.2f; MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } From 0c6de331f3075fe701210031271d79bd4cdeef8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 15:59:19 +0200 Subject: [PATCH 069/126] Move footer button padding to a higher level --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 838c3597ae..06baea8364 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -145,13 +145,18 @@ namespace osu.Game.Overlays.Mods }); } + FooterContent.Padding = new MarginPadding + { + Vertical = PADDING, + Horizontal = 70 + }; + if (AllowCustomisation) { - Footer.Add(new ShearedToggleButton(200) + FooterContent.Add(new ShearedToggleButton(200) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Vertical = PADDING, Left = 70 }, Text = "Mod Customisation", Active = { BindTarget = customisationVisible } }); From 0b95594f60f8afeab0b3927944e4f28e66b8eb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 16:02:32 +0200 Subject: [PATCH 070/126] Add some more padding between footer and column scroll --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 06baea8364..75f9f4e72e 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -95,6 +95,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING, + Bottom = PADDING }, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, From 852e4a97668421ad78b258d6366204ebb8cdd858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 16:31:59 +0200 Subject: [PATCH 071/126] Add select/deselect all buttons to free mod select screen --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 61 +++++++++++++------ .../Screens/OnlinePlay/FreeModSelectScreen.cs | 21 ++++++- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 75f9f4e72e..419a6ee7f7 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -47,11 +47,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Whether configurable s can be configured by the local user. - /// - protected virtual bool AllowCustomisation => true; - /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. /// @@ -59,12 +54,27 @@ namespace osu.Game.Overlays.Mods protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); + protected virtual Drawable[] CreateFooterButtons() => new Drawable[] + { + customisationButton = new ShearedToggleButton(200) + { + Text = "Mod Customisation", + Active = { BindTarget = customisationVisible } + }, + new ShearedButton(200) + { + Text = "Deselect All", + Action = DeselectAll + } + }; + private readonly BindableBool customisationVisible = new BindableBool(); private DifficultyMultiplierDisplay? multiplierDisplay; private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; + private ShearedToggleButton? customisationButton; [BackgroundDependencyLoader] private void load() @@ -146,22 +156,21 @@ namespace osu.Game.Overlays.Mods }); } - FooterContent.Padding = new MarginPadding + FooterContent.Child = new FillFlowContainer { - Vertical = PADDING, - Horizontal = 70 - }; - - if (AllowCustomisation) - { - FooterContent.Add(new ShearedToggleButton(200) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = "Mod Customisation", - Active = { BindTarget = customisationVisible } - }); - } + Vertical = PADDING, + Horizontal = 70 + }, + Spacing = new Vector2(10), + Children = CreateFooterButtons() + }; } private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) @@ -216,7 +225,7 @@ namespace osu.Game.Overlays.Mods private void updateCustomisation(ValueChangedEvent> valueChangedEvent) { - if (!AllowCustomisation) + if (customisationButton == null) return; bool anyCustomisableMod = false; @@ -325,6 +334,18 @@ namespace osu.Game.Overlays.Mods } } + protected void SelectAll() + { + foreach (var column in columnFlow.Columns) + column.SelectAll(); + } + + protected void DeselectAll() + { + foreach (var column in columnFlow.Columns) + column.DeselectAll(); + } + public override bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.Back && customisationVisible.Value) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 5a7a60b479..52ca28ce5d 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osuTK.Input; @@ -10,7 +12,6 @@ namespace osu.Game.Screens.OnlinePlay { public class FreeModSelectScreen : ModSelectScreen { - protected override bool AllowCustomisation => false; protected override bool ShowTotalMultiplier => false; public new Func IsValidMod @@ -25,5 +26,23 @@ namespace osu.Game.Screens.OnlinePlay } protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); + + protected override Drawable[] CreateFooterButtons() => new Drawable[] + { + new ShearedButton(200) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Select All", + Action = SelectAll + }, + new ShearedButton(200) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Deselect All", + Action = DeselectAll + } + }; } } From 35c106efaab09a067c7b456aabfda8ec7e24fdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 20:54:36 +0200 Subject: [PATCH 072/126] Add test coverage for deselecting all mods on user mod select --- .../UserInterface/TestSceneModSelectScreen.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index c92a738b56..9e0c8206b7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -415,6 +415,23 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value); } + [Test] + public void TestDeselectAllViaButton() + { + createScreen(); + changeRuleset(0); + + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); + AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + + AddStep("click deselect all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From e1953c484c0ae08bb4846a1243bf4c3283640ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 16:43:24 +0200 Subject: [PATCH 073/126] Add test coverage for selecting/deselecting all mods on free mod select --- .../TestSceneFreeModSelectScreen.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs index 8eaa45696f..a02055d960 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs @@ -1,19 +1,32 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeModSelectScreen : MultiplayerTestScene { private FreeModSelectScreen freeModSelectScreen; + private readonly Bindable>> availableMods = new Bindable>>(); + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGameBase) + { + availableMods.BindTo(osuGameBase.AvailableMods); + } [Test] public void TestFreeModSelect() @@ -42,6 +55,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); } + [Test] + public void TestSelectDeselectAll() + { + createFreeModSelect(); + + AddStep("click select all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods selected", assertAllAvailableModsSelected); + + AddStep("click deselect all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods deselected", () => !freeModSelectScreen.SelectedMods.Value.Any()); + } + private void createFreeModSelect() { AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen @@ -52,5 +85,21 @@ namespace osu.Game.Tests.Visual.Multiplayer () => freeModSelectScreen.ChildrenOfType().Any() && freeModSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); } + + private bool assertAllAvailableModsSelected() + { + var allAvailableMods = availableMods.Value + .SelectMany(pair => pair.Value) + .Where(mod => mod.UserPlayable && mod.HasImplementation) + .ToList(); + + foreach (var availableMod in allAvailableMods) + { + if (freeModSelectScreen.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType())) + return false; + } + + return true; + } } } From 9f96dd47d12db4a4df8ebfa9268eec86907fb886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 16:48:01 +0200 Subject: [PATCH 074/126] Remove schedule in panel load It was causing selection/deselection of all mods to work improperly if a select/deselect all operation was requested before the panel was scrolled into view. In general the schedule was an over-optimisation - the game-global set of available mods shouldn't be changing so often as to warrant such an aggressive debounce. --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index f6fa591666..7f1d7e0541 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -275,7 +275,7 @@ namespace osu.Game.Overlays.Mods return; localAvailableMods = newMods; - Scheduler.AddOnce(loadPanels); + loadPanels(); } private CancellationTokenSource? cancellationTokenSource; From 9514a5cef7a6b291056697f1305fa159903103a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 17:33:32 +0200 Subject: [PATCH 075/126] Only load panels asynchronously outside of BDL --- osu.Game/Overlays/Mods/ModColumn.cs | 41 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 7f1d7e0541..6297b2d92e 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -275,30 +275,25 @@ namespace osu.Game.Overlays.Mods return; localAvailableMods = newMods; - loadPanels(); + + if (!IsLoaded) + // if we're coming from BDL, perform the first load synchronously to make sure everything is in place as early as possible. + onPanelsLoaded(createPanels()); + else + asyncLoadPanels(); } private CancellationTokenSource? cancellationTokenSource; - private void loadPanels() + private void asyncLoadPanels() { cancellationTokenSource?.Cancel(); - var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); + var panels = createPanels(); Task? loadTask; - latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => - { - panelFlow.ChildrenEnumerable = loaded; - - updateState(); - - foreach (var panel in panelFlow) - { - panel.Active.BindValueChanged(_ => panelStateChanged(panel)); - } - }, (cancellationTokenSource = new CancellationTokenSource()).Token); + latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => { if (loadTask == latestLoadTask) @@ -306,6 +301,24 @@ namespace osu.Game.Overlays.Mods }); } + private IEnumerable createPanels() + { + var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); + return panels; + } + + private void onPanelsLoaded(IEnumerable loaded) + { + panelFlow.ChildrenEnumerable = loaded; + + updateState(); + + foreach (var panel in panelFlow) + { + panel.Active.BindValueChanged(_ => panelStateChanged(panel)); + } + } + private void updateState() { foreach (var panel in panelFlow) From 18e4c3ed0f6659ff7c3dc3b5bb089a7c09c04b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 18:08:11 +0200 Subject: [PATCH 076/126] Update mod columns even if they're not present/offscreen Important to make "select/deselect all" operations work on all columns simultaneously, even if they're offscreen. Unfortunately by the nature of how the existing selection animation works, it is hard-tied to the update loop, so we need to compensate. --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 6297b2d92e..07871663a6 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -399,7 +399,7 @@ namespace osu.Game.Overlays.Mods private readonly Queue pendingSelectionOperations = new Queue(); - protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; + internal bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; protected override void Update() { diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 419a6ee7f7..5ffe07f34a 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -454,6 +454,8 @@ namespace osu.Game.Overlays.Mods FinishTransforms(); } + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning; + private void updateDim() { Colour4 targetColour; From 4ff96f82be57186874089d942beb35ee4766a015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 22:36:08 +0200 Subject: [PATCH 077/126] Dim other buttons if customisation panel is open --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 13 ++++++++++--- osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 5ffe07f34a..e5b4927a6a 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Mods protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); - protected virtual Drawable[] CreateFooterButtons() => new Drawable[] + protected virtual IEnumerable CreateFooterButtons() => new[] { customisationButton = new ShearedToggleButton(200) { @@ -75,6 +75,7 @@ namespace osu.Game.Overlays.Mods private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; private ShearedToggleButton? customisationButton; + private FillFlowContainer footerButtonFlow = null!; [BackgroundDependencyLoader] private void load() @@ -156,7 +157,7 @@ namespace osu.Game.Overlays.Mods }); } - FooterContent.Child = new FillFlowContainer + FooterContent.Child = footerButtonFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -169,7 +170,7 @@ namespace osu.Game.Overlays.Mods Horizontal = 70 }, Spacing = new Vector2(10), - Children = CreateFooterButtons() + ChildrenEnumerable = CreateFooterButtons() }; } @@ -259,6 +260,12 @@ namespace osu.Game.Overlays.Mods MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic); + foreach (var button in footerButtonFlow) + { + if (button != customisationButton) + button.Enabled.Value = !customisationVisible.Value; + } + float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0; modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic); diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 52ca28ce5d..6298e1f8c0 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; @@ -27,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); - protected override Drawable[] CreateFooterButtons() => new Drawable[] + protected override IEnumerable CreateFooterButtons() => new[] { new ShearedButton(200) { From 6ad990dfc3a32d824c08e29ad5dc7d20410b14e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 10:17:24 +0200 Subject: [PATCH 078/126] Add basic localisation strings for new mod select --- osu.Game/Localisation/CommonStrings.cs | 10 +++++++ .../Localisation/ModSelectScreenStrings.cs | 29 +++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectScreen.cs | 9 +++--- .../Screens/OnlinePlay/FreeModSelectScreen.cs | 5 ++-- 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Localisation/ModSelectScreenStrings.cs diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 52e9811cf7..9cd626af0f 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -59,6 +59,16 @@ namespace osu.Game.Localisation /// public static LocalisableString Importing => new TranslatableString(getKey(@"importing"), @"Importing..."); + /// + /// "Deselect All" + /// + public static LocalisableString DeselectAll => new TranslatableString(getKey(@"deselect_all"), @"Deselect All"); + + /// + /// "Select All" + /// + public static LocalisableString SelectAll => new TranslatableString(getKey(@"select_all"), @"Select All"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/ModSelectScreenStrings.cs b/osu.Game/Localisation/ModSelectScreenStrings.cs new file mode 100644 index 0000000000..0c113fd381 --- /dev/null +++ b/osu.Game/Localisation/ModSelectScreenStrings.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class ModSelectScreenStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ModSelectScreen"; + + /// + /// "Mod Select" + /// + public static LocalisableString ModSelectTitle => new TranslatableString(getKey(@"mod_select_title"), @"Mod Select"); + + /// + /// "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun." + /// + public static LocalisableString ModSelectDescription => new TranslatableString(getKey(@"mod_select_description"), @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."); + + /// + /// "Mod Customisation" + /// + public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index e5b4927a6a..4a80a0637e 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -23,6 +23,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Input; +using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { @@ -58,12 +59,12 @@ namespace osu.Game.Overlays.Mods { customisationButton = new ShearedToggleButton(200) { - Text = "Mod Customisation", + Text = ModSelectScreenStrings.ModCustomisation, Active = { BindTarget = customisationVisible } }, new ShearedButton(200) { - Text = "Deselect All", + Text = CommonStrings.DeselectAll, Action = DeselectAll } }; @@ -80,8 +81,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load() { - Header.Title = "Mod Select"; - Header.Description = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."; + Header.Title = ModSelectScreenStrings.ModSelectTitle; + Header.Description = ModSelectScreenStrings.ModSelectDescription; AddRange(new Drawable[] { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 6298e1f8c0..5a7fe8a778 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osuTK.Input; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay { @@ -34,14 +35,14 @@ namespace osu.Game.Screens.OnlinePlay { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = "Select All", + Text = CommonStrings.SelectAll, Action = SelectAll }, new ShearedButton(200) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = "Deselect All", + Text = CommonStrings.DeselectAll, Action = DeselectAll } }; From 271d64fd545deaa4fb42eb6d6464b7283d55f871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 May 2022 22:30:07 +0200 Subject: [PATCH 079/126] Add back button to mod select overlays --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 4a80a0637e..2af65d89b5 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { Header.Title = ModSelectScreenStrings.ModSelectTitle; Header.Description = ModSelectScreenStrings.ModSelectDescription; @@ -171,7 +171,13 @@ namespace osu.Game.Overlays.Mods Horizontal = 70 }, Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons() + ChildrenEnumerable = CreateFooterButtons().Prepend(new ShearedButton(200) + { + Text = CommonStrings.Back, + Action = Hide, + DarkerColour = colours.Pink2, + LighterColour = colours.Pink1 + }) }; } From 1c029552d7c9308f05969bb640c7adec2f51a889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 10:23:49 +0200 Subject: [PATCH 080/126] Add test coverage for mod select back button --- .../UserInterface/TestSceneModSelectScreen.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 9e0c8206b7..42ffeba444 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -432,6 +432,25 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); } + [Test] + public void TestCloseViaBackButton() + { + createScreen(); + changeRuleset(0); + + AddStep("select difficulty adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); + assertCustomisationToggleState(disabled: false, active: true); + AddAssert("back button disabled", () => !this.ChildrenOfType().First().Enabled.Value); + + AddStep("dismiss customisation area", () => InputManager.Key(Key.Escape)); + AddStep("click back button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From 9d3e67b10dbf9b6fc27405a50023f735a95d16fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 10:25:23 +0200 Subject: [PATCH 081/126] Update free mod select/deselect all test after back button addition --- .../Visual/Multiplayer/TestSceneFreeModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs index a02055d960..4eb14542ba 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectScreen.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click select all button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.MoveMouseTo(this.ChildrenOfType().ElementAt(1)); InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods selected", assertAllAvailableModsSelected); From fa0a256f4843f4cd5c3cbfcf1737e613f2ff856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 10:48:15 +0200 Subject: [PATCH 082/126] Add localisable string for difficulty multiplier display --- .../DifficultyMultiplierDisplayStrings.cs | 19 +++++++++++++++++++ .../Mods/DifficultyMultiplierDisplay.cs | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs diff --git a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs new file mode 100644 index 0000000000..c281d90190 --- /dev/null +++ b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs @@ -0,0 +1,19 @@ +// 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; + +namespace osu.Game.Localisation +{ + public static class DifficultyMultiplierDisplayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DifficultyMultiplierDisplay"; + + /// + /// "Difficulty Multiplier" + /// + public static LocalisableString DifficultyMultiplier => new TranslatableString(getKey(@"difficulty_multiplier"), @"Difficulty Multiplier"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 1d848fe456..4ccec0dd87 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { @@ -99,7 +100,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = 18 }, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = "Difficulty Multiplier", + Text = DifficultyMultiplierDisplayStrings.DifficultyMultiplier, Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) } } From f761d4d4d4060597ff01624f513c849090b9160b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 10:56:03 +0200 Subject: [PATCH 083/126] Reuse "select/deselect all" localisable string on column toggle --- osu.Game/Overlays/Mods/ModColumn.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 07871663a6..cbd036c71f 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -22,6 +23,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; @@ -220,7 +222,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Scale = new Vector2(0.8f), RelativeSizeAxes = Axes.X, - LabelText = "Enable All", Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) }); panelFlow.Padding = new MarginPadding @@ -265,6 +266,19 @@ namespace osu.Game.Overlays.Mods contentBackground.Colour = colourProvider.Background4; } + protected override void LoadComplete() + { + base.LoadComplete(); + + toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); + } + + private void updateToggleAllText() + { + Debug.Assert(toggleAllCheckbox != null); + toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll; + } + private void updateLocalAvailableMods() { var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty()) From fd75963ca2d4ce479ce1965ae81390b45cf68da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 11:01:05 +0200 Subject: [PATCH 084/126] Dismiss mod select if select binding is pressed --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 7d0400aace..706222761d 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -384,7 +384,7 @@ namespace osu.Game.Overlays.Mods return true; } - if (e.Action == Hotkey) + if (e.Action == Hotkey || e.Action == GlobalAction.Select) { if (customisationVisible.Value) customisationVisible.Value = false; From 8ee3fdd0aaa9cd03339ed21576a2aaa080bc4ff3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 7 May 2022 12:15:11 +0300 Subject: [PATCH 085/126] Change mod scrolling behaviour to not scroll horizontally on columns/settings --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModSettingsArea.cs | 2 +- .../Mods/NestedVerticalScrollContainer.cs | 48 ------------------- 3 files changed, 2 insertions(+), 50 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 07871663a6..1bc139a181 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Mods }, new Drawable[] { - new NestedVerticalScrollContainer + new OsuScrollContainer(Direction.Vertical) { RelativeSizeAxes = Axes.Both, ClampExtension = 100, diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs index 08f563f93e..f44e4bf07f 100644 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Mods new[] { Empty() }, new Drawable[] { - new NestedVerticalScrollContainer + new OsuScrollContainer(Direction.Vertical) { RelativeSizeAxes = Axes.Both, ClampExtension = 100, diff --git a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs deleted file mode 100644 index d27f97f3d2..0000000000 --- a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Mods -{ - /// - /// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container. - /// - public class NestedVerticalScrollContainer : OsuScrollContainer - { - private ModSelectScreen.ColumnScrollContainer? parentScrollContainer; - - protected override void LoadComplete() - { - base.LoadComplete(); - - parentScrollContainer = this.FindClosestParent(); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (parentScrollContainer == null) - return base.OnScroll(e); - - bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight); - bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft); - - // If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view). - if (!topRightInView || !bottomLeftInView) - return false; - - bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd(); - bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0; - - // If at either of our extents, delegate scroll to the horizontal parent container. - if (scrollingPastStart || scrollingPastEnd) - return false; - - return base.OnScroll(e); - } - } -} From d4c9de8596b88e268893892b36305048d1faa5ba Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 7 May 2022 13:23:48 +0300 Subject: [PATCH 086/126] Move button width in mod select to constant --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 8 +++++--- osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 2af65d89b5..a9af48fc06 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -29,6 +29,8 @@ namespace osu.Game.Overlays.Mods { public abstract class ModSelectScreen : ShearedOverlayContainer { + protected const int BUTTON_WIDTH = 200; + protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green; [Cached] @@ -57,12 +59,12 @@ namespace osu.Game.Overlays.Mods protected virtual IEnumerable CreateFooterButtons() => new[] { - customisationButton = new ShearedToggleButton(200) + customisationButton = new ShearedToggleButton(BUTTON_WIDTH) { Text = ModSelectScreenStrings.ModCustomisation, Active = { BindTarget = customisationVisible } }, - new ShearedButton(200) + new ShearedButton(BUTTON_WIDTH) { Text = CommonStrings.DeselectAll, Action = DeselectAll @@ -171,7 +173,7 @@ namespace osu.Game.Overlays.Mods Horizontal = 70 }, Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons().Prepend(new ShearedButton(200) + ChildrenEnumerable = CreateFooterButtons().Prepend(new ShearedButton(BUTTON_WIDTH) { Text = CommonStrings.Back, Action = Hide, diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs index 5a7fe8a778..6d241af043 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectScreen.cs @@ -31,14 +31,14 @@ namespace osu.Game.Screens.OnlinePlay protected override IEnumerable CreateFooterButtons() => new[] { - new ShearedButton(200) + new ShearedButton(BUTTON_WIDTH) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Text = CommonStrings.SelectAll, Action = SelectAll }, - new ShearedButton(200) + new ShearedButton(BUTTON_WIDTH) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, From a716f62a6a373ed4e37827c7790f2927e5b402c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 12:59:15 +0200 Subject: [PATCH 087/126] Flash back button when keyboard back binding is pressed --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index a9af48fc06..8ec2260370 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -77,8 +77,10 @@ namespace osu.Game.Overlays.Mods private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; - private ShearedToggleButton? customisationButton; + private FillFlowContainer footerButtonFlow = null!; + private ShearedButton backButton = null!; + private ShearedToggleButton? customisationButton; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -173,7 +175,7 @@ namespace osu.Game.Overlays.Mods Horizontal = 70 }, Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons().Prepend(new ShearedButton(BUTTON_WIDTH) + ChildrenEnumerable = CreateFooterButtons().Prepend(backButton = new ShearedButton(BUTTON_WIDTH) { Text = CommonStrings.Back, Action = Hide, @@ -364,9 +366,12 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == GlobalAction.Back && customisationVisible.Value) + if (e.Action == GlobalAction.Back) { - customisationVisible.Value = false; + if (customisationVisible.Value) + customisationVisible.Value = false; + else + backButton.TriggerClick(); return true; } From 5d6d7bb324bab493478d8a843278362bfbdcc330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 13:10:03 +0200 Subject: [PATCH 088/126] Fix incorrect assert in multiplayer song select test scene --- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index d170b12dc3..061fe5715b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -132,7 +132,11 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertHasFreeModButton(Type type, bool hasButton = true) { AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", - () => this.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type)); + () => this.ChildrenOfType() + .Single() + .ChildrenOfType() + .Where(panel => !panel.Filtered.Value) + .All(b => b.Mod.GetType() != type)); } private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect From 0c2b4a6c0b189571cd175c21e195c9193b8d0f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 15:44:22 +0200 Subject: [PATCH 089/126] Fix invalid `IsLoaded` check Would fail when `availableMods` would be changed from a different thread while the columns aren't loaded. --- osu.Game/Overlays/Mods/ModColumn.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index a1ad435733..346e62e5fa 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -250,9 +250,8 @@ namespace osu.Game.Overlays.Mods private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours) { availableMods.BindTo(game.AvailableMods); - // this `BindValueChanged` callback is intentionally here, to ensure that local available mods are constructed as early as possible. - // this is needed to make sure no external changes to mods are dropped while mod panels are asynchronously loading. - availableMods.BindValueChanged(_ => updateLocalAvailableMods(), true); + updateLocalAvailableMods(asyncLoadContent: false); + availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true)); headerBackground.Colour = accentColour = colours.ForModType(ModType); @@ -279,7 +278,7 @@ namespace osu.Game.Overlays.Mods toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll; } - private void updateLocalAvailableMods() + private void updateLocalAvailableMods(bool asyncLoadContent) { var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty()) .Select(m => m.DeepClone()) @@ -290,11 +289,10 @@ namespace osu.Game.Overlays.Mods localAvailableMods = newMods; - if (!IsLoaded) - // if we're coming from BDL, perform the first load synchronously to make sure everything is in place as early as possible. - onPanelsLoaded(createPanels()); - else + if (asyncLoadContent) asyncLoadPanels(); + else + onPanelsLoaded(createPanels()); } private CancellationTokenSource? cancellationTokenSource; From 0d32bf91eb19af79e5d61bab685fced1f3d26d2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 01:30:21 +0900 Subject: [PATCH 090/126] Hardcode hide key handling in `ModSelectScreen` --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 33 +++++++++---------- .../OnlinePlay/OnlinePlaySongSelect.cs | 2 -- osu.Game/Screens/Select/SongSelect.cs | 5 +-- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 3980f6f4b9..2a9e235364 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -20,11 +20,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select; using osuTK; using osuTK.Input; -using osu.Game.Localisation; namespace osu.Game.Overlays.Mods { @@ -49,15 +48,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Hotkey that should be used to hide the mod select screen. - /// - /// - /// This is handled locally here rather than via , because this overlay is being registered at the game level - /// and therefore takes away keyboard focus from the screen stack. - /// - internal GlobalAction? Hotkey { get; set; } - /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. /// @@ -382,6 +372,8 @@ namespace osu.Game.Overlays.Mods if (e.Repeat) return false; + // This is handled locally here because this overlay is being registered at the game level + // and therefore takes away keyboard focus from the screen stack. if (e.Action == GlobalAction.Back) { if (customisationVisible.Value) @@ -391,15 +383,20 @@ namespace osu.Game.Overlays.Mods return true; } - if (e.Action == Hotkey || e.Action == GlobalAction.Select) + switch (e.Action) { - if (customisationVisible.Value) - customisationVisible.Value = false; - Hide(); - return true; - } + case GlobalAction.ToggleModSelection: + case GlobalAction.Select: + { + if (customisationVisible.Value) + customisationVisible.Value = false; + Hide(); + return true; + } - return base.OnPressed(e); + default: + return base.OnPressed(e); + } } internal class ColumnScrollContainer : OsuScrollContainer diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 4b6c06481b..c4503773ad 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -163,7 +162,6 @@ namespace osu.Game.Screens.OnlinePlay protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum) { - Hotkey = GlobalAction.ToggleModSelection, IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7317b20f88..eb5e996972 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -333,10 +333,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen - { - Hotkey = GlobalAction.ToggleModSelection - }; + protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 1d27ef18dc7d4a02aea655aa357ed8f62ec8a6ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 01:49:29 +0900 Subject: [PATCH 091/126] Add xmldoc for `IsValidMod` --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModSelectScreen.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 346e62e5fa..f0741cdc40 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Mods private Func? filter; /// - /// Function determining whether each mod in the column should be displayed. + /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. /// diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 2a9e235364..8098e8b0f6 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -36,6 +36,11 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = m => true; + /// + /// A function determining whether each mod in the column should be displayed. + /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. + /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. + /// public Func IsValidMod { get => isValidMod; From 03c80d91978988229b338c4056ef3a7f6d282702 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 01:58:37 +0900 Subject: [PATCH 092/126] Reorder methods / properties in `ModSelectScreen` for legibility --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 86 ++++++++++++++--------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 8098e8b0f6..ef96811144 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -60,29 +60,20 @@ namespace osu.Game.Overlays.Mods protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); - protected virtual IEnumerable CreateFooterButtons() => new[] - { - customisationButton = new ShearedToggleButton(BUTTON_WIDTH) - { - Text = ModSelectScreenStrings.ModCustomisation, - Active = { BindTarget = customisationVisible } - }, - new ShearedButton(BUTTON_WIDTH) - { - Text = CommonStrings.DeselectAll, - Action = DeselectAll - } - }; + protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; + + protected virtual IEnumerable CreateFooterButtons() => createDefaultFooterButtons(); private readonly BindableBool customisationVisible = new BindableBool(); - private DifficultyMultiplierDisplay? multiplierDisplay; private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; - private FillFlowContainer footerButtonFlow = null!; private ShearedButton backButton = null!; + + private DifficultyMultiplierDisplay? multiplierDisplay; + private ShearedToggleButton? customisationButton; protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green) @@ -193,14 +184,6 @@ namespace osu.Game.Overlays.Mods }; } - private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) - => new ColumnDimContainer(CreateModColumn(modType, toggleKeys)) - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140) - }; - protected override void LoadComplete() { base.LoadComplete(); @@ -224,6 +207,47 @@ namespace osu.Game.Overlays.Mods updateAvailableMods(); } + /// + /// Select all visible mods in all columns. + /// + protected void SelectAll() + { + foreach (var column in columnFlow.Columns) + column.SelectAll(); + } + + /// + /// Deselect all visible mods in all columns. + /// + protected void DeselectAll() + { + foreach (var column in columnFlow.Columns) + column.DeselectAll(); + } + + private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) + => new ColumnDimContainer(CreateModColumn(modType, toggleKeys)) + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140) + }; + + private ShearedButton[] createDefaultFooterButtons() + => new[] + { + customisationButton = new ShearedToggleButton(BUTTON_WIDTH) + { + Text = ModSelectScreenStrings.ModCustomisation, + Active = { BindTarget = customisationVisible } + }, + new ShearedButton(BUTTON_WIDTH) + { + Text = CommonStrings.DeselectAll, + Action = DeselectAll + } + }; + private void updateMultiplier() { if (multiplierDisplay == null) @@ -314,7 +338,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); } - protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; + #region Transition handling protected override void PopIn() { @@ -360,17 +384,9 @@ namespace osu.Game.Overlays.Mods } } - protected void SelectAll() - { - foreach (var column in columnFlow.Columns) - column.SelectAll(); - } + #endregion - protected void DeselectAll() - { - foreach (var column in columnFlow.Columns) - column.DeselectAll(); - } + #region Input handling public override bool OnPressed(KeyBindingPressEvent e) { @@ -404,6 +420,8 @@ namespace osu.Game.Overlays.Mods } } + #endregion + internal class ColumnScrollContainer : OsuScrollContainer { public ColumnScrollContainer() From 2278da563e95e26f4ed6718b6441067df7f67463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 02:03:28 +0900 Subject: [PATCH 093/126] Add various xmldoc for nested classes --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index ef96811144..0a1b4e857e 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -422,6 +422,9 @@ namespace osu.Game.Overlays.Mods #endregion + /// + /// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility. + /// internal class ColumnScrollContainer : OsuScrollContainer { public ColumnScrollContainer() @@ -460,6 +463,9 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Manages padding and layout of mod columns. + /// internal class ColumnFlowContainer : FillFlowContainer { public IEnumerable Columns => Children.Select(dimWrapper => dimWrapper.Column); @@ -496,11 +502,21 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Encapsulates a column and provides dim and input blocking based on an externally managed "active" state. + /// internal class ColumnDimContainer : Container { public ModColumn Column { get; } + /// + /// Tracks whether this column is in an interactive state. Generally only the case when the column is on-screen. + /// public readonly Bindable Active = new BindableBool(); + + /// + /// Invoked when the column is clicked while not active, requesting a scroll to be performed to bring it on-screen. + /// public Action? RequestScroll { get; set; } [Resolved] @@ -555,6 +571,9 @@ namespace osu.Game.Overlays.Mods } } + /// + /// A container which blocks and handles input, managing the "return from customisation" state change. + /// private class ClickToReturnContainer : Container { public BindableBool HandleMouse { get; } = new BindableBool(); From 46d3220c07ea99d4ef4dc67756ddee5d781f06a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 21:20:00 +0200 Subject: [PATCH 094/126] Isolate sample screens from global mods bindable Fixes scenario wherein entering the first run setup overlay, exiting at the "UI scale" step (which shows a song select), then moving to actua song select and trying to select a mod would lead to a crash. The crash was caused by two active mod screen instances attempting to swap the global mod bindable's mod instances for ones they owned. This logic - while generally problematic and hard to maintain - was fixing several issues with mod reference management and setting copying, so I'm letting it live another day. This change will mean that the song select preview on the "UI scale" step will not receive the same mods that the actual game has enabled. That said, it already doesn't use the same beatmap or ruleset, so this looks fine to break. --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 862506add2..152d67ab27 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -2,6 +2,7 @@ // 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.Audio; @@ -19,6 +20,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; @@ -131,6 +133,10 @@ namespace osu.Game.Overlays.FirstRunSetup [Cached(typeof(IBindable))] protected Bindable Beatmap { get; private set; } = new Bindable(); + [Cached] + [Cached(typeof(IBindable>))] + protected Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); + public override bool HandlePositionalInput => false; public override bool HandleNonPositionalInput => false; public override bool PropagatePositionalInputSubTree => false; From 836de491353212ed8acc533437ac72b1c349af38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 22:41:48 +0200 Subject: [PATCH 095/126] Adjust skin editor scene library test for extensibility --- .../TestSceneSkinEditorSceneLibrary.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs index d3aeba2c0f..3e6cf325df 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs @@ -20,20 +20,21 @@ namespace osu.Game.Tests.Visual.Navigation { public class TestSceneSkinEditorSceneLibrary : OsuGameTestScene { - private SkinEditor skinEditor; + private TestPlaySongSelect songSelect; + private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); - public override void SetUpSteps() + private void advanceToSongSelect() { - base.SetUpSteps(); - - Screens.Select.SongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + } + private void openSkinEditor() + { AddStep("open skin editor", () => { InputManager.PressKey(Key.ControlLeft); @@ -42,13 +43,15 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.ControlLeft); InputManager.ReleaseKey(Key.ShiftLeft); }); - - AddUntilStep("get skin editor", () => (skinEditor = Game.ChildrenOfType().FirstOrDefault()) != null); + AddUntilStep("skin editor loaded", () => skinEditor != null); } [Test] public void TestEditComponentDuringGameplay() { + advanceToSongSelect(); + openSkinEditor(); + switchToGameplayScene(); BarHitErrorMeter hitErrorMeter = null; @@ -85,6 +88,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay() { + advanceToSongSelect(); + openSkinEditor(); AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); switchToGameplayScene(); @@ -95,6 +100,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestAutoplayIncompatibleModsRemovedOnEnteringGameplay() { + advanceToSongSelect(); + openSkinEditor(); AddStep("select no fail and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModSpunOut() }); switchToGameplayScene(); @@ -105,6 +112,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestDuplicateAutoplayModRemovedOnEnteringGameplay() { + advanceToSongSelect(); + openSkinEditor(); AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() }); switchToGameplayScene(); @@ -115,6 +124,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestCinemaModRemovedOnEnteringGameplay() { + advanceToSongSelect(); + openSkinEditor(); AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() }); switchToGameplayScene(); From a3e61fddcb06c21ed7e12d7af61bd9a7d03a260c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 22:43:45 +0200 Subject: [PATCH 096/126] Rename skin editor scene library test scene to be more general --- ...inEditorSceneLibrary.cs => TestSceneSkinEditorNavigation.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Navigation/{TestSceneSkinEditorSceneLibrary.cs => TestSceneSkinEditorNavigation.cs} (98%) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs similarity index 98% rename from osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs rename to osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 3e6cf325df..5359d992a1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorSceneLibrary.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -18,7 +18,7 @@ using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; namespace osu.Game.Tests.Visual.Navigation { - public class TestSceneSkinEditorSceneLibrary : OsuGameTestScene + public class TestSceneSkinEditorNavigation : OsuGameTestScene { private TestPlaySongSelect songSelect; private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); From bdea6d865443e51e2ae927b01d3f347473364907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 22:47:19 +0200 Subject: [PATCH 097/126] Add failing test for mod overlay not closing on entering skin editor --- .../Navigation/TestSceneSkinEditorNavigation.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 5359d992a1..68c44f49cc 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Overlays.Settings; @@ -133,6 +134,16 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any()); } + [Test] + public void TestModOverlayClosesOnOpeningSkinEditor() + { + advanceToSongSelect(); + AddStep("open mod overlay", () => songSelect.ModSelectOverlay.Show()); + + openSkinEditor(); + AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + } + private void switchToGameplayScene() { AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick()); From a45ca2ad101957b9e00560a0b66a01508e724168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 22:50:10 +0200 Subject: [PATCH 098/126] Fix focused overlays not registering as such Also fixes bug wherein opening skin editor at song select with mod select open would show mod select on top of the skin editor. --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c2a96a9082..54c4231b06 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -201,6 +201,10 @@ namespace osu.Game externalOverlays.Add(overlayContainer); overlayContent.Add(overlayContainer); + + if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer) + focusedOverlays.Add(focusedOverlayContainer); + return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer)); } @@ -223,6 +227,10 @@ namespace osu.Game private void unregisterBlockingOverlay(OverlayContainer overlayContainer) { externalOverlays.Remove(overlayContainer); + + if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer) + focusedOverlays.Remove(focusedOverlayContainer); + overlayContainer.Expire(); } From 230c4e27b8baee58b553d651d289a5f199f3c962 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 12:56:07 +0900 Subject: [PATCH 099/126] Simplify and centralise hiding logic for mod overlay Behaviourally, this also always toggles via button triggering to add the button flash animation. --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 40 ++++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 0a1b4e857e..5ec667bd90 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -393,30 +393,38 @@ namespace osu.Game.Overlays.Mods if (e.Repeat) return false; - // This is handled locally here because this overlay is being registered at the game level - // and therefore takes away keyboard focus from the screen stack. - if (e.Action == GlobalAction.Back) - { - if (customisationVisible.Value) - customisationVisible.Value = false; - else - backButton.TriggerClick(); - return true; - } - switch (e.Action) { + case GlobalAction.Back: + // Pressing the back binding should only go back one step at a time. + hideOverlay(false); + return true; + + // This is handled locally here because this overlay is being registered at the game level + // and therefore takes away keyboard focus from the screen stack. case GlobalAction.ToggleModSelection: case GlobalAction.Select: { - if (customisationVisible.Value) - customisationVisible.Value = false; - Hide(); + // Pressing toggle or select should completely hide the overlay in one shot. + hideOverlay(true); return true; } + } - default: - return base.OnPressed(e); + return base.OnPressed(e); + + void hideOverlay(bool immediate) + { + if (customisationVisible.Value) + { + Debug.Assert(customisationButton != null); + customisationButton.TriggerClick(); + + if (!immediate) + return; + } + + backButton.TriggerClick(); } } From 3eeedd802478279e91d72caf4971a027427cd4b9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 13:45:21 +0900 Subject: [PATCH 100/126] Fix per-hit object slider velocity ignored in osu!catch --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7ddbc2f768..b91a74c4a1 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -183,15 +183,15 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint lastRelevantSamplePoint = null; DifficultyControlPoint lastRelevantDifficultyPoint = null; - bool isOsuRuleset = onlineRulesetID == 0; + // In osu!taiko and osu!mania, a scroll speed is stored as "slider velocity" in legacy formats. + // In that case, a scrolling speed change is a global effect and per-hit object difficulty control points are ignored. + bool scrollSpeedEncodedAsSliderVelocity = onlineRulesetID == 1 || onlineRulesetID == 3; // iterate over hitobjects and pull out all required sample and difficulty changes extractDifficultyControlPoints(beatmap.HitObjects); extractSampleControlPoints(beatmap.HitObjects); - // handle scroll speed, which is stored as "slider velocity" in legacy formats. - // this is relevant for scrolling ruleset beatmaps. - if (!isOsuRuleset) + if (scrollSpeedEncodedAsSliderVelocity) { foreach (var point in legacyControlPoints.EffectPoints) legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); @@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps.Formats IEnumerable collectDifficultyControlPoints(IEnumerable hitObjects) { - if (!isOsuRuleset) + if (scrollSpeedEncodedAsSliderVelocity) yield break; foreach (var hitObject in hitObjects) From 125628dd20076054c6e5a8ab040d08b729558d6c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 15:52:14 +0900 Subject: [PATCH 101/126] Fix hit object not shown in timline while placement is waiting --- .../Edit/Blueprints/BananaShowerPlacementBlueprint.cs | 2 ++ .../Edit/Blueprints/JuiceStreamPlacementBlueprint.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 6dea8b0712..4613bfd36e 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints base.Update(); outline.UpdateFrom(HitObjectContainer, HitObject); + + BeginPlacement(); } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs index cff5bc2417..e9c8e2bb2c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs @@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints base.LoadComplete(); inputManager = GetContainingInputManager(); + + BeginPlacement(); } protected override bool OnMouseDown(MouseDownEvent e) From 9ae019eb3945acb74eecebf58aa7baeeaa2a3e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 16:17:23 +0200 Subject: [PATCH 102/126] Move `ISamplePlaybackDisabler` to more general namespace --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 1 + osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 1 - osu.Game/{Screens/Play => Audio}/ISamplePlaybackDisabler.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Play/Player.cs | 1 + osu.Game/Skinning/PausableSkinnableSound.cs | 1 - 6 files changed, 5 insertions(+), 4 deletions(-) rename osu.Game/{Screens/Play => Audio}/ISamplePlaybackDisabler.cs (85%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index ae2bc60fc6..815cc09448 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects.Drawables; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 64afe1235b..3953ef8b33 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Game.Audio; -using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Audio/ISamplePlaybackDisabler.cs similarity index 85% rename from osu.Game/Screens/Play/ISamplePlaybackDisabler.cs rename to osu.Game/Audio/ISamplePlaybackDisabler.cs index 6b37021fe6..65d36e9171 100644 --- a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs +++ b/osu.Game/Audio/ISamplePlaybackDisabler.cs @@ -4,11 +4,11 @@ using osu.Framework.Bindables; using osu.Game.Skinning; -namespace osu.Game.Screens.Play +namespace osu.Game.Audio { /// /// Allows a component to disable sample playback dynamically as required. - /// Handled by . + /// Automatically handled by . /// public interface ISamplePlaybackDisabler { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3fde033587..143d975104 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ae3eb1ed8b..2f37b578f2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 10b8c47028..b34351d4e7 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; using osu.Game.Audio; -using osu.Game.Screens.Play; namespace osu.Game.Skinning { From cbd1169495a283d4d439cfc16edd856b23304f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 16:23:30 +0200 Subject: [PATCH 103/126] Move cache declarations of `ISamplePlaybackDisabler` to interface --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 1 - osu.Game/Audio/ISamplePlaybackDisabler.cs | 2 ++ osu.Game/Screens/Edit/Editor.cs | 1 - osu.Game/Screens/Play/Player.cs | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 3953ef8b33..31abcb6748 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -130,7 +130,6 @@ namespace osu.Game.Tests.Visual.Gameplay } [Cached(typeof(ISkinSource))] - [Cached(typeof(ISamplePlaybackDisabler))] private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler { [Resolved] diff --git a/osu.Game/Audio/ISamplePlaybackDisabler.cs b/osu.Game/Audio/ISamplePlaybackDisabler.cs index 65d36e9171..2f49e94f34 100644 --- a/osu.Game/Audio/ISamplePlaybackDisabler.cs +++ b/osu.Game/Audio/ISamplePlaybackDisabler.cs @@ -1,6 +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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Skinning; @@ -10,6 +11,7 @@ namespace osu.Game.Audio /// Allows a component to disable sample playback dynamically as required. /// Automatically handled by . /// + [Cached] public interface ISamplePlaybackDisabler { /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 143d975104..947c184009 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -51,7 +51,6 @@ using osuTK.Input; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] - [Cached(typeof(ISamplePlaybackDisabler))] [Cached] public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2f37b578f2..51c1e6b43b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -38,7 +38,6 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { [Cached] - [Cached(typeof(ISamplePlaybackDisabler))] public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo { /// From 81ca534f878879af0cbb3384fb26fb8084aaeae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 16:28:28 +0200 Subject: [PATCH 104/126] Implement `ISamplePlaybackDisabler` in mod select --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 5ec667bd90..4e87b5955a 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Framework.Lists; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -27,7 +28,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract class ModSelectScreen : ShearedOverlayContainer + public abstract class ModSelectScreen : ShearedOverlayContainer, ISamplePlaybackDisabler { protected const int BUTTON_WIDTH = 200; @@ -188,6 +189,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true); + ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); SelectedMods.BindValueChanged(val => @@ -430,6 +433,13 @@ namespace osu.Game.Overlays.Mods #endregion + #region Sample playback control + + private readonly Bindable samplePlaybackDisabled = new BindableBool(true); + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; + + #endregion + /// /// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility. /// From b92d95a17acb60a0ba671add4a80fc74eda4957f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 May 2022 16:35:34 +0200 Subject: [PATCH 105/126] Fix mod panels playing samples when hidden at a higher level --- osu.Game/Audio/ISamplePlaybackDisabler.cs | 1 + osu.Game/Overlays/Mods/ModPanel.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/ISamplePlaybackDisabler.cs b/osu.Game/Audio/ISamplePlaybackDisabler.cs index 2f49e94f34..4167316780 100644 --- a/osu.Game/Audio/ISamplePlaybackDisabler.cs +++ b/osu.Game/Audio/ISamplePlaybackDisabler.cs @@ -10,6 +10,7 @@ namespace osu.Game.Audio /// /// Allows a component to disable sample playback dynamically as required. /// Automatically handled by . + /// May also be manually handled locally to particular components. /// [Cached] public interface ISamplePlaybackDisabler diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index f2a97da3b2..4c4951307d 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -12,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -21,8 +24,6 @@ using osu.Game.Rulesets.UI; using osuTK; using osuTK.Input; -#nullable enable - namespace osu.Game.Overlays.Mods { public class ModPanel : OsuClickableContainer @@ -50,6 +51,7 @@ namespace osu.Game.Overlays.Mods private Colour4 activeColour; + private readonly Bindable samplePlaybackDisabled = new BindableBool(); private Sample? sampleOff; private Sample? sampleOn; @@ -139,13 +141,16 @@ namespace osu.Game.Overlays.Mods Action = Active.Toggle; } - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler) { sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); activeColour = colours.ForModType(Mod.Type); + + if (samplePlaybackDisabler != null) + ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); @@ -166,6 +171,9 @@ namespace osu.Game.Overlays.Mods private void playStateChangeSamples() { + if (samplePlaybackDisabled.Value) + return; + if (Active.Value) sampleOn?.Play(); else From 778497b9e27c0bce96d5580d71d9bb915c997b00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 17:00:20 +0900 Subject: [PATCH 106/126] Scroll mod select slightly into view on first display --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 4e87b5955a..959d9ad2fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -208,6 +208,14 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); updateAvailableMods(); + + // Start scrolled slightly to the right to give the user a sense that + // there is more horizontal content available. + ScheduleAfterChildren(() => + { + columnScroll.ScrollTo(100, false); + columnScroll.ScrollToStart(); + }); } /// From b8cb2c1b82570100a6c60388f65d7fa2e240c7fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 19:57:03 +0900 Subject: [PATCH 107/126] Increase scroll amount slightly --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 959d9ad2fc..fc06af3f9d 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Mods // there is more horizontal content available. ScheduleAfterChildren(() => { - columnScroll.ScrollTo(100, false); + columnScroll.ScrollTo(200, false); columnScroll.ScrollToStart(); }); } From dcf0d5a9d5b93045385c8f4b9f5fe16844294bb3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:49:42 +0900 Subject: [PATCH 108/126] Fix slider velocity wrongly decoded as scrolling speed in osu!catch --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 79d8bd3bb3..3a893a1238 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -431,9 +431,10 @@ namespace osu.Game.Beatmaps.Formats OmitFirstBarLine = omitFirstBarSignature, }; - bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0; - // scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments. - if (!isOsuRuleset) + int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; + + // osu!taiko and osu!mania use effect points rather than difficulty points for scroll speed adjustments. + if (onlineRulesetID == 1 || onlineRulesetID == 3) effectPoint.ScrollSpeed = speedMultiplier; addControlPoint(time, effectPoint, timingChange); From 92ccec20d787d08e19d81437ab7c89e148175abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 May 2022 14:44:54 +0200 Subject: [PATCH 109/126] Hide mod columns if all mods within are filtered out --- osu.Game/Overlays/Mods/ModColumn.cs | 13 +++++++ osu.Game/Overlays/Mods/ModSelectScreen.cs | 45 +++++++++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index f0741cdc40..89f472a290 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -55,8 +55,18 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Determines whether this column should accept user input. + /// public Bindable Active = new BindableBool(true); + private readonly Bindable allFiltered = new BindableBool(); + + /// + /// True if all of the panels in this column have been filtered out by the current . + /// + public IBindable AllFiltered => allFiltered; + /// /// List of mods marked as selected in this column. /// @@ -339,6 +349,9 @@ namespace osu.Game.Overlays.Mods panel.ApplyFilter(Filter); } + allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value); + Alpha = allFiltered.Value ? 0 : 1; + if (toggleAllCheckbox != null && !SelectionAnimationRunning) { toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0; diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index fc06af3f9d..6a3df3fb05 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -237,11 +237,11 @@ namespace osu.Game.Overlays.Mods } private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) - => new ColumnDimContainer(CreateModColumn(modType, toggleKeys)) + => new ColumnDimContainer(CreateModColumn(modType, toggleKeys).With(column => column.Filter = IsValidMod)) { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, - RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140) + RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140), }; private ShearedButton[] createDefaultFooterButtons() @@ -351,6 +351,8 @@ namespace osu.Game.Overlays.Mods #region Transition handling + private const float distance = 700; + protected override void PopIn() { const double fade_in_duration = 400; @@ -362,13 +364,26 @@ namespace osu.Game.Overlays.Mods .FadeIn(fade_in_duration / 2, Easing.OutQuint) .ScaleTo(1, fade_in_duration, Easing.OutElastic); + int nonFilteredColumnCount = 0; + for (int i = 0; i < columnFlow.Count; i++) { - columnFlow[i].Column - .TopLevelContent - .Delay(i * 30) - .MoveToY(0, fade_in_duration, Easing.OutQuint) - .FadeIn(fade_in_duration, Easing.OutQuint); + var column = columnFlow[i].Column; + + double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30; + double duration = column.AllFiltered.Value ? 0 : fade_in_duration; + float startingYPosition = 0; + if (!column.AllFiltered.Value) + startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; + + column.TopLevelContent + .MoveToY(startingYPosition) + .Delay(delay) + .MoveToY(0, duration, Easing.OutQuint) + .FadeIn(duration, Easing.OutQuint); + + if (!column.AllFiltered.Value) + nonFilteredColumnCount += 1; } } @@ -382,16 +397,24 @@ namespace osu.Game.Overlays.Mods .FadeOut(fade_out_duration / 2, Easing.OutQuint) .ScaleTo(0.75f, fade_out_duration, Easing.OutQuint); + int nonFilteredColumnCount = 0; + for (int i = 0; i < columnFlow.Count; i++) { - const float distance = 700; - var column = columnFlow[i].Column; + double duration = column.AllFiltered.Value ? 0 : fade_out_duration; + float newYPosition = 0; + if (!column.AllFiltered.Value) + newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; + column.FlushPendingSelections(); column.TopLevelContent - .MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint) - .FadeOut(fade_out_duration, Easing.OutQuint); + .MoveToY(newYPosition, duration, Easing.OutQuint) + .FadeOut(duration, Easing.OutQuint); + + if (!column.AllFiltered.Value) + nonFilteredColumnCount += 1; } } From 38c004d734be28cdc973bbd8eb9f4ca19e5b55db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 May 2022 14:45:01 +0200 Subject: [PATCH 110/126] Add test coverage for hiding mod columns --- .../UserInterface/TestSceneModSelectScreen.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 42ffeba444..fa7758df59 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -451,6 +451,36 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden); } + [Test] + public void TestColumnHiding() + { + AddStep("create screen", () => Child = modSelectScreen = new UserModSelectScreen + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods }, + IsValidMod = mod => mod.Type == ModType.DifficultyIncrease || mod.Type == ModType.Conversion + }); + waitForColumnLoad(); + changeRuleset(0); + + AddAssert("two columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2); + + AddStep("unset filter", () => modSelectScreen.IsValidMod = _ => true); + AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); + + AddStep("filter out everything", () => modSelectScreen.IsValidMod = _ => false); + AddAssert("no columns visible", () => this.ChildrenOfType().All(col => !col.IsPresent)); + + AddStep("hide", () => modSelectScreen.Hide()); + AddStep("set filter for 3 columns", () => modSelectScreen.IsValidMod = mod => mod.Type == ModType.DifficultyReduction + || mod.Type == ModType.Automation + || mod.Type == ModType.Conversion); + + AddStep("show", () => modSelectScreen.Show()); + AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From 077c77d52459c92b3b4e4f1beb9f634a0585eada Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 8 May 2022 16:00:07 +0300 Subject: [PATCH 111/126] Add method for scaling results screen in tests --- .../Multiplayer/TestSceneMultiplayerTeamResults.cs | 12 ++++++++++++ .../Visual/Ranking/TestSceneResultsScreen.cs | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index bcb36a585f..0237298fa1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -8,11 +8,23 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Resources; +using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerTeamResults : ScreenTestScene { + [Test] + public void TestScaling() + { + // scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason. + AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() => + { + Stack.Scale = new Vector2(v); + Stack.Size = new Vector2(1f / v); + })); + } + [TestCase(7483253, 1048576)] [TestCase(1048576, 7483253)] [TestCase(1048576, 1048576)] diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 4eed2a25f5..a1d51683e4 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -56,6 +56,17 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestScaling() + { + // scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason. + AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() => + { + Content.Scale = new Vector2(v); + Content.Size = new Vector2(1f / v); + })); + } + [Test] public void TestResultsWithoutPlayer() { From 298c2a1828bf628d59a5bd7ec1b671e925732ec9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 8 May 2022 16:03:57 +0300 Subject: [PATCH 112/126] Replace vertical scrolling in results screen with size-preserving container --- .../MultiplayerTeamResultsScreen.cs | 6 ++--- osu.Game/Screens/Ranking/ResultsScreen.cs | 26 ++----------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs index 3f0f3e043c..117415ac8f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs @@ -46,9 +46,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { const float winner_background_half_height = 250; - VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre; - VerticalScrollContent.Scale = new Vector2(0.9f); - VerticalScrollContent.Y = 75; + Content.Anchor = Content.Origin = Anchor.TopCentre; + Content.Scale = new Vector2(0.9f); + Content.Y = 75; var redScore = teamScores.First().Value; var blueScore = teamScores.Last().Value; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 98514cd846..2c91b17917 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -14,7 +14,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -41,7 +40,7 @@ namespace osu.Game.Screens.Ranking protected ScorePanelList ScorePanelList { get; private set; } - protected VerticalScrollContainer VerticalScrollContent { get; private set; } + protected Container Content { get; private set; } [Resolved(CanBeNull = true)] private Player player { get; set; } @@ -79,10 +78,9 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - VerticalScrollContent = new VerticalScrollContainer + Content = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, Child = new Container { RelativeSizeAxes = Axes.Both, @@ -346,25 +344,5 @@ namespace osu.Game.Screens.Ranking public void OnReleased(KeyBindingReleaseEvent e) { } - - protected class VerticalScrollContainer : OsuScrollContainer - { - protected override Container Content => content; - - private readonly Container content; - - public VerticalScrollContainer() - { - Masking = false; - - base.Content.Add(content = new Container { RelativeSizeAxes = Axes.X }); - } - - protected override void Update() - { - base.Update(); - content.Height = Math.Max(screen_height, DrawHeight); - } - } } } From 7f1ad149d5775606497144af30d94e801222e8a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 8 May 2022 16:04:24 +0300 Subject: [PATCH 113/126] Remove no longer necessary horizontal scroll blocker --- osu.Game/Screens/Ranking/ScorePanelList.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index c2ef5529e8..a5341242e2 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -85,7 +85,6 @@ namespace osu.Game.Screens.Ranking InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, - HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. Child = flow = new Flow { Anchor = Anchor.Centre, @@ -359,11 +358,6 @@ namespace osu.Game.Screens.Ranking /// public float? InstantScrollTarget; - /// - /// Whether this container should handle scroll trigger events. - /// - public Func HandleScroll; - protected override void UpdateAfterChildren() { if (InstantScrollTarget != null) @@ -374,10 +368,6 @@ namespace osu.Game.Screens.Ranking base.UpdateAfterChildren(); } - - public override bool HandlePositionalInput => HandleScroll(); - - public override bool HandleNonPositionalInput => HandleScroll(); } } } From 6f4cdccf6c5430991c8e4c87a6ad60698faff4ce Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 8 May 2022 16:20:26 +0300 Subject: [PATCH 114/126] Remove no longer required constant --- osu.Game/Screens/Ranking/ResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2c91b17917..87e49fcc5e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Ranking public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler { protected const float BACKGROUND_BLUR = 20; - private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; public override bool DisallowExternalBeatmapRulesetChanges => true; From 6bdcf893b7d6c960d8154c9908b78f8c92a6c273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 May 2022 15:13:29 +0200 Subject: [PATCH 115/126] Move alpha management closer to screen level Felt bad messing with alpha at the column level. --- osu.Game/Overlays/Mods/ModColumn.cs | 1 - osu.Game/Overlays/Mods/ModSelectScreen.cs | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 89f472a290..3a2fda0bb0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -350,7 +350,6 @@ namespace osu.Game.Overlays.Mods } allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value); - Alpha = allFiltered.Value ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 6a3df3fb05..a2e73b9575 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -580,17 +580,20 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => updateDim(), true); + Active.BindValueChanged(_ => updateState()); + Column.AllFiltered.BindValueChanged(_ => updateState(), true); FinishTransforms(); } protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning; - private void updateDim() + private void updateState() { Colour4 targetColour; - if (Active.Value) + Column.Alpha = Column.AllFiltered.Value ? 0 : 1; + + if (Column.Active.Value) targetColour = Colour4.White; else targetColour = IsHovered ? colours.GrayC : colours.Gray8; @@ -609,14 +612,14 @@ namespace osu.Game.Overlays.Mods protected override bool OnHover(HoverEvent e) { base.OnHover(e); - updateDim(); + updateState(); return Active.Value; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - updateDim(); + updateState(); } } From d964b4f23c47df79e7c27acdcb7056b7b8774210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 8 May 2022 15:22:32 +0200 Subject: [PATCH 116/126] Fix uneven spacing when some mod columns are hidden --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index a2e73b9575..8b19e38954 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -129,7 +129,6 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(SHEAR, 0), RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Spacing = new Vector2(10, 0), Margin = new MarginPadding { Horizontal = 70 }, Children = new[] { @@ -237,12 +236,21 @@ namespace osu.Game.Overlays.Mods } private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) - => new ColumnDimContainer(CreateModColumn(modType, toggleKeys).With(column => column.Filter = IsValidMod)) + { + var column = CreateModColumn(modType, toggleKeys).With(column => + { + column.Filter = IsValidMod; + // spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden. + column.Margin = new MarginPadding { Right = 10 }; + }); + + return new ColumnDimContainer(column) { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, - RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140), + RequestScroll = col => columnScroll.ScrollIntoView(col, extraScroll: 140), }; + } private ShearedButton[] createDefaultFooterButtons() => new[] From 483a611c411ea64e24ca8e3a63fe58cb53cc99d3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 23:10:51 +0900 Subject: [PATCH 117/126] Fix `BeginPlacement` location. --- .../Edit/Blueprints/BananaShowerPlacementBlueprint.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 4613bfd36e..44cfea7779 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -18,13 +18,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints InternalChild = outline = new TimeSpanOutline(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + BeginPlacement(); + } + protected override void Update() { base.Update(); outline.UpdateFrom(HitObjectContainer, HitObject); - - BeginPlacement(); } protected override bool OnMouseDown(MouseDownEvent e) From 158f1342608ff147b27ee48ef52652bded7632d2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 9 May 2022 00:01:05 +0900 Subject: [PATCH 118/126] Fix duration is negative while placing banana shower in catch editor. Timeline blueprint is glitched when the hit object has negative duration. Negative duration is unwanted anyways so placement implementation is fixed instead of supporting it in timline blueprint. --- .../TestSceneBananaShowerPlacementBlueprint.cs | 3 +++ .../BananaShowerPlacementBlueprint.cs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index cca3701a60..fec253924f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddMoveStep(end_time, 0); AddClickStep(MouseButton.Left); + AddMoveStep(start_time, 0); + AddAssert("duration is positive", () => ((BananaShower)CurrentBlueprint.HitObject).Duration > 0); + AddClickStep(MouseButton.Right); AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time)); AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time)); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 6dea8b0712..c3a5306b3f 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -1,6 +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 osu.Framework.Input.Events; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; @@ -13,6 +14,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { private readonly TimeSpanOutline outline; + private double placementStartTime; + private double placementEndTime; + public BananaShowerPlacementBlueprint() { InternalChild = outline = new TimeSpanOutline(); @@ -38,13 +42,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints case PlacementState.Active: if (e.Button != MouseButton.Right) break; - // If the duration is negative, swap the start and the end time to make the duration positive. - if (HitObject.Duration < 0) - { - HitObject.StartTime = HitObject.EndTime; - HitObject.Duration = -HitObject.Duration; - } - EndPlacement(HitObject.Duration > 0); return true; } @@ -61,13 +58,16 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints switch (PlacementActive) { case PlacementState.Waiting: - HitObject.StartTime = time; + placementStartTime = placementEndTime = time; break; case PlacementState.Active: - HitObject.EndTime = time; + placementEndTime = time; break; } + + HitObject.StartTime = Math.Min(placementStartTime, placementEndTime); + HitObject.EndTime = Math.Max(placementStartTime, placementEndTime); } } } From bc839be4d85653fc0872c7e9d923129072eeedff Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 4 May 2022 09:26:58 -0700 Subject: [PATCH 119/126] Add failing rapid back button exit test --- .../Navigation/TestSceneScreenNavigation.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f8eee7be56..9674ef7ae1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -503,6 +504,22 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("test dispose doesn't crash", () => Game.Dispose()); } + [Test] + public void TestRapidBackButtonExit() + { + AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0)); + + AddStep("press escape twice rapidly", () => + { + InputManager.Key(Key.Escape); + InputManager.Key(Key.Escape); + }); + + pushEscape(); + + AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog != null); + } + private Func playToResults() { Player player = null; From 21e1f4546a7dc35eb056748899dd423c6273cf0e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 4 May 2022 09:27:53 -0700 Subject: [PATCH 120/126] Fix popup dialog potentially not clicking last button when dismissed --- osu.Game/Overlays/Dialog/PopupDialog.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index d08b6b7beb..5959fe656c 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -100,10 +100,6 @@ namespace osu.Game.Overlays.Dialog } } - // We always want dialogs to show their appear animation, so we request they start hidden. - // Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete(). - protected override bool StartHidden => true; - protected PopupDialog() { RelativeSizeAxes = Axes.Both; @@ -272,7 +268,7 @@ namespace osu.Game.Overlays.Dialog protected override void PopOut() { - if (!actionInvoked && content.IsPresent) + if (!actionInvoked) // In the case a user did not choose an action before a hide was triggered, press the last button. // This is presumed to always be a sane default "cancel" action. buttonsContainer.Last().TriggerClick(); From 3b4fdf20f9c5bb510ab660373c2a8ec498e18321 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 9 May 2022 12:15:54 +0900 Subject: [PATCH 121/126] Prevent throwing exceptions on first run without internet --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 46f5b418bd..fc39887e79 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -153,7 +153,17 @@ namespace osu.Game.Beatmaps } }; - Task.Run(() => cacheDownloadRequest.PerformAsync()); + Task.Run(async () => + { + try + { + await cacheDownloadRequest.PerformAsync(); + } + catch + { + // Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway. + } + }); } private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo) From e4521b1fff489cc92164d8d5de8c16cb628bd675 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 10:16:57 +0300 Subject: [PATCH 122/126] Revert scale locking changes for now --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 87e49fcc5e..70a1b69bcf 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - Content = new DrawSizePreservingFillContainer + Content = new Container { RelativeSizeAxes = Axes.Both, Child = new Container From 3407a299ef30657d040736d71c7e84687adf927f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 10:35:12 +0300 Subject: [PATCH 123/126] Revert "Revert scale locking changes for now" This reverts commit e4521b1fff489cc92164d8d5de8c16cb628bd675. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 70a1b69bcf..87e49fcc5e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - Content = new Container + Content = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both, Child = new Container From 422531d8ec5cb7240b4948c9e426663982e2a973 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 10:35:13 +0300 Subject: [PATCH 124/126] Revert "Remove no longer required constant" This reverts commit 6f4cdccf6c5430991c8e4c87a6ad60698faff4ce. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 87e49fcc5e..2c91b17917 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -27,6 +27,7 @@ namespace osu.Game.Screens.Ranking public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler { protected const float BACKGROUND_BLUR = 20; + private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; public override bool DisallowExternalBeatmapRulesetChanges => true; From 2f3ac61b479bae1e4ecef58ffef024809ac1f5b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 9 May 2022 10:35:14 +0300 Subject: [PATCH 125/126] Revert "Replace vertical scrolling in results screen with size-preserving container" This reverts commit 298c2a1828bf628d59a5bd7ec1b671e925732ec9. --- .../MultiplayerTeamResultsScreen.cs | 6 ++--- osu.Game/Screens/Ranking/ResultsScreen.cs | 26 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs index 117415ac8f..3f0f3e043c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs @@ -46,9 +46,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { const float winner_background_half_height = 250; - Content.Anchor = Content.Origin = Anchor.TopCentre; - Content.Scale = new Vector2(0.9f); - Content.Y = 75; + VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre; + VerticalScrollContent.Scale = new Vector2(0.9f); + VerticalScrollContent.Y = 75; var redScore = teamScores.First().Value; var blueScore = teamScores.Last().Value; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2c91b17917..98514cd846 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -40,7 +41,7 @@ namespace osu.Game.Screens.Ranking protected ScorePanelList ScorePanelList { get; private set; } - protected Container Content { get; private set; } + protected VerticalScrollContainer VerticalScrollContent { get; private set; } [Resolved(CanBeNull = true)] private Player player { get; set; } @@ -78,9 +79,10 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - Content = new DrawSizePreservingFillContainer + VerticalScrollContent = new VerticalScrollContainer { RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, Child = new Container { RelativeSizeAxes = Axes.Both, @@ -344,5 +346,25 @@ namespace osu.Game.Screens.Ranking public void OnReleased(KeyBindingReleaseEvent e) { } + + protected class VerticalScrollContainer : OsuScrollContainer + { + protected override Container Content => content; + + private readonly Container content; + + public VerticalScrollContainer() + { + Masking = false; + + base.Content.Add(content = new Container { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + content.Height = Math.Max(screen_height, DrawHeight); + } + } } } From 72552ecc855a1a30483dd80d4b9c9c41726ac7c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 May 2022 16:49:20 +0900 Subject: [PATCH 126/126] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2866ec24a6..97d9dbc380 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fa7563da55..2f32c843c0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index f987ae9bf8..b483267696 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - +