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