From 1025e1939ba90b96595c3ab0d9fc5a86a3f0527a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Mar 2022 11:54:18 +0300 Subject: [PATCH 01/58] 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 02/58] 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 03/58] 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 04/58] 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 05/58] 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 06/58] 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 07/58] 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 08/58] 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 09/58] 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 10/58] 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 11/58] `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 12/58] 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 13/58] 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 14/58] 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 15/58] 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 16/58] 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 17/58] 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 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 18/58] 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 19/58] 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 20/58] 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 21/58] 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 22/58] 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 23/58] 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 24/58] 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 25/58] 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 26/58] 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 27/58] 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 28/58] 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 cc251ed5c3fe4a50126817d8893fba4a4a591536 Mon Sep 17 00:00:00 2001 From: Hugo Denizart Date: Fri, 6 May 2022 19:52:25 +0200 Subject: [PATCH 29/58] =?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 230c4e27b8baee58b553d651d289a5f199f3c962 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 May 2022 12:56:07 +0900 Subject: [PATCH 30/58] 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 31/58] 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 32/58] 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 33/58] 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 34/58] 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 35/58] 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 36/58] 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 37/58] 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 38/58] 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 39/58] 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 40/58] 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 41/58] 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 42/58] 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 43/58] 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 44/58] 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 45/58] 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 46/58] 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 47/58] 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 48/58] 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 49/58] 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 50/58] 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 51/58] 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 52/58] 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 53/58] 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 54/58] 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 55/58] 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 56/58] 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 57/58] 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 @@ - + From a16f2349aa6c9b2bf4246e1fc970dcebf5ada2e4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 9 May 2022 17:55:40 +0900 Subject: [PATCH 58/58] Fix next queued item not selecting after gameplay --- .../TestSceneMultiplayerMatchSubScreen.cs | 37 +++++++++++++++++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 8 ++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 2abde82e92..6173580f0b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -176,5 +177,41 @@ namespace osu.Game.Tests.Visual.Multiplayer .ChildrenOfType() .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } + + [Test] + public void TestNextPlaylistItemSelectedAfterCompletion() + { + AddStep("add two playlist items", () => + { + SelectedRoom.Value.Playlist.AddRange(new[] + { + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }, + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + + AddStep("change user to loaded", () => MultiplayerClient.ChangeState(MultiplayerUserState.Loaded)); + AddUntilStep("user playing", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Playing); + AddStep("abort gameplay", () => MultiplayerClient.AbortGameplay()); + + AddUntilStep("last playlist item selected", () => + { + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.APIRoom?.Playlist.Last().ID); + return lastItem.IsSelectedItem; + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 459b861d96..2618e15d31 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -65,6 +65,8 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; + public bool IsSelectedItem => SelectedItem.Value?.ID == Item.ID; + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); @@ -128,12 +130,10 @@ namespace osu.Game.Screens.OnlinePlay SelectedItem.BindValueChanged(selected => { - bool isCurrent = selected.NewValue == Model; - if (!valid.Value) { // Don't allow selection when not valid. - if (isCurrent) + if (IsSelectedItem) { SelectedItem.Value = selected.OldValue; } @@ -142,7 +142,7 @@ namespace osu.Game.Screens.OnlinePlay return; } - maskingContainer.BorderThickness = isCurrent ? 5 : 0; + maskingContainer.BorderThickness = IsSelectedItem ? 5 : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));