Skip to content

Commit 585f1c1

Browse files
authored
Merge pull request #283 from smoogipoo/freestyle-mods
Allow playlist items with both freestyle and mods
2 parents 3041230 + 63178f0 commit 585f1c1

File tree

7 files changed

+38
-70
lines changed

7 files changed

+38
-70
lines changed

.idea/.idea.osu.Server.Spectator/.idea/projectSettingsUpdater.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SampleMultiplayerClient/SampleMultiplayerClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.3" />
1212
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.3" />
1313
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
14-
<PackageReference Include="ppy.osu.Game" Version="2025.420.0" />
14+
<PackageReference Include="ppy.osu.Game" Version="2025.530.0" />
1515
</ItemGroup>
1616

1717
</Project>

SampleSpectatorClient/SampleSpectatorClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.3" />
1212
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.3" />
1313
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
14-
<PackageReference Include="ppy.osu.Game" Version="2025.420.0" />
14+
<PackageReference Include="ppy.osu.Game" Version="2025.530.0" />
1515
</ItemGroup>
1616

1717
</Project>

osu.Server.Spectator.Tests/Multiplayer/FreestyleTests.cs

Lines changed: 23 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,19 @@ public async Task AddItem()
2929
{
3030
await Hub.JoinRoom(ROOM_ID);
3131
await Hub.AddPlaylistItem(new MultiplayerPlaylistItem
32-
{
33-
BeatmapChecksum = "checksum",
34-
BeatmapID = 1234,
35-
Freestyle = true
36-
});
37-
}
38-
39-
[Fact]
40-
public async Task AddItem_WithRequiredModsFails()
41-
{
42-
await Hub.JoinRoom(ROOM_ID);
43-
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.AddPlaylistItem(new MultiplayerPlaylistItem
4432
{
4533
BeatmapChecksum = "checksum",
4634
BeatmapID = 1234,
4735
Freestyle = true,
48-
RequiredMods = [new APIMod(new OsuModHidden())]
49-
}));
50-
}
36+
RequiredMods = [new APIMod(new OsuModHidden())],
37+
});
5138

52-
[Fact]
53-
public async Task AddItem_WithAllowedModsFails()
54-
{
55-
await Hub.JoinRoom(ROOM_ID);
5639
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.AddPlaylistItem(new MultiplayerPlaylistItem
5740
{
5841
BeatmapChecksum = "checksum",
5942
BeatmapID = 1234,
6043
Freestyle = true,
61-
AllowedMods = [new APIMod(new OsuModHidden())]
44+
AllowedMods = [new APIMod(new OsuModHidden())],
6245
}));
6346
}
6447

@@ -244,8 +227,18 @@ await Hub.EditPlaylistItem(new MultiplayerPlaylistItem
244227
ID = 1,
245228
BeatmapChecksum = "checksum",
246229
BeatmapID = 1234,
247-
Freestyle = true
230+
Freestyle = true,
231+
RequiredMods = [new APIMod(new OsuModHidden())],
248232
});
233+
234+
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.EditPlaylistItem(new MultiplayerPlaylistItem
235+
{
236+
ID = 1,
237+
BeatmapChecksum = "checksum",
238+
BeatmapID = 1234,
239+
Freestyle = true,
240+
AllowedMods = [new APIMod(new OsuModHardRock())]
241+
}));
249242
}
250243

251244
/// <summary>
@@ -427,32 +420,6 @@ await Hub.EditPlaylistItem(new MultiplayerPlaylistItem
427420
}
428421
}
429422

430-
[Fact]
431-
public async Task EditItem_WithRequiredModsFails()
432-
{
433-
await Hub.JoinRoom(ROOM_ID);
434-
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.AddPlaylistItem(new MultiplayerPlaylistItem
435-
{
436-
BeatmapChecksum = "checksum",
437-
BeatmapID = 1234,
438-
Freestyle = true,
439-
RequiredMods = [new APIMod(new OsuModHidden())]
440-
}));
441-
}
442-
443-
[Fact]
444-
public async Task EditItem_WithAllowedModsFails()
445-
{
446-
await Hub.JoinRoom(ROOM_ID);
447-
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.AddPlaylistItem(new MultiplayerPlaylistItem
448-
{
449-
BeatmapChecksum = "checksum",
450-
BeatmapID = 1234,
451-
Freestyle = true,
452-
AllowedMods = [new APIMod(new OsuModHidden())]
453-
}));
454-
}
455-
456423
#endregion
457424

458425
#region CurrentItemChanged
@@ -614,30 +581,29 @@ await Hub.EditPlaylistItem(new MultiplayerPlaylistItem
614581
ID = 1,
615582
BeatmapChecksum = "checksum",
616583
BeatmapID = 1234,
617-
Freestyle = true
584+
Freestyle = true,
585+
RequiredMods = [new APIMod(new OsuModHardRock())]
618586
});
619587

620588
// Set user style + mods.
621589
await Hub.ChangeUserStyle(12345, 1);
622-
await Hub.ChangeUserMods(new[] { new APIMod(new TaikoModConstantSpeed()), new APIMod(new TaikoModHardRock()) });
590+
await Hub.ChangeUserMods(new[] { new APIMod(new TaikoModHidden()), new APIMod(new TaikoModConstantSpeed()) });
623591
using (var usage = await Hub.GetRoom(ROOM_ID))
624-
Assert.Equal(["CS", "HR"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
592+
Assert.Equal(["HD", "CS"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
625593

626-
// Try select mod from invalid ruleset.
627-
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.ChangeUserMods(new[] { new APIMod(new OsuModTraceable()) }));
594+
// Try select invalid mod.
595+
await Assert.ThrowsAsync<InvalidStateException>(() => Hub.ChangeUserMods(new[] { new APIMod(new TaikoModEasy()) }));
628596
using (var usage = await Hub.GetRoom(ROOM_ID))
629-
Assert.Equal(["CS", "HR"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
597+
Assert.Equal(["HD", "CS"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
630598

631599
// Try change ruleset.
632600
Receiver.Invocations.Clear();
633601
await Hub.ChangeUserStyle(12345, 0);
634-
using (var usage = await Hub.GetRoom(ROOM_ID))
635-
Assert.Equal(["HR"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
636602

637603
using (var usage = await Hub.GetRoom(ROOM_ID))
638604
{
639-
Assert.Equal(["HR"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
640-
Receiver.Verify(u => u.UserModsChanged(USER_ID, It.Is<IEnumerable<APIMod>>(mods => mods.Single().Acronym == "HR")), Times.Once);
605+
Assert.Equal(["HD"], usage.Item!.Users.Single().Mods.Select(m => m.Acronym));
606+
Receiver.Verify(u => u.UserModsChanged(USER_ID, It.IsAny<IEnumerable<APIMod>>()), Times.Once());
641607
Receiver.Verify(u => u.UserStyleChanged(USER_ID, 12345, 0), Times.Once);
642608
}
643609
}

osu.Server.Spectator/Extensions/MultiplayerPlaylistItemExtensions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static bool ValidateUserMods(this MultiplayerPlaylistItem item, Multiplay
2828
bool proposedWereValid = true;
2929
proposedWereValid &= ModUtils.InstantiateValidModsForRuleset(ruleset, proposedMods, out var valid);
3030

31+
// Freestyle unconditionally allows all freemods.
3132
if (!item.Freestyle)
3233
{
3334
// check allowed by room
@@ -42,7 +43,7 @@ public static bool ValidateUserMods(this MultiplayerPlaylistItem item, Multiplay
4243
}
4344

4445
// check valid as combination
45-
if (!ModUtils.CheckCompatibleSet(valid, out var invalid))
46+
if (!ModUtils.CheckCompatibleSet(item.RequiredMods.Select(m => m.ToMod(ruleset)).Concat(valid), out var invalid))
4647
{
4748
proposedWereValid = false;
4849
foreach (var mod in invalid)
@@ -78,10 +79,10 @@ public static void EnsureModsValid(this MultiplayerPlaylistItem item)
7879
if (!ModUtils.CheckCompatibleSet(requiredMods, out var invalid))
7980
throw new InvalidStateException($"Invalid combination of required mods: {string.Join(',', invalid.Select(m => m.Acronym))}");
8081

81-
if (!ModUtils.CheckValidRequiredModsForMultiplayer(requiredMods, out invalid))
82+
if (!ModUtils.CheckValidRequiredModsForMultiplayer(requiredMods, item.Freestyle, out invalid))
8283
throw new InvalidStateException($"Invalid required mods were selected: {string.Join(',', invalid.Select(m => m.Acronym))}");
8384

84-
if (!ModUtils.CheckValidFreeModsForMultiplayer(allowedMods, out invalid))
85+
if (!ModUtils.CheckValidAllowedModsForMultiplayer(allowedMods, item.Freestyle, out invalid))
8586
throw new InvalidStateException($"Invalid free mods were selected: {string.Join(',', invalid.Select(m => m.Acronym))}");
8687

8788
// check aggregate combinations with each allowed mod individually.

osu.Server.Spectator/Hubs/Multiplayer/MultiplayerQueue.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public async Task AddItem(MultiplayerPlaylistItem item, MultiplayerRoomUser user
118118
if (room.Playlist.Count(i => i.OwnerID == user.UserID && !i.Expired) >= limit)
119119
throw new InvalidStateException($"Can't enqueue more than {limit} items at once.");
120120

121-
if (item.Freestyle && (item.AllowedMods.Any() || item.RequiredMods.Any()))
121+
if (item.Freestyle && item.AllowedMods.Any())
122122
throw new InvalidStateException("Cannot enqueue freestyle item with mods.");
123123

124124
using (var db = dbFactory.GetInstance())
@@ -151,8 +151,8 @@ public async Task EditItem(MultiplayerPlaylistItem item, MultiplayerRoomUser use
151151
if (dbFactory == null)
152152
throw new InvalidOperationException($"Call {nameof(Initialise)} first.");
153153

154-
if (item.Freestyle && (item.AllowedMods.Any() || item.RequiredMods.Any()))
155-
throw new InvalidStateException("Cannot edit freestyle item with mods.");
154+
if (item.Freestyle && item.AllowedMods.Any())
155+
throw new InvalidStateException("Cannot enqueue freestyle item with mods.");
156156

157157
using (var db = dbFactory.GetInstance())
158158
{

osu.Server.Spectator/osu.Server.Spectator.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.3" />
1616
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
1717
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
18-
<PackageReference Include="ppy.osu.Game" Version="2025.420.0" />
19-
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.420.0" />
20-
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.420.0" />
21-
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.420.0" />
22-
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.420.0" />
18+
<PackageReference Include="ppy.osu.Game" Version="2025.530.0" />
19+
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.530.0" />
20+
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.530.0" />
21+
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.530.0" />
22+
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.530.0" />
2323
<PackageReference Include="ppy.osu.Server.OsuQueueProcessor" Version="2025.317.0" />
2424
<PackageReference Include="Sentry.AspNetCore" Version="5.0.1" />
2525
</ItemGroup>

0 commit comments

Comments
 (0)