// 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 System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Rulesets.Mods; #nullable enable namespace osu.Game.Utils { /// /// A set of utilities to handle combinations. /// public static class ModUtils { /// /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. /// /// /// The allowed types must contain exact types for the respective s to be allowed. /// /// The s to check. /// The set of allowed types. /// Whether all s are compatible with each-other and appear in the set of allowed types. public static bool CheckCompatibleSetAndAllowed(IEnumerable combination, IEnumerable allowedTypes) { // Prevent multiple-enumeration. var combinationList = combination as ICollection ?? combination.ToArray(); return CheckCompatibleSet(combinationList, out _) && CheckAllowed(combinationList, allowedTypes); } /// /// Checks that all s in a combination are compatible with each-other. /// /// The combination to check. /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination) => CheckCompatibleSet(combination, out _); /// /// Checks that all s in a combination are compatible with each-other. /// /// The combination to check. /// Any invalid mods in the set. /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination, [NotNullWhen(false)] out List? invalidMods) { combination = FlattenMods(combination).ToArray(); invalidMods = null; foreach (var mod in combination) { foreach (var type in mod.IncompatibleMods) { foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) { invalidMods ??= new List(); invalidMods.Add(invalid); } } } return invalidMods == null; } /// /// Checks that all s in a combination appear within a set of allowed types. /// /// /// The set of allowed types must contain exact types for the respective s to be allowed. /// /// The combination to check. /// The set of allowed types. /// Whether all s in the combination are allowed. public static bool CheckAllowed(IEnumerable combination, IEnumerable allowedTypes) { var allowedSet = new HashSet(allowedTypes); return combination.SelectMany(FlattenMod) .All(m => allowedSet.Contains(m.GetType())); } /// /// 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. /// 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) { mods = mods.ToArray(); CheckCompatibleSet(mods, out invalidMods); foreach (var mod in mods) { if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod) { invalidMods ??= new List(); invalidMods.Add(mod); } } return invalidMods == null; } /// /// Flattens a set of s, returning a new set with all s removed. /// /// The set of s to flatten. /// The new set, containing all s in recursively with all s removed. public static IEnumerable FlattenMods(IEnumerable mods) => mods.SelectMany(FlattenMod); /// /// Flattens a , returning a set of s in-place of any s. /// /// The to flatten. /// A set of singular "flattened" s public static IEnumerable FlattenMod(Mod mod) { if (mod is MultiMod multi) { foreach (var m in multi.Mods.SelectMany(FlattenMod)) yield return m; } else yield return mod; } /// /// Returns the underlying value of the given mod setting object. /// Used in for serialization and equality comparison purposes. /// /// The mod setting. public static object GetSettingUnderlyingValue(object setting) { switch (setting) { case Bindable d: return d.Value; case Bindable i: return i.Value; case Bindable f: return f.Value; case Bindable b: return b.Value; case IBindable u: // A mod with unknown (e.g. enum) generic type. var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); Debug.Assert(valueMethod != null); return valueMethod.GetValue(u); default: // fall back for non-bindable cases. return setting; } } } }