Skip to content

Commit ecc12ab

Browse files
authored
Merge pull request #31850 from smoogipoo/freestyle-mods
Allow user mods in multiplayer freestyle
2 parents 7490bca + 550d21d commit ecc12ab

16 files changed

+299
-193
lines changed

osu.Game.Tests/Mods/ModUtilsTest.cs

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Moq;
77
using NUnit.Framework;
88
using osu.Framework.Localisation;
9+
using osu.Game.Online.Rooms;
910
using osu.Game.Rulesets.Mods;
1011
using osu.Game.Rulesets.Osu;
1112
using osu.Game.Rulesets.Osu.Mods;
@@ -342,6 +343,40 @@ public void TestFormatScoreMultiplier()
342343
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
343344
}
344345

346+
[Test]
347+
public void TestRoomModValidity()
348+
{
349+
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModHardRock(), MatchType.Playlists));
350+
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModDoubleTime(), MatchType.Playlists));
351+
Assert.IsTrue(ModUtils.IsValidModForMatchType(new ModAdaptiveSpeed(), MatchType.Playlists));
352+
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModAutoplay(), MatchType.Playlists));
353+
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModTouchDevice(), MatchType.Playlists));
354+
355+
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModHardRock(), MatchType.HeadToHead));
356+
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModDoubleTime(), MatchType.HeadToHead));
357+
// For now, adaptive speed isn't allowed in multiplayer because it's a per-user rate adjustment.
358+
Assert.IsFalse(ModUtils.IsValidModForMatchType(new ModAdaptiveSpeed(), MatchType.HeadToHead));
359+
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModAutoplay(), MatchType.HeadToHead));
360+
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModTouchDevice(), MatchType.HeadToHead));
361+
}
362+
363+
[Test]
364+
public void TestRoomFreeModValidity()
365+
{
366+
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModHardRock(), MatchType.Playlists));
367+
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModDoubleTime(), MatchType.Playlists));
368+
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new ModAdaptiveSpeed(), MatchType.Playlists));
369+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModAutoplay(), MatchType.Playlists));
370+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModTouchDevice(), MatchType.Playlists));
371+
372+
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModHardRock(), MatchType.HeadToHead));
373+
// For now, all rate adjustment mods aren't allowed as free mods in multiplayer.
374+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModDoubleTime(), MatchType.HeadToHead));
375+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new ModAdaptiveSpeed(), MatchType.HeadToHead));
376+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModAutoplay(), MatchType.HeadToHead));
377+
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModTouchDevice(), MatchType.HeadToHead));
378+
}
379+
345380
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
346381
{
347382
}

osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private void createFreeModSelect()
166166
Anchor = Anchor.BottomRight,
167167
Origin = Anchor.BottomRight,
168168
Y = -ScreenFooter.HEIGHT,
169-
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
169+
FreeMods = { BindTarget = freeModSelectOverlay.SelectedMods },
170170
},
171171
footer = new ScreenFooter(),
172172
},

osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs

+37
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
using osu.Game.Graphics.Cursor;
1313
using osu.Game.Graphics.UserInterface;
1414
using osu.Game.Online;
15+
using osu.Game.Online.API;
1516
using osu.Game.Online.API.Requests.Responses;
1617
using osu.Game.Online.Multiplayer;
1718
using osu.Game.Online.Rooms;
19+
using osu.Game.Rulesets.Catch.Mods;
1820
using osu.Game.Rulesets.Mods;
1921
using osu.Game.Rulesets.Osu.Mods;
22+
using osu.Game.Rulesets.Taiko.Mods;
2023
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
2124
using osu.Game.Users;
2225
using osuTK;
@@ -393,6 +396,40 @@ public void TestModOverlap()
393396
});
394397
}
395398

399+
[Test]
400+
public void TestModsAndRuleset()
401+
{
402+
AddStep("add another user", () =>
403+
{
404+
MultiplayerClient.AddUser(new APIUser
405+
{
406+
Id = 0,
407+
Username = "User 0",
408+
RulesetsStatistics = new Dictionary<string, UserStatistics>
409+
{
410+
{
411+
Ruleset.Value.ShortName,
412+
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
413+
}
414+
},
415+
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
416+
});
417+
418+
MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable());
419+
});
420+
421+
AddStep("set user styles", () =>
422+
{
423+
MultiplayerClient.ChangeUserStyle(API.LocalUser.Value.OnlineID, 259, 1);
424+
MultiplayerClient.ChangeUserMods(API.LocalUser.Value.OnlineID,
425+
[new APIMod(new TaikoModConstantSpeed()), new APIMod(new TaikoModHidden()), new APIMod(new TaikoModFlashlight()), new APIMod(new TaikoModHardRock())]);
426+
427+
MultiplayerClient.ChangeUserStyle(0, 259, 2);
428+
MultiplayerClient.ChangeUserMods(0,
429+
[new APIMod(new CatchModFloatingFruits()), new APIMod(new CatchModHidden()), new APIMod(new CatchModMirror())]);
430+
});
431+
}
432+
396433
private void createNewParticipantsList()
397434
{
398435
ParticipantsList? participantsList = null;

osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ private void assertItemInHistoryListStep(int playlistItemId, int visualIndex)
266266

267267
private void assertQueueTabCount(int count)
268268
{
269-
string queueTabText = count > 0 ? $"Queue ({count})" : "Queue";
269+
string queueTabText = count > 0 ? $"Up next ({count})" : "Up next";
270270
AddUntilStep($"Queue tab shows \"{queueTabText}\"", () =>
271271
{
272272
return this.ChildrenOfType<OsuTabControl<MultiplayerPlaylistDisplayMode>.OsuTabItem>()

osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs

-2
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,11 @@ public OverlinedHeader(LocalisableString title)
5353
{
5454
RelativeSizeAxes = Axes.X,
5555
Height = 2,
56-
Margin = new MarginPadding { Bottom = 2 }
5756
},
5857
new FillFlowContainer
5958
{
6059
AutoSizeAxes = Axes.Both,
6160
Direction = FillDirection.Horizontal,
62-
Margin = new MarginPadding { Top = 5 },
6361
Spacing = new Vector2(10, 0),
6462
Children = new Drawable[]
6563
{

osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs

+10-18
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,22 @@
1111
using osu.Framework.Graphics.Containers;
1212
using osu.Framework.Graphics.Shapes;
1313
using osu.Framework.Graphics.Sprites;
14-
using osu.Framework.Graphics.UserInterface;
1514
using osu.Game.Graphics;
1615
using osu.Game.Graphics.Sprites;
1716
using osu.Game.Graphics.UserInterface;
17+
using osu.Game.Localisation;
1818
using osu.Game.Rulesets.Mods;
1919
using osu.Game.Screens.Select;
2020
using osuTK;
21-
using osu.Game.Localisation;
2221

2322
namespace osu.Game.Screens.OnlinePlay
2423
{
25-
public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
24+
public partial class FooterButtonFreeMods : FooterButton
2625
{
27-
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());
26+
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>();
27+
public readonly IBindable<bool> Freestyle = new Bindable<bool>();
2828

29-
public Bindable<IReadOnlyList<Mod>> Current
30-
{
31-
get => current.Current;
32-
set
33-
{
34-
ArgumentNullException.ThrowIfNull(value);
35-
36-
current.Current = value;
37-
}
38-
}
29+
protected override bool IsActive => FreeMods.Value.Count > 0;
3930

4031
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
4132

@@ -104,7 +95,8 @@ protected override void LoadComplete()
10495
{
10596
base.LoadComplete();
10697

107-
Current.BindValueChanged(_ => updateModDisplay(), true);
98+
Freestyle.BindValueChanged(_ => updateModDisplay());
99+
FreeMods.BindValueChanged(_ => updateModDisplay(), true);
108100
}
109101

110102
/// <summary>
@@ -114,16 +106,16 @@ private void toggleAllFreeMods()
114106
{
115107
var availableMods = allAvailableAndValidMods.ToArray();
116108

117-
Current.Value = Current.Value.Count == availableMods.Length
109+
FreeMods.Value = FreeMods.Value.Count == availableMods.Length
118110
? Array.Empty<Mod>()
119111
: availableMods;
120112
}
121113

122114
private void updateModDisplay()
123115
{
124-
int currentCount = Current.Value.Count;
116+
int currentCount = FreeMods.Value.Count;
125117

126-
if (currentCount == allAvailableAndValidMods.Count())
118+
if (currentCount == allAvailableAndValidMods.Count() || Freestyle.Value)
127119
{
128120
count.Text = "all";
129121
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);

osu.Game/Screens/OnlinePlay/FooterButtonFreestyle.cs

+7-12
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,18 @@
88
using osu.Framework.Graphics;
99
using osu.Framework.Graphics.Containers;
1010
using osu.Framework.Graphics.Shapes;
11-
using osu.Framework.Graphics.UserInterface;
1211
using osu.Game.Graphics;
1312
using osu.Game.Graphics.Sprites;
14-
using osu.Game.Screens.Select;
1513
using osu.Game.Localisation;
14+
using osu.Game.Screens.Select;
1615

1716
namespace osu.Game.Screens.OnlinePlay
1817
{
19-
public partial class FooterButtonFreestyle : FooterButton, IHasCurrentValue<bool>
18+
public partial class FooterButtonFreestyle : FooterButton
2019
{
21-
private readonly BindableWithCurrent<bool> current = new BindableWithCurrent<bool>();
20+
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
2221

23-
public Bindable<bool> Current
24-
{
25-
get => current.Current;
26-
set => current.Current = value;
27-
}
22+
protected override bool IsActive => Freestyle.Value;
2823

2924
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
3025

@@ -37,7 +32,7 @@ public Bindable<bool> Current
3732
public FooterButtonFreestyle()
3833
{
3934
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
40-
base.Action = () => current.Value = !current.Value;
35+
base.Action = () => Freestyle.Value = !Freestyle.Value;
4136
}
4237

4338
[BackgroundDependencyLoader]
@@ -81,12 +76,12 @@ protected override void LoadComplete()
8176
{
8277
base.LoadComplete();
8378

84-
Current.BindValueChanged(_ => updateDisplay(), true);
79+
Freestyle.BindValueChanged(_ => updateDisplay(), true);
8580
}
8681

8782
private void updateDisplay()
8883
{
89-
if (current.Value)
84+
if (Freestyle.Value)
9085
{
9186
text.Text = "on";
9287
text.FadeColour(colours.Gray2, 200, Easing.OutQuint);

osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs

+8-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.ComponentModel;
7-
using System.Diagnostics;
87
using System.Linq;
98
using osu.Framework.Allocation;
109
using osu.Framework.Audio;
@@ -440,11 +439,14 @@ private void updateSpecifics()
440439

441440
var rulesetInstance = GetGameplayRuleset().CreateInstance();
442441

442+
Mod[] allowedMods = item.Freestyle
443+
? rulesetInstance.AllMods.OfType<Mod>().Where(m => ModUtils.IsValidFreeModForMatchType(m, Room.Type)).ToArray()
444+
: item.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
445+
443446
// Remove any user mods that are no longer allowed.
444-
Mod[] allowedMods = item.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
445447
Mod[] newUserMods = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToArray();
446448
if (!newUserMods.SequenceEqual(UserMods.Value))
447-
UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList();
449+
UserMods.Value = newUserMods;
448450

449451
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
450452
int beatmapId = GetGameplayBeatmap().OnlineID;
@@ -455,14 +457,7 @@ private void updateSpecifics()
455457
Mods.Value = GetGameplayMods().Select(m => m.ToMod(rulesetInstance)).ToArray();
456458
Ruleset.Value = GetGameplayRuleset();
457459

458-
bool freeMod = item.AllowedMods.Any();
459-
bool freestyle = item.Freestyle;
460-
461-
// For now, the game can never be in a state where freemod and freestyle are on at the same time.
462-
// This will change, but due to the current implementation if this was to occur drawables will overlap so let's assert.
463-
Debug.Assert(!freeMod || !freestyle);
464-
465-
if (freeMod)
460+
if (allowedMods.Length > 0)
466461
{
467462
UserModsSection.Show();
468463
UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
@@ -474,7 +469,7 @@ private void updateSpecifics()
474469
UserModsSelectOverlay.IsValidMod = _ => false;
475470
}
476471

477-
if (freestyle)
472+
if (item.Freestyle)
478473
{
479474
UserStyleSection.Show();
480475

@@ -487,7 +482,7 @@ private void updateSpecifics()
487482
UserStyleDisplayContainer.Child = new DrawableRoomPlaylistItem(gameplayItem, true)
488483
{
489484
AllowReordering = false,
490-
AllowEditing = freestyle,
485+
AllowEditing = true,
491486
RequestEdit = _ => OpenStyleSelection()
492487
};
493488
}

osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public QueueTabItem()
3232
protected override void LoadComplete()
3333
{
3434
base.LoadComplete();
35-
QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true);
35+
QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Up next ({QueueItems.Count})" : "Up next", true);
3636
}
3737
}
3838
}

osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs

-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using osu.Game.Graphics.UserInterface;
1212
using osu.Game.Online.Multiplayer;
1313
using osu.Game.Online.Rooms;
14-
using osu.Game.Rulesets.Mods;
1514
using osu.Game.Screens.Select;
1615

1716
namespace osu.Game.Screens.OnlinePlay.Multiplayer
@@ -122,9 +121,5 @@ protected override bool SelectItem(PlaylistItem item)
122121
}
123122

124123
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
125-
126-
protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.ValidForMultiplayer;
127-
128-
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidForMultiplayerAsFreeMod;
129124
}
130125
}

0 commit comments

Comments
 (0)