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) {