1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 00:02:56 +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] [Test]
public void TestModIsCompatibleByItself() public void TestModIsCompatibleByItself()
{ {
var mod = new Mock<Mod>(); var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object }));
} }
[Test] [Test]
public void TestIncompatibleThroughTopLevel() public void TestIncompatibleThroughTopLevel()
{ {
var mod1 = new Mock<Mod>(); var mod1 = new Mock<CustomMod1>();
var mod2 = new Mock<Mod>(); var mod2 = new Mock<CustomMod2>();
mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() });
// Test both orderings. // Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False);
} }
[Test] [Test]
public void TestIncompatibleThroughMultiMod() public void TestMultiModIncompatibleWithTopLevel()
{ {
var mod1 = new Mock<Mod>(); var mod1 = new Mock<CustomMod1>();
// The nested mod. // The nested mod.
var mod2 = new Mock<Mod>(); var mod2 = new Mock<CustomMod2>();
mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() });
var multiMod = new MultiMod(new MultiMod(mod2.Object)); var multiMod = new MultiMod(new MultiMod(mod2.Object));
// Test both orderings. // Test both orderings.
Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod1.Object }), Is.False);
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), 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] [Test]
public void TestAllowedThroughMostDerivedType() public void TestAllowedThroughMostDerivedType()
{ {
var mod = new Mock<Mod>(); var mod = new Mock<CustomMod1>();
Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() }));
} }
[Test] [Test]
public void TestNotAllowedThroughBaseType() 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); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False);
} }
@ -88,5 +126,13 @@ namespace osu.Game.Tests.Mods
else else
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.TypeExtensions;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
#nullable enable #nullable enable
@ -29,7 +28,7 @@ namespace osu.Game.Utils
{ {
// Prevent multiple-enumeration. // Prevent multiple-enumeration.
var combinationList = combination as ICollection<Mod> ?? combination.ToArray(); var combinationList = combination as ICollection<Mod> ?? combination.ToArray();
return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes); return CheckCompatibleSet(combinationList, out _) && CheckAllowed(combinationList, allowedTypes);
} }
/// <summary> /// <summary>
@ -38,32 +37,32 @@ namespace osu.Game.Utils
/// <param name="combination">The <see cref="Mod"/> combination to check.</param> /// <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> /// <returns>Whether all <see cref="Mod"/>s in the combination are compatible with each-other.</returns>
public static bool CheckCompatibleSet(IEnumerable<Mod> combination) public static bool CheckCompatibleSet(IEnumerable<Mod> combination)
{ => CheckCompatibleSet(combination, out _);
var incompatibleTypes = new HashSet<Type>();
var incomingTypes = new HashSet<Type>();
foreach (var mod in combination.SelectMany(FlattenMod)) /// <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)
{ {
// Add the new mod incompatibilities, checking whether any match the existing mod types. combination = FlattenMods(combination).ToArray();
foreach (var t in mod.IncompatibleMods) invalidMods = null;
{
if (incomingTypes.Contains(t))
return false;
incompatibleTypes.Add(t); foreach (var mod in combination)
{
foreach (var type in mod.IncompatibleMods)
{
foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m)))
{
invalidMods ??= new List<Mod>();
invalidMods.Add(invalid);
} }
// 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; return invalidMods == null;
} }
/// <summary> /// <summary>