1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 19:54:15 +08:00

Fix non-default mod settings allowing for duplicate freestyle mod selection in multiplayer (#37646)

Fixes a bug that allowed for selecting the same mod twice in multiplayer
if the playlist entry has non-default settings for a required mod.
This happens due to a strict equality mod check in the mod set
compatibility check function, which only considers mods duplicates if
their settings are exactly the same. Replacing with a more lenient
`Type` check fixes this.

Adds a regression test for this behavior

Fixes https://github.com/ppy/osu/issues/37625.

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
This commit is contained in:
triacontakai
2026-05-07 02:59:54 -04:00
committed by GitHub
Unverified
parent b8729cb7fd
commit 3f5c113394
3 changed files with 47 additions and 1 deletions
+19
View File
@@ -7,8 +7,10 @@ using System.Linq;
using Moq;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@@ -33,6 +35,17 @@ namespace osu.Game.Tests.Mods
Assert.That(invalid, Is.EquivalentTo(new[] { mod.Object }));
}
[Test]
public void TestModIsNotCompatibleWithItselfEvenIfSettingsDiffer()
{
var mod1 = new Mock<CustomMod3>();
var mod2 = new Mock<CustomMod3>();
mod2.Setup(m => m.Setting).Returns(new BindableBool(true));
Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }, out var invalid), Is.False);
Assert.That(invalid, Is.EquivalentTo(new[] { mod2.Object }));
}
[Test]
public void TestModIsCompatibleByItself()
{
@@ -397,6 +410,12 @@ namespace osu.Game.Tests.Mods
{
}
public abstract class CustomMod3 : Mod, IModCompatibilitySpecification
{
[SettingSource("Setting")]
public virtual BindableBool Setting { get; } = new BindableBool();
}
private class InvalidMultiplayerMod : Mod
{
public override string Name => string.Empty;
@@ -330,6 +330,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
}
[Test]
public void TestModSelectOverlayNonDefaultSettings()
{
AddStep("add playlist item", () =>
{
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods =
[
new APIMod(new OsuModSuddenDeath { FailOnSliderTail = { Value = true } }),
],
AllowedMods = [],
Freestyle = true
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
ClickButtonWhenEnabled<UserModSelectButton>();
AddAssert("sudden death not visible", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().ChildrenOfType<ModPanel>().Single(m => m.Mod is ModSuddenDeath).Visible == false);
}
[Test]
public void TestChangeSettingsButtonVisibleForHost()
{
+1 -1
View File
@@ -63,7 +63,7 @@ namespace osu.Game.Utils
{
var m = mods[j];
if (candidate.Equals(m))
if (candidate.GetType() == m.GetType())
{
invalidMods ??= new List<Mod>();
invalidMods.Add(m);