// 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.Linq; using osu.Framework.Extensions.TypeExtensions; using osu.Game.Rulesets.Mods; #nullable enable namespace osu.Game.Utils { /// /// A set of utilities to validate combinations. /// public static class ModValidation { /// /// 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 CheckCompatibleAndAllowed(IEnumerable combination, IEnumerable allowedTypes) { // Prevent multiple-enumeration. var combinationList = combination as ICollection ?? combination.ToArray(); return CheckCompatible(combinationList) && 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 CheckCompatible(IEnumerable combination) { var incompatibleTypes = new HashSet(); var incomingTypes = new HashSet(); foreach (var mod in combination.SelectMany(FlattenMod)) { // Add the new mod incompatibilities, checking whether any match the existing mod types. foreach (var t in mod.IncompatibleMods) { if (incomingTypes.Contains(t)) return false; incompatibleTypes.Add(t); } // Add the new mod types, checking whether any match the incompatible types. foreach (var t in mod.GetType().EnumerateBaseTypes()) { if (incomingTypes.Contains(t)) return false; incomingTypes.Add(t); } } return true; } /// /// 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())); } /// /// Determines whether a is in a set of incompatible types. /// /// /// A can be incompatible through its most-declared type or any of its base types. /// /// The to test. /// The set of incompatible types. /// Whether the given is incompatible. private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) => FlattenMod(mod) .SelectMany(m => m.GetType().EnumerateBaseTypes()) .Any(incompatibleTypes.Contains); /// /// 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; } } }