1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 12:45:09 +08:00

Merge branch 'add-mod-utils' into mod-consistency-function

This commit is contained in:
smoogipoo 2021-02-02 18:41:35 +09:00
commit 5881b8be96
2 changed files with 78 additions and 33 deletions

View File

@ -18,50 +18,88 @@ namespace osu.Game.Tests.Mods
[Test]
public void TestModIsCompatibleByItself()
{
var mod = new Mock<Mod>();
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object }));
}
[Test]
public void TestIncompatibleThroughTopLevel()
{
var mod1 = new Mock<Mod>();
var mod2 = new Mock<Mod>();
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False);
}
[Test]
public void TestIncompatibleThroughMultiMod()
public void TestMultiModIncompatibleWithTopLevel()
{
var mod1 = new Mock<Mod>();
var mod1 = new Mock<CustomMod1>();
// The nested mod.
var mod2 = new Mock<Mod>();
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() });
var multiMod = new MultiMod(new MultiMod(mod2.Object));
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod1.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, multiMod }), Is.False);
}
[Test]
public void TestTopLevelIncompatibleWithMultiMod()
{
// The nested mod.
var mod1 = new Mock<CustomMod1>();
var multiMod = new MultiMod(new MultiMod(mod1.Object));
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(CustomMod1) });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, multiMod }), Is.False);
}
[Test]
public void TestCompatibleMods()
{
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.True);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.True);
}
[Test]
public void TestIncompatibleThroughBaseType()
{
var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(Mod) });
// Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False);
}
[Test]
public void TestAllowedThroughMostDerivedType()
{
var mod = new Mock<Mod>();
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() }));
}
[Test]
public void TestNotAllowedThroughBaseType()
{
var mod = new Mock<Mod>();
var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False);
}
@ -88,5 +126,13 @@ namespace osu.Game.Tests.Mods
else
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
}
public abstract class CustomMod1 : Mod
{
}
public abstract class CustomMod2 : Mod
{
}
}
}

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.TypeExtensions;
using osu.Game.Rulesets.Mods;
#nullable enable
@ -29,7 +28,7 @@ namespace osu.Game.Utils
{
// Prevent multiple-enumeration.
var combinationList = combination as ICollection<Mod> ?? combination.ToArray();
return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes);
return CheckCompatibleSet(combinationList, out _) && CheckAllowed(combinationList, allowedTypes);
}
/// <summary>
@ -38,32 +37,32 @@ namespace osu.Game.Utils
/// <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, out List<Mod>? invalidMods)
{
var incompatibleTypes = new HashSet<Type>();
var incomingTypes = new HashSet<Type>();
combination = FlattenMods(combination).ToArray();
invalidMods = null;
foreach (var mod in combination.SelectMany(FlattenMod))
foreach (var mod in combination)
{
// Add the new mod incompatibilities, checking whether any match the existing mod types.
foreach (var t in mod.IncompatibleMods)
foreach (var type 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);
foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m)))
{
invalidMods ??= new List<Mod>();
invalidMods.Add(invalid);
}
}
}
return true;
return invalidMods == null;
}
/// <summary>