mirror of https://github.com/ppy/osu
219 lines
9.0 KiB
C#
219 lines
9.0 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
|
|
using osu.Game.Rulesets.Mods;
|
|
|
|
#nullable enable
|
|
|
|
namespace osu.Game.Utils
|
|
{
|
|
/// <summary>
|
|
/// A set of utilities to handle <see cref="Mod"/> combinations.
|
|
/// </summary>
|
|
public static class ModUtils
|
|
{
|
|
/// <summary>
|
|
/// Checks that all <see cref="Mod"/>s are compatible with each-other, and that all appear within a set of allowed types.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The allowed types must contain exact <see cref="Mod"/> types for the respective <see cref="Mod"/>s to be allowed.
|
|
/// </remarks>
|
|
/// <param name="combination">The <see cref="Mod"/>s to check.</param>
|
|
/// <param name="allowedTypes">The set of allowed <see cref="Mod"/> types.</param>
|
|
/// <returns>Whether all <see cref="Mod"/>s are compatible with each-other and appear in the set of allowed types.</returns>
|
|
public static bool CheckCompatibleSetAndAllowed(IEnumerable<Mod> combination, IEnumerable<Type> allowedTypes)
|
|
{
|
|
// Prevent multiple-enumeration.
|
|
var combinationList = combination as ICollection<Mod> ?? combination.ToArray();
|
|
return CheckCompatibleSet(combinationList, out _) && CheckAllowed(combinationList, allowedTypes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks that all <see cref="Mod"/>s in a combination are compatible with each-other.
|
|
/// </summary>
|
|
/// <param name="combination">The <see cref="Mod"/> combination to check.</param>
|
|
/// <returns>Whether all <see cref="Mod"/>s in the combination are compatible with each-other.</returns>
|
|
public static bool CheckCompatibleSet(IEnumerable<Mod> combination)
|
|
=> CheckCompatibleSet(combination, out _);
|
|
|
|
/// <summary>
|
|
/// Checks that all <see cref="Mod"/>s in a combination are compatible with each-other.
|
|
/// </summary>
|
|
/// <param name="combination">The <see cref="Mod"/> combination to check.</param>
|
|
/// <param name="invalidMods">Any invalid mods in the set.</param>
|
|
/// <returns>Whether all <see cref="Mod"/>s in the combination are compatible with each-other.</returns>
|
|
public static bool CheckCompatibleSet(IEnumerable<Mod> combination, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
|
{
|
|
var mods = FlattenMods(combination).ToArray();
|
|
invalidMods = null;
|
|
|
|
// ensure there are no duplicate mod definitions.
|
|
for (int i = 0; i < mods.Length; i++)
|
|
{
|
|
var candidate = mods[i];
|
|
|
|
for (int j = i + 1; j < mods.Length; j++)
|
|
{
|
|
var m = mods[j];
|
|
|
|
if (candidate.Equals(m))
|
|
{
|
|
invalidMods ??= new List<Mod>();
|
|
invalidMods.Add(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var mod in mods)
|
|
{
|
|
foreach (var type in mod.IncompatibleMods)
|
|
{
|
|
foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m)))
|
|
{
|
|
if (invalid == mod)
|
|
continue;
|
|
|
|
invalidMods ??= new List<Mod>();
|
|
invalidMods.Add(invalid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return invalidMods == null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks that all <see cref="Mod"/>s in a combination appear within a set of allowed types.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The set of allowed types must contain exact <see cref="Mod"/> types for the respective <see cref="Mod"/>s to be allowed.
|
|
/// </remarks>
|
|
/// <param name="combination">The <see cref="Mod"/> combination to check.</param>
|
|
/// <param name="allowedTypes">The set of allowed <see cref="Mod"/> types.</param>
|
|
/// <returns>Whether all <see cref="Mod"/>s in the combination are allowed.</returns>
|
|
public static bool CheckAllowed(IEnumerable<Mod> combination, IEnumerable<Type> allowedTypes)
|
|
{
|
|
var allowedSet = new HashSet<Type>(allowedTypes);
|
|
|
|
return combination.SelectMany(FlattenMod)
|
|
.All(m => allowedSet.Contains(m.GetType()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check the provided combination of mods are valid for a local gameplay session.
|
|
/// </summary>
|
|
/// <param name="mods">The mods to check.</param>
|
|
/// <param name="invalidMods">Invalid mods, if any were found. Can be null if all mods were valid.</param>
|
|
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
|
public static bool CheckValidForGameplay(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? 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<Mod>();
|
|
invalidMods.Add(mod);
|
|
}
|
|
}
|
|
|
|
return invalidMods == null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flattens a set of <see cref="Mod"/>s, returning a new set with all <see cref="MultiMod"/>s removed.
|
|
/// </summary>
|
|
/// <param name="mods">The set of <see cref="Mod"/>s to flatten.</param>
|
|
/// <returns>The new set, containing all <see cref="Mod"/>s in <paramref name="mods"/> recursively with all <see cref="MultiMod"/>s removed.</returns>
|
|
public static IEnumerable<Mod> FlattenMods(IEnumerable<Mod> mods) => mods.SelectMany(FlattenMod);
|
|
|
|
/// <summary>
|
|
/// Flattens a <see cref="Mod"/>, returning a set of <see cref="Mod"/>s in-place of any <see cref="MultiMod"/>s.
|
|
/// </summary>
|
|
/// <param name="mod">The <see cref="Mod"/> to flatten.</param>
|
|
/// <returns>A set of singular "flattened" <see cref="Mod"/>s</returns>
|
|
public static IEnumerable<Mod> FlattenMod(Mod mod)
|
|
{
|
|
if (mod is MultiMod multi)
|
|
{
|
|
foreach (var m in multi.Mods.SelectMany(FlattenMod))
|
|
yield return m;
|
|
}
|
|
else
|
|
yield return mod;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the underlying value of the given mod setting object.
|
|
/// Used in <see cref="APIMod"/> for serialization and equality comparison purposes.
|
|
/// </summary>
|
|
/// <param name="setting">The mod setting.</param>
|
|
public static object GetSettingUnderlyingValue(object setting)
|
|
{
|
|
switch (setting)
|
|
{
|
|
case Bindable<double> d:
|
|
return d.Value;
|
|
|
|
case Bindable<int> i:
|
|
return i.Value;
|
|
|
|
case Bindable<float> f:
|
|
return f.Value;
|
|
|
|
case Bindable<bool> b:
|
|
return b.Value;
|
|
|
|
case IBindable u:
|
|
// A mod with unknown (e.g. enum) generic type.
|
|
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
|
|
Debug.Assert(valueMethod != null);
|
|
return valueMethod.GetValue(u);
|
|
|
|
default:
|
|
// fall back for non-bindable cases.
|
|
return setting;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies all proposed mods are valid for a given ruleset and returns instantiated <see cref="Mod"/>s for further processing.
|
|
/// </summary>
|
|
/// <param name="ruleset">The ruleset to verify mods against.</param>
|
|
/// <param name="proposedMods">The proposed mods.</param>
|
|
/// <param name="valid">Mods instantiated from <paramref name="proposedMods"/> which were valid for the given <paramref name="ruleset"/>.</param>
|
|
/// <returns>Whether all <paramref name="proposedMods"/> were valid for the given <paramref name="ruleset"/>.</returns>
|
|
public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable<APIMod> proposedMods, out List<Mod> valid)
|
|
{
|
|
valid = new List<Mod>();
|
|
bool proposedWereValid = true;
|
|
|
|
foreach (var apiMod in proposedMods)
|
|
{
|
|
try
|
|
{
|
|
// will throw if invalid
|
|
valid.Add(apiMod.ToMod(ruleset));
|
|
}
|
|
catch
|
|
{
|
|
proposedWereValid = false;
|
|
}
|
|
}
|
|
|
|
return proposedWereValid;
|
|
}
|
|
}
|
|
}
|